import math

num_types = ["<class 'int'>", "<class 'float'>"]

# replaces nan items from a series with a given value
def fillna(series, filler=0):
    result = []
    for i in series:
        if (str(type(i)) in num_types) and not math.isnan(i):
            result.append(i)
        else:
            result.append(filler)
    return result

# average of the series
def mean(series):
    s = [s for s in series if s is not None and not math.isnan(s)]
    if len(s) == 0:
        return None
    return sum(s) / float(len(s))

# standard deviateion of the series
def standard_deviation(series):
    var = variance(series)
    if var is None:
        return None
    return variance(series) ** (0.5)

# statistal variance of the series
def variance(series):
    s = [s for s in series if s is not None and not math.isnan(s)]
    if len(s) == 0:
        return None
    series_mean = mean(s)
    return sum((x - series_mean) ** 2.0 for x in s) / (len(s) - 1)

# executes a rule against each item in a series and returns matches, rule should be a lambe
# matches(data, lambda x: x > 2)
# list comprehension is slower compared to loops for shorter lists but faster for longer lists
def matches(series, rule):
    return [i for i, item in enumerate(series) if rule(item)]

# applied a function to a series of values, formula should be a lambda
# f_x(data, lambda x: 3x + 2)
def f_x(series, formula):
    return [formula(item) for item in series]

def series_diff(series_a, series_b, adjustment=None):
    if len(series_a) != len (series_b):
        raise Exception('series_diff: two series must be the same length')
    if adjustment == None:
        adjustment = mean(series_a)
    series = []
    for i in range(len(series_a)):
        series.append(series_a[i] - series_b[i] + adjustment)
    return series

import math

num_types = ["<class 'int'>", "<class 'float'>"]

# replaces nan items from a series with a given value
def fillna(series, filler=0):
    result = []
    for i in series:
        if (str(type(i)) in num_types) and not math.isnan(i):
            result.append(i)
        else:
            result.append(filler)
    return result

# average of the series
def mean(series):
    s = [s for s in series if s is not None and not math.isnan(s)]
    if len(s) == 0:
        return None
    return sum(s) / float(len(s))

# standard deviateion of the series
def standard_deviation(series):
    var = variance(series)
    if var is None:
        return None
    return variance(series) ** (0.5)

# statistal variance of the series
def variance(series):
    s = [s for s in series if s is not None and not math.isnan(s)]
    if len(s) == 0:
        return None
    series_mean = mean(s)
    return sum((x - series_mean) ** 2.0 for x in s) / (len(s) - 1)

# executes a rule against each item in a series and returns matches, rule should be a lambe
# matches(data, lambda x: x > 2)
# list comprehension is slower compared to loops for shorter lists but faster for longer lists
def matches(series, rule):
    return [i for i, item in enumerate(series) if rule(item)]

# applied a function to a series of values, formula should be a lambda
# f_x(data, lambda x: 3x + 2)
def f_x(series, formula):
    return [formula(item) for item in series]

def series_diff(series_a, series_b, adjustment=None):
    if len(series_a) != len (series_b):
        raise Exception('series_diff: two series must be the same length')
    if adjustment == None:
        adjustment = mean(series_a)
    series = []
    for i in range(len(series_a)):
        series.append(series_a[i] - series_b[i] + adjustment)
    return series

# replaces nan items from a series with a given value # average of the series # standard deviateion of the series # statistal variance of the series # executes a rule against each item in a series and returns matches, rule should be a lambe # matches(data, lambda x: x > 2) # list comprehension is slower compared to loops for shorter lists but faster for longer lists # applied a function to a series of values, formula should be a lambda # f_x(data, lambda x: 3x + 2)