Pandas 如何在幕后计算指数移动平均线?

How does Pandas compute exponential moving averages under the hood?

我正在尝试比较 pandas EMA performance to numba 性能。

一般来说,如果函数已经内置了 pandas,我不会编写函数,因为 pandas 总是比我手写的慢速 python 函数快;例如 quantile, sort values 等。我相信这是因为 pandas 的大部分内容都是在幕后用 C 编码的,而且 pandas .apply() 方法比显式 python for 循环由于矢量化(但如果这不是真的,我愿意解释)。但是在这里,为了计算 EMA,我发现使用 numba 的效果远远优于 pandas.

我编码的EMA

定义

S_t = Y_1, t = 1

S_t = alpha*Y_t + (1 - alpha)*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

我们看到 hand the hand 编码的 ewm 函数比 pandas ewm 方法快大约 50 倍。

numba 的性能可能也优于其他各种 pandas 方法,具体取决于人们如何对其功能进行编码。但在这里,我感兴趣的是 numba 在计算指数移动平均线方面如何优于 pandas。 pandas 正在做什么(不做什么)让它变慢 - 或者在这种情况下 numba 只是非常快? pandas 如何计算 EMA?

But here I am interested in how numba outperforms Pandas in calculating exponential moving averages.

您的版本似乎更快,仅仅是因为您向它传递的是 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 本身会更快。

How does Pandas compute EMAs under the hood?

当你调用df.ewm()时(还没有调用.mean().cov()等方法),中间结果是真正的classEWMpandas/core/window.py.

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

无论您传递 comspanhalflife 还是 alpha,Pandas 都会 map this back to a com 并使用它。

当您调用方法本身时,例如 ewm.mean(),它映射到 ._apply(), which in this case serves as a router 到适当的 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()