pandas groupby和rolling_apply忽略了NaN

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)

Ale*_*ley 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
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因为它允许您以通用/简洁为代价而牺牲性能.


kna*_*aev 1

这个结果符合你的预期吗?我使用 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)