Как Pandas вычисляет экспоненциальные скользящие средние под капотом? - PullRequest
0 голосов
/ 12 октября 2018

Я пытаюсь сравнить производительность панд EMA с производительностью numba .

Как правило, я не пишу функции, если они уже встроены в панд, поскольку панды всегда будут быстрее, чем мои медленные функции Python, написанные вручную;например квантиль , значения сортировки и т. д. Я полагаю, что это потому, что большая часть панд закодирована в C под капотом, а также методы панд .apply() намного быстрее, чем явный pythonдля циклов из-за векторизации (но я открыт для объяснения, если это не так).Но здесь, для вычисления EMA, я обнаружил, что использование numba намного превосходит панд.

Код EMA , который я кодировал, определяется как

S_t = Y_1, t = 1

S_t = альфа * Y_t + (1 - альфа) * ​​S_ {t-1}, t> 1

, где Y_t - значение временного ряда в момент времени t, S_t - значениезначение скользящего среднего в момент времени t, а alpha является параметром сглаживания.

Код выглядит следующим образом

from numba import jit
import pandas as pd
import numpy as np

@jit
def ewm(arr, alpha):
    """
    Calculate the EMA of an array arr
    :param arr: numpy array of floats
    :param alpha: float between 0 and 1
    :return: numpy array of floats
    """
    # initialise ewm_arr
    ewm_arr = np.zeros_like(arr)
    ewm_arr[0] = arr[0]
    for t in range(1,arr.shape[0]):
        ewm_arr[t] = alpha*arr[t] + (1 - alpha)*ewm_arr[t-1]

    return ewm_arr

# initialize array and dataframe randomly
a = np.random.random(10000)
df = pd.DataFrame(a)

%timeit df.ewm(com=0.5, adjust=False).mean()
>>> 1000 loops, best of 3: 1.77 ms per loop

%timeit ewm(a, 0.5)
>>> 10000 loops, best of 3: 34.8 µs per loop

Мы видим, что рука, закодированная функцией ewm, находится вокругВ 50 раз быстрее, чем метод pandas ewm.

Возможно, что numba также превосходит другие методы pandas в зависимости от того, как кодируется их функция.Но здесь меня интересует, как numba превосходит панд в расчете экспоненциальных скользящих средних.Что панды делают (не делают), что делает их медленными - или это просто то, что в этом случае нумба очень быстрая?Как панды вычисляют EMA под капотом?

1 Ответ

0 голосов
/ 12 октября 2018

Но здесь меня интересует, как numba превосходит Pandas в вычислении экспоненциальных скользящих средних.

Ваша версия выглядит быстрее только потому, что вы передаете ей массив NumPy, а неСтруктура данных Pandas:

>>> s = pd.Series(np.random.random(10000))

>>> %timeit ewm(s, alpha=0.5)
82 ms ± 10.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit ewm(s.values, alpha=0.5)
26 µs ± 193 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit s.ewm(alpha=0.5).mean()
852 µs ± 5.44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

В целом, сравнение операций NumPy и Pandas проводится по принципу «яблоки с апельсинами».Последний построен поверх первого и почти всегда будет торговать скоростью для гибкости.(Но, принимая это во внимание, Pandas все еще быстр и стал со временем все больше полагаться на операции Cython.) Я не совсем уверен, что именно с Numba / Jit ведет себя лучше с NumPy.Но если вы сравниваете обе функции с помощью серии Pandas, сама Pandas выходит быстрее.

Как Pandas вычисляет EMA под капотом?

Когда вы звоните df.ewm()(пока не вызывая такие методы, как .mean() или .cov()), промежуточный результат представляет собой истинный класс EWM, который находится в pandas/core/window.py.

>>> ewm = pd.DataFrame().ewm(alpha=0.1)
>>> type(ewm)
<class 'pandas.core.window.EWM'>

Независимо от того, передаете ли вы com,span, halflife или alpha, Панды отобразят это обратно на com и будут использовать его.

Когда вы вызываете сам метод, например ewm.mean() это соответствует ._apply(), который в этом случае служит маршрутизатором для соответствующей функции Cython:

cfunc = getattr(_window, func, None)

В случае .mean(), func - это "ewma"._window - это модуль Cython pandas/libs/window.pyx.

Это подводит вас к сути вещей с помощью функции ewma(), где находится основная массаработы выполняется:

weighted_avg = ((old_wt * weighted_avg) +
                (new_wt * cur)) / (old_wt + new_wt)

Если вы хотите более справедливое сравнение, вызовите эту функцию напрямую со значениями NumPy:

>>> from pandas._libs.window import ewma                                                                                                                 
>>> %timeit ewma(s.values, 0.4, 0, 0, 0)                                                                                                                 
513 µs ± 10.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

(Помните, что требуется толькоcom; для этого вы можете использовать pandas.core.window._get_center_of_mass().

...