【问题标题】:Apply rolling function to groupby over several columns将滚动功能应用于多列的 groupby
【发布时间】:2017-10-29 06:42:23
【问题描述】:

我想将滚动函数应用于按两列分组的数据框,其中包含重复的日期条目。具体来说,“频率”和“窗口”都作为日期时间值,而不仅仅是整数。

原则上,我尝试结合How to apply rolling functions in a group by object in pandaspandas rolling sum of last five minutes的方法。

输入

这是一个数据样本,其中一个 id=33,尽管我们希望有多个 id。

X = [{'date': '2017-02-05', 'id': 33, 'item': 'A', 'points': 20},
 {'date': '2017-02-05', 'id': 33, 'item': 'B', 'points': 10},
 {'date': '2017-02-06', 'id': 33, 'item': 'B', 'points': 10},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-13', 'id': 33, 'item': 'A', 'points': 4}]

# df = pd.DataFrame(X) and reindex df to pd.to_datetime(df['date'])

df

            id item  points
date                       
2017-02-05  33    A      20
2017-02-05  33    B      10
2017-02-06  33    B      10
2017-02-11  33    A       1
2017-02-11  33    A       1
2017-02-11  33    A       1
2017-02-13  33    A       4

目标

每 2 天对每个 'id' 进行采样 (freq='2d') 并返回前三天内每个项目的总分之和 (window='3D'),包括结束日期

期望的输出

            id    A    B
date                       
2017-02-05  33    20   10
2017-02-07  33    20   30    
2017-02-09  33    0    10
2017-02-11  33    3    0
2017-02-13  33    7    0

例如在包含右端的结束日期 2017-02-13 上,我们对 2017-02-11 至 2017-02-13 的 3 天期间进行抽样。在此期间,id=33 的 A 点总和等于 1+1+1+4 = 7

尝试

由于重复日期,尝试使用以下 pd.rolling_sum 的 groupby 无效

df.groupby(['id', 'item'])['points'].apply(pd.rolling_sum, freq='4D', window=3)
ValueError: cannot reindex from a duplicate axis

另请注意,在文档中 http://pandas.pydata.org/pandas-docs/version/0.17.0/generated/pandas.rolling_apply.html'window' 是一个 int 值,表示采样周期的大小,而不是采样的天数。

我们也可以尝试重新采样并使用最后一个,但是似乎没有使用所需的 3 天回溯

df.groupby(['id', 'item'])['points'].resample('2D', label='right', closed='right').\
apply(lambda x: x.last('3D').sum())

id  item  date      
33  A     2017-02-05    20
          2017-02-07     0
          2017-02-09     0
          2017-02-11     3
          2017-02-13     4
    B     2017-02-05    10
          2017-02-07    10

当然,在唯一 id 的 ID 上设置一个循环,选择 df_id = df[df['id']==ID],然后对各个时间段求和确实有效,但计算量很大,并且不能利用 groupby 的优点矢量化。

感谢@jezrael 迄今为止的好建议

备注

熊猫版本 = 0.20.1

我有点困惑为什么关于 rolling() 的文档在这里:https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rolling.html 建议“窗口”参数可以是 int 或偏移量,但在尝试 df.rolling(window='3D',...) 我得到raise ValueError("window must be an integer") 看来上述文档与来自 ./core/window.py 的滚动窗口的最新代码不一致: https://github.com/pandas-dev/pandas/blob/master/pandas/core/window.py

elif not is_integer(self.window):
            raise ValueError("window must be an integer")

【问题讨论】:

    标签: pandas group-by time-series


    【解决方案1】:
    • 当我们有单级日期时间索引时,使用日期频率处理 resamplerolling 是最容易的。
    • 但是,如果不处理重复的A/Bs,我就不能适当地pivot/unstack,所以我groupbysum
    • unstack 一级date 所以我可以fill_value=0。目前,当我unstack 一次超过一个级别时,我不能fill_value=0。我用转置来弥补它T
    • 现在我在索引中有一个级别,我重新索引了索引中从最小值到最大值的日期范围
    • 最后,我使用resample 进行滚动 3 天的总和并每 2 天重新采样一次结果
    • 我用一些重命名索引和一个更多的枢轴来清理它。

    s = df.set_index(['id', 'item'], append=True).points
    s = s.groupby(level=['date', 'id', 'item']).sum()
    
    d = s.unstack('date', fill_value=0).T
    tidx = pd.date_range(d.index.min(), d.index.max())
    d = d.reindex(tidx, fill_value=0)
    
    d1 = d.rolling('3D').sum().resample('2D').first().astype(d.dtypes).stack(0)
    d1 = d1.rename_axis(['date', 'id']).rename_axis(None, 1)
    print(d1)
    
                    A   B
    date       id        
    2017-02-05 33  20  10
    2017-02-07 33  20  20
    2017-02-09 33   0   0
    2017-02-11 33   3   0
    2017-02-13 33   7   0
    

    【讨论】:

    • 感谢您的解释。有关在滚动方法中访问日期时间值的任何提示(例如,给定窗口中最近的非零条目的时间)?目前,似乎只有浮点数 X 的数组可以在 d.rolling('3D').apply(lambda X: func(X)).resample('2D')等调用中访问
    • 这听起来应该是一个单独的问题。提示是在将零掩码为np.nan 后使用pd.Series.first_valid_indexpd.Series.last_valid_index。你也可以在numpy 中做一些其他的技巧。
    【解决方案2】:
    df = pd.DataFrame(X) 
    
    # group sum by day
    df = df.groupby(['date', 'id', 'item'])['points'].sum().reset_index().sort_values(['date', 'id', 'item'])
    
    # convert index to datetime index
    df = df.set_index('date')
    df.index = DatetimeIndex(df.index)
    
    # rolloing sum by 3D
    df['pointsum'] = df.groupby(['id', 'item']).transform(lambda x: x.rolling(window='3D').sum())
    
    # reshape dataframe
    df = df.reset_index().set_index(['date', 'id', 'item'])['pointsum'].unstack().reset_index().set_index('date').fillna(0)
    
    df
    

    【讨论】:

    • 这似乎没有解决“2D”采样频率问题?
    • 可以添加到“x.rolling(window='3D')”吗?样本数据太少,我无法理解整个问题。
    • 查看 piRsquared 的回答 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-11
    • 2013-02-28
    • 1970-01-01
    • 2013-03-08
    • 1970-01-01
    • 2019-12-19
    • 2019-07-22
    相关资源
    最近更新 更多