Ste*_*ios 8 python nan dataframe pandas pandas-groupby
我有一个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']))
Run Code Online (Sandbox Code Playgroud)
如果我试试这个:
df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: 1)
Run Code Online (Sandbox Code Playgroud)
我在输出中得到了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']))
Run Code Online (Sandbox Code Playgroud)
结果是:
0 NaN
1 NaN
2 2.0
3 NaN
4 2.5
5 NaN
6 3.0
7 2.0
Run Code Online (Sandbox Code Playgroud)
虽然我想要以下内容:
0 NaN
1 NaN
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
Run Code Online (Sandbox Code Playgroud)
和大熊猫一样,坚持使用矢量化方法(即避免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
Run Code Online (Sandbox Code Playgroud)
我们的想法是对窗口中的值求和(使用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
Run Code Online (Sandbox Code Playgroud)
在更大的DataFrame(大约100,000行)上测试它,运行时间不到100毫秒,明显快于我尝试的任何基于应用程序的方法.
可能值得测试实际数据的不同方法,因为时间可能会受到其他因素(如组数)的影响.不过,相当肯定的是,矢量化计算会胜出.
上面显示的方法适用于简单的计算,例如滚动平均值.它将适用于更复杂的计算(例如滚动标准偏差),尽管实施更复杂.
一般的想法是查看在pandas(例如sum)中快速的每个简单例程,然后用标识元素(例如0)填充任何空值.然后,您可以使用groubpy并执行滚动操作(例如.rolling(2).sum()).然后将输出与其他操作的输出组合.
例如,要实现group by 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()
Run Code Online (Sandbox Code Playgroud)
请注意,此函数可能不是数值稳定的(平方可能导致溢出).熊猫在内部使用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
Run Code Online (Sandbox Code Playgroud)
在这种情况下,矢量化速度快100倍.当然,根据您拥有的数据量,您可能希望坚持使用,apply因为它允许您以通用/简洁为代价而牺牲性能.
这个结果符合你的预期吗?我使用 min_periods 参数和 nan 的正确过滤器稍微改变了您的解决方案。
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
Run Code Online (Sandbox Code Playgroud)