Быстрый расчет EMA для большого набора данных с нерегулярными временными интервалами - PullRequest
2 голосов
/ 09 июля 2019

У меня есть данные, которые имеют 800 000+ строк. Я хочу взять экспоненциальную скользящую среднюю (EMA) одного из столбцов. Время дискретизировано неравномерно, и я хочу уменьшить EMA при каждом обновлении (строка). У меня есть код:

window = 5            
for i in range(1, len(series)):
    dt = series['datetime'][i] - series['datetime'][i - 1]
    decay = 1 - numpy.exp(-dt / window)
    result[i] = (1 - decay) * result[i - 1] + decay * series['midpoint'].iloc[i]
return pandas.Series(result, index=series.index)

Проблема в том, что для 800 000 строк это очень медленно. Есть ли способ оптимизировать это, используя некоторые другие функции NumPy? Я не могу векторизовать его, потому что results[i] зависит от results[i-1].

образец данных здесь:

Timestamp             Midpoint
1559655000001096130    2769.125
1559655000001162260    2769.127
1559655000001171688    2769.154
1559655000001408734    2769.138
1559655000001424200    2769.123
1559655000001433128    2769.110
1559655000001541560    2769.125
1559655000001640406    2769.125
1559655000001658436    2769.127
1559655000001755924    2769.129
1559655000001793266    2769.125
1559655000001878688    2769.143
1559655000002061024    2769.125

Ответы [ 2 ]

2 голосов
/ 10 июля 2019

Как насчет чего-то подобного следующему, которое занимает у меня 0,34 секунды для запуска серии данных с нерегулярно разнесенными данными и 900k строк?Я предполагаю, что окно 5 подразумевает 5-дневный промежуток.

Во-первых, давайте создадим несколько примеров данных.

# Create sample data for a price stream of 2.6m price observations sampled 1 second apart.
seconds_per_day = 60 * 60 * 24  # 60 seconds / minute * 60 minutes / hour * 24 hours / day
starting_value = 100
annualized_vol = .3
sampling_percentage = .35  # 35%
start_date = '2018-12-01'
end_date = '2018-12-31'

np.random.seed(0)
idx = pd.date_range(start=start_date, end=end_date, freq='s')  # One second intervals.
periodic_vol = annualized_vol * (1/ 252 / seconds_per_day) ** 0.5
daily_returns = np.random.randn(len(idx)) * periodic_vol
cumulative_indexed_return = (1 + daily_returns).cumprod() * starting_value
index_level = pd.Series(cumulative_indexed_return, index=idx)

# Sample 35% of the simulated prices to create a time series of 907k rows with irregular time intervals.
s = index_level.sample(frac=sampling_percentage).sort_index()

Теперь давайте создадим функцию генератора для хранения последнего значения экспоненциальновзвешенный временной ряд.Это может запустить с.В 4 раза быстрее, установив numba, импортировав его, а затем добавив одну строку декоратора над определением функции @jit(nopython=True).

from numba import jit  # Optional, see below.

@jit(nopython=True)  # Optional, see below.
def ewma(vals, decay_vals):
    result = vals[0]
    yield result
    for val, decay in zip(vals[1:], decay_vals[1:]):
        result = result * (1 - decay) + val * decay
        yield result

Теперь давайте запустим этот генератор на нерегулярно расположенных рядах s.Для этого примера с 900k строк у меня уходит 1,2 секунды на запуск следующего кода.Я могу дополнительно сократить время выполнения до 0,34 секунды, опционально используя компилятор Just in Time с numba .Сначала вам нужно установить этот пакет, например conda install numba.Обратите внимание, что я использовал список со списком для заполнения значений ewma из генератора, а затем присваиваю эти значения исходному ряду после первого преобразования его в массив данных.

# Assumes time series data is now named `s`.
window = 5  # Span of 5 days?
dt = pd.Series(s.index).diff().dt.total_seconds().div(seconds_per_day)  # Measured in days.
decay = (1 - (dt / -window).apply(np.exp))
g = ewma_generator(s.values, decay.values)
result = s.to_frame('midpoint').assign(
    ewma=pd.Series([next(g) for _ in range(len(s))], index=s.index))

>>> result.tail()
                       midpoint        ewma
2018-12-30 23:59:45  103.894471  105.546004
2018-12-30 23:59:49  103.914077  105.545929
2018-12-30 23:59:50  103.901910  105.545910
2018-12-30 23:59:53  103.913476  105.545853
2018-12-31 00:00:00  103.910422  105.545720

>>> result.shape
(907200, 2)

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

obs_per_day = 24  # 24 hourly observations per day.
step = int(seconds_per_day / obs_per_day)
>>> result.iloc[::step, :].plot()

enter image description here

0 голосов
/ 09 июля 2019

Небольшое улучшение может быть достигнуто путем итерации по базовым массивам numpy вместо pandas DataFrames и Series:

result = np.ndarray(len(series))
window = 5
serdt = series['datetime'].values
sermp = series['midpoint'].values
for i in range(1, len(series)):
    dt = serdt[i] - serdt[i - 1]
    decay = 1 - numpy.exp(-dt / window)
    result[i] = (1 - decay) * result[i - 1] + decay * sermp[i]
return pandas.Series(result, index=series.index)

С вашими образцами данных это примерно в 6 раз быстрее, чем оригинальный метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...