рассчитать экспоненциальную скользящую среднюю в питоне - PullRequest
21 голосов
/ 28 января 2009

У меня есть диапазон дат и измерения для каждой из этих дат. Я хотел бы рассчитать экспоненциальное скользящее среднее для каждой из дат. Кто-нибудь знает, как это сделать?

Я новичок в питоне. Похоже, что средние значения не встроены в стандартную библиотеку Python, что кажется мне немного странным. Может быть, я не смотрю в нужном месте.

Итак, учитывая следующий код, как я могу рассчитать скользящее средневзвешенное значение точек IQ для календарных дат?

from datetime import date
days = [date(2008,1,1), date(2008,1,2), date(2008,1,7)]
IQ = [110, 105, 90]

(вероятно, есть лучший способ структурировать данные, любой совет был бы оценен)

Ответы [ 11 ]

19 голосов
/ 28 января 2009

EDIT: Кажется, что mov_average_expw() функция из scikits.timeseries.lib.moving_funcs субмодуль из SciKits (дополнительные инструменты, которые дополняют SciPy ) лучше подходит под формулировку вашего вопроса.


Чтобы вычислить экспоненциальное сглаживание ваших данных с коэффициентом сглаживания alpha (это (1 - alpha) в терминах Википедии):

>>> alpha = 0.5
>>> assert 0 < alpha <= 1.0
>>> av = sum(alpha**n.days * iq 
...     for n, iq in map(lambda (day, iq), today=max(days): (today-day, iq), 
...         sorted(zip(days, IQ), key=lambda p: p[0], reverse=True)))
95.0

Вышесказанное не очень красиво, поэтому давайте немного его рефакторинг:

from collections import namedtuple
from operator    import itemgetter

def smooth(iq_data, alpha=1, today=None):
    """Perform exponential smoothing with factor `alpha`.

    Time period is a day.
    Each time period the value of `iq` drops `alpha` times.
    The most recent data is the most valuable one.
    """
    assert 0 < alpha <= 1

    if alpha == 1: # no smoothing
        return sum(map(itemgetter(1), iq_data))

    if today is None:
        today = max(map(itemgetter(0), iq_data))

    return sum(alpha**((today - date).days) * iq for date, iq in iq_data)

IQData = namedtuple("IQData", "date iq")

if __name__ == "__main__":
    from datetime import date

    days = [date(2008,1,1), date(2008,1,2), date(2008,1,7)]
    IQ = [110, 105, 90]
    iqdata = list(map(IQData, days, IQ))
    print("\n".join(map(str, iqdata)))

    print(smooth(iqdata, alpha=0.5))
* * 1 022 Пример: * 1 023 *
$ python26 smooth.py
IQData(date=datetime.date(2008, 1, 1), iq=110)
IQData(date=datetime.date(2008, 1, 2), iq=105)
IQData(date=datetime.date(2008, 1, 7), iq=90)
95.0
9 голосов
/ 28 января 2009

Я немного погуглил и нашел следующий пример кода (http://osdir.com/ml/python.matplotlib.general/2005-04/msg00044.html):

def ema(s, n):
    """
    returns an n period exponential moving average for
    the time series s

    s is a list ordered from oldest (index 0) to most
    recent (index -1)
    n is an integer

    returns a numeric array of the exponential
    moving average
    """
    s = array(s)
    ema = []
    j = 1

    #get n sma first and calculate the next n period ema
    sma = sum(s[:n]) / n
    multiplier = 2 / float(1 + n)
    ema.append(sma)

    #EMA(current) = ( (Price(current) - EMA(prev) ) x Multiplier) + EMA(prev)
    ema.append(( (s[n] - sma) * multiplier) + sma)

    #now calculate the rest of the values
    for i in s[n+1:]:
        tmp = ( (i - ema[j]) * multiplier) + ema[j]
        j = j + 1
        ema.append(tmp)

    return ema
8 голосов
/ 04 октября 2015

Я всегда вычисляю EMA с помощью панд:

Вот пример, как это сделать:

import pandas as pd
import numpy as np

def ema(values, period):
    values = np.array(values)
    return pd.ewma(values, span=period)[-1]

values = [9, 5, 10, 16, 5]
period = 5

print ema(values, period)

Больше информации о Pandas EWMA:

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.ewma.html

5 голосов
/ 25 июня 2014

В примерах matplotlib.org (http://matplotlib.org/examples/pylab_examples/finance_work2.html) приведен один хороший пример функции экспоненциального скользящего среднего (EMA) с использованием numpy:

def moving_average(x, n, type):
    x = np.asarray(x)
    if type=='simple':
        weights = np.ones(n)
    else:
        weights = np.exp(np.linspace(-1., 0., n))

    weights /= weights.sum()

    a =  np.convolve(x, weights, mode='full')[:len(x)]
    a[:n] = a[n]
    return a
5 голосов
/ 28 января 2009

Мой питон немного заржавел (любой может смело редактировать этот код, чтобы внести исправления, если я как-то испортил синтаксис), но здесь идет ...

def movingAverageExponential(values, alpha, epsilon = 0):

   if not 0 < alpha < 1:
      raise ValueError("out of range, alpha='%s'" % alpha)

   if not 0 <= epsilon < alpha:
      raise ValueError("out of range, epsilon='%s'" % epsilon)

   result = [None] * len(values)

   for i in range(len(result)):
       currentWeight = 1.0

       numerator     = 0
       denominator   = 0
       for value in values[i::-1]:
           numerator     += value * currentWeight
           denominator   += currentWeight

           currentWeight *= alpha
           if currentWeight < epsilon: 
              break

       result[i] = numerator / denominator

   return result

Эта функция перемещается назад, от конца списка к началу, вычисляя экспоненциальную скользящую среднюю для каждого значения, работая в обратном направлении до тех пор, пока весовой коэффициент для элемента не станет меньше указанного эпсилона.

В конце функции происходит обратное изменение значений перед возвратом списка (чтобы они были в правильном порядке для вызывающей стороны).

(ВНИМАНИЕ: если бы я использовал язык, отличный от python, я сначала создал бы пустой массив в натуральную величину, а затем заполнил бы его в обратном порядке, чтобы в конце мне не пришлось обращать его вспять. Но я не думаю, что вы можете объявить большой пустой массив в python. А в списках python добавление намного дешевле, чем добавление, поэтому я построил список в обратном порядке. Пожалуйста, исправьте меня, если я ошибаюсь.)

Аргумент 'alpha' - это коэффициент затухания на каждой итерации. Например, если вы использовали альфа 0,5, то сегодняшнее значение скользящего среднего будет состоять из следующих взвешенных значений:

today:        1.0
yesterday:    0.5
2 days ago:   0.25
3 days ago:   0.125
...etc...

Конечно, если у вас огромный массив значений, значения от десяти до пятнадцати дней назад не будут сильно влиять на средневзвешенное значение сегодняшнего дня. Аргумент 'epsilon' позволяет вам установить точку отсечения, ниже которой вы перестанете заботиться о старых значениях (поскольку их вклад в сегодняшнее значение будет незначительным).

Вы бы вызвали функцию примерно так:

result = movingAverageExponential(values, 0.75, 0.0001)
4 голосов
/ 10 марта 2017

Вы также можете использовать метод фильтра SciPy, потому что EMA является фильтром IIR. Это даст преимущество примерно в 64 раза быстрее, чем измеряется в моей системе с использованием timeit для больших наборов данных по сравнению с подходом enumerate () .

import numpy as np
from scipy.signal import lfilter

x = np.random.normal(size=1234)
alpha = .1 # smoothing coefficient
zi = [x[0]] # seed the filter state with first value
# filter can process blocks of continuous data if <zi> is maintained
y, zi = lfilter([1.-alpha], [1., -alpha], x, zi=zi)
4 голосов
/ 28 января 2009

Я не знаю Python, но для усреднения вы имеете в виду экспоненциально затухающий фильтр нижних частот вида

y_new = y_old + (input - y_old)*alpha

где альфа = дт / тау, дт = временной шаг фильтра, тау = постоянная времени фильтра? (Форма с переменным временным шагом выглядит следующим образом, просто обрезайте dt / tau, чтобы она не превышала 1,0)

y_new = y_old + (input - y_old)*dt/tau

Если вы хотите отфильтровать что-то вроде даты, убедитесь, что вы преобразовали в число с плавающей запятой, такое как # секунд с 1 января 1970 года.

2 голосов
/ 13 февраля 2014

Я нашел приведенный выше фрагмент кода @earino довольно полезным, но мне нужно было что-то, что могло бы непрерывно сгладить поток значений, поэтому я реорганизовал его так:

def exponential_moving_average(period=1000):
    """ Exponential moving average. Smooths the values in v over ther period. Send in values - at first it'll return a simple average, but as soon as it's gahtered 'period' values, it'll start to use the Exponential Moving Averge to smooth the values.
    period: int - how many values to smooth over (default=100). """
    multiplier = 2 / float(1 + period)
    cum_temp = yield None  # We are being primed

    # Start by just returning the simple average until we have enough data.
    for i in xrange(1, period + 1):
        cum_temp += yield cum_temp / float(i)

    # Grab the timple avergae
    ema = cum_temp / period

    # and start calculating the exponentially smoothed average
    while True:
        ema = (((yield ema) - ema) * multiplier) + ema

и я использую это так:

def temp_monitor(pin):
    """ Read from the temperature monitor - and smooth the value out. The sensor is noisy, so we use exponential smoothing. """
    ema = exponential_moving_average()
    next(ema)  # Prime the generator

    while True:
        yield ema.send(val_to_temp(pin.read()))

(где pin.read () создает следующее значение, которое я хотел бы использовать).

1 голос
/ 12 августа 2015

Вот простой пример, который я обработал на основе http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages

Обратите внимание, что в отличие от их электронной таблицы, я не рассчитываю SMA и не жду, чтобы сгенерировать EMA после 10 выборок. Это означает, что мои значения немного отличаются, но если вы наметите это, это будет точно после 10 образцов. В течение первых 10 выборок EMA, который я вычисляю, соответственно сглаживается.

def emaWeight(numSamples):
    return 2 / float(numSamples + 1)

def ema(close, prevEma, numSamples):
    return ((close-prevEma) * emaWeight(numSamples) ) + prevEma

samples = [
22.27, 22.19, 22.08, 22.17, 22.18, 22.13, 22.23, 22.43, 22.24, 22.29,
22.15, 22.39, 22.38, 22.61, 23.36, 24.05, 23.75, 23.83, 23.95, 23.63,
23.82, 23.87, 23.65, 23.19, 23.10, 23.33, 22.68, 23.10, 22.40, 22.17,
]
emaCap = 10
e=samples[0]
for s in range(len(samples)):
    numSamples = emaCap if s > emaCap else s
    e =  ema(samples[s], e, numSamples)
    print e
0 голосов
/ 19 июля 2018

Я использую список и скорость распада в качестве входных данных. Я надеюсь, что эта небольшая функция с двумя строками поможет вам в этом, учитывая, что глубокая рекурсия не стабильна в python.

def expma(aseries, ratio):
    return sum([ratio*aseries[-x-1]*((1-ratio)**x) for x in range(len(aseries))])
...