我有一个pandas数据帧,我想计算列的滚动平均值(在groupby子句之后)。但是,我想排除NaN。
例如,如果groupby返回[2,NaN,1],则结果应为1.5,而当前它返回NaN。
我已经尝试了以下但它似乎不起作用:
df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN']))
如果我试试这个:
df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: 1)
我在输出中得到了NaN,所以它必须与pandas在后台运行的方式有关。
有什么想法吗?
编辑: 这是我正在尝试做的代码示例:
import pandas as pd
import numpy as np
df = pd.DataFrame({'var1' : ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], 'value' : [1, 2, 3, np.nan, 2, 3, 4, 1] })
print df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN']))
结果是:
0 NaN
1 NaN
2 2.0
3 NaN
4 2.5
5 NaN
6 3.0
7 2.0
虽然我想要以下内容:
0 NaN
1 NaN
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
答案 0 :(得分:9)
与熊猫一样,坚持使用矢量化方法(即避免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()
)。然后将输出与其他操作的输出组合。
例如,要实现 group by NaN-aware滚动方差(标准偏差是平方根),我们必须找到“平方的平均值减去平均值的平方”。以下是这可能是什么的草图:
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's algorithm来缓解此问题。
无论如何,这个功能虽然使用了几个操作,但仍然非常快。以下是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
,因为它允许您以通用/简洁为代价而牺牲性能。
答案 1 :(得分:1)
这个结果能否符合您的期望? 我用min_periods参数和右边的过滤器稍微改变了你的解决方案。
In [164]: df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if not np.isnan(i)]), min_periods=1)
Out[164]:
0 1.0
1 2.0
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
dtype: float64
答案 2 :(得分:1)
这是一个没有列表理解的替代实现,但它也无法使用np.nan
means = df.groupby('var1')['value'].apply(
lambda gp: gp.rolling(2, min_periods=1).apply(np.nanmean))