这些并不是解决方案,多数是像示例函数这样简单情况的权宜之计。但它证实了 df.rolling.apply
的处理速度远非最优。
出于明显的原因,使用一个小得多的数据集。
import pandas as pd
import numpy as np
df = pd.DataFrame(
np.random.rand(200,100)
)
period = 10
res = [0,0]
使用 pandas
v1.3.5 运行时间
%%timeit -n1 -r1
dd=lambda x: np.nanmax(1.0 - x / np.fmax.accumulate(x))
res[0] = df.rolling(window=period, min_periods=1).apply(dd)
与 numpy
实现相比
from numpy.lib.stride_tricks import sliding_window_view as window
%%timeit
x = window(np.vstack([np.full((period-1,df.shape[1]), np.nan),df.to_numpy()]), period, axis=0)
res[1] = np.nanmax(1.0 - x / np.fmax.accumulate(x, axis=-1), axis=-1)
np.testing.assert_allclose(res[0], res[1])
8.72*1000 / 3.39 = 2572.27
倍加速。
分块处理列
l = []
for arr in np.array_split(df.to_numpy(), 100, 1):
x = window(np.vstack([np.full((period-1,arr.shape[1]), np.nan),arr]), period, axis=0)
l.append(np.nanmax(1.0 - x / np.fmax.accumulate(x, axis=-1), axis=-1))
res[1] = np.hstack(l)
使用 pandas
numba
引擎
我们可以通过 pandas
支持 numba
jitted 函数来获得更快的速度。不幸的是,numba v0.55.1
无法编译 ufunc.accumulate
。我们必须编写自己的实现 np.fmax.accumulate
(我的实现不能保证正确性)。请注意,第一次调用较慢,因为需要编译函数。
def dd_numba(x):
res = np.empty_like(x)
res[0] = x[0]
for i in range(1, len(res)):
if res[i-1] > x[i] or np.isnan(x[i]):
res[i] = res[i-1]
else:
res[i] = x[i]
return np.nanmax(1.0 - x / res)
df.rolling(window=period, min_periods=1).apply(dd_numba, engine='numba', raw=True)
我们可以使用熟悉的pandas接口,它比我分块的numpy方法在df.shape(2000,2000)上快大约1.16倍。