9

我有一个熊猫数据框,我想计算一列的滚动平均值(在 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
4

3 回答 3

10

与 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因为它允许您以牺牲性能为代价实现通用性/简洁性。

于 2016-05-23T20:05:43.223 回答
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
于 2016-05-23T15:46:49.410 回答
1

这是一个没有列表理解的替代实现,但它也无法填充输出的第一个条目np.nan

means = df.groupby('var1')['value'].apply(
    lambda gp: gp.rolling(2, min_periods=1).apply(np.nanmean))
于 2016-05-23T16:00:57.863 回答