与 pandas 一样,坚持使用矢量化方法(即避免apply
)对于性能和可扩展性至关重要。
您想要执行的操作有点繁琐,因为 groupby 对象的滚动操作目前不支持 NaN(版本 0.18.1)。因此,我们需要几行简短的代码:
g1 = df.groupby(['var1'])['value'] # group values
g2 = df.fillna(0).groupby(['var1'])['value'] # fillna, then group values
s = g2.rolling(2).sum() / g1.rolling(2).count() # the actual computation
s.reset_index(level=0, drop=True).sort_index() # drop/sort index
想法是对窗口中的值求和(使用sum
),计算 NaN 值(使用count
),然后除以求均值。此代码提供与您所需的输出相匹配的以下输出:
0 NaN
1 NaN
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
Name: value, dtype: float64
在更大的 DataFrame(大约 100,000 行)上进行测试,运行时间不到 100 毫秒,比我尝试过的任何基于应用的方法都要快得多。
可能值得在您的实际数据上测试不同的方法,因为时间可能会受到其他因素(例如组数)的影响。不过,可以肯定的是,矢量化计算会胜出。
上面显示的方法适用于简单的计算,例如滚动平均值。它适用于更复杂的计算(例如滚动标准偏差),尽管实现更复杂。
一般的想法是查看每个在pandas 中快速的简单例程(例如sum
),然后用标识元素(例如0
)填充任何空值。然后,您可以使用 groubpy 并执行滚动操作(例如.rolling(2).sum()
)。然后将输出与其他操作的输出组合。
例如,要实现groupby NaN 感知滚动方差(其中标准差是平方根),我们必须找到“平方的均值减去均值的平方”。这是这可能的样子的草图:
def rolling_nanvar(df, window):
"""
Group df by 'var1' values and then calculate rolling variance,
adjusting for the number of NaN values in the window.
Note: user may wish to edit this function to control degrees of
freedom (n), depending on their overall aim.
"""
g1 = df.groupby(['var1'])['value']
g2 = df.fillna(0).groupby(['var1'])['value']
# fill missing values with 0, square values and groupby
g3 = df['value'].fillna(0).pow(2).groupby(df['var1'])
n = g1.rolling(window).count()
mean_of_squares = g3.rolling(window).sum() / n
square_of_mean = (g2.rolling(window).sum() / n)**2
variance = mean_of_squares - square_of_mean
return variance.reset_index(level=0, drop=True).sort_index()
请注意,此函数可能在数值上不稳定(平方可能导致溢出)。pandas 在内部使用Welford 算法来缓解这个问题。
不管怎样,这个功能虽然使用了几个操作,但还是非常快的。这是与Yakym Pirozhenko建议的更简洁的基于应用的方法的比较:
>>> df2 = pd.concat([df]*10000, ignore_index=True) # 80000 rows
>>> %timeit df2.groupby('var1')['value'].apply(\
lambda gp: gp.rolling(7, min_periods=1).apply(np.nanvar))
1 loops, best of 3: 11 s per loop
>>> %timeit rolling_nanvar(df2, 7)
10 loops, best of 3: 110 ms per loop
在这种情况下,矢量化速度提高了 100 倍。当然,根据您拥有的数据量,您可能希望坚持使用,apply
因为它允许您以牺牲性能为代价实现通用性/简洁性。