Pandas基于时间列对多列进行滚动求和分组

6

我试图按组获取多个列的滚动总和,在日期时间列上进行滚动(即在指定的时间间隔内)。对单个列进行滚动似乎运行良好,但是当我通过向量化滚动多个列时,我得到了意外的结果。

我的第一次尝试:

df = pd.DataFrame({"column1": range(6), 
                   "column2": range(6), 
                   'group': 3*['A','B'], 
                   'date':pd.date_range("20190101", periods=6)})

(df.groupby('group').rolling("1d", on='date')['column1'].sum()).groupby('group').shift(fill_value=0)

# output:
group  date      
A      2019-01-01    0.0
       2019-01-03    0.0
       2019-01-05    2.0
B      2019-01-02    0.0
       2019-01-04    1.0
       2019-01-06    3.0
Name: column1, dtype: float64

上述操作产生了期望的结果,但我在过程中丢失了原始索引。由于我的数据中有一些日期是相同的,所以我需要在组合+日期上重新加入到原始数据帧上,但这很低效。因此,为避免这种情况并保留原始索引,我采用了以下方法:
df.groupby('group').apply(lambda x: x.rolling("1d", on='date')['column1'].sum().shift(fill_value=0))

# output:
group   
A      0    0.0
       2    0.0
       4    2.0
B      1    0.0
       3    1.0
       5    3.0
Name: column1, dtype: float64

使用这种方法,我可以轻松将其分配给原始数据框的新列,方法是按索引排序。现在,我想对'column2'重复相同的步骤,并通过矢量化完成。然而,我得到的结果出乎意料:

df.groupby('group').apply(lambda x: x.rolling("1d", on='date')[['column1','column2']].sum().shift(fill_value=0))

# output:

   column1  column2       date
0      0.0      0.0 1970-01-01
1      0.0      0.0 1970-01-01
2      0.0      0.0 2019-01-01
3      1.0      1.0 2019-01-02
4      2.0      2.0 2019-01-03
5      3.0      3.0 2019-01-04

结果是正确的,但出乎意料,原因如下:(1)在groupby中忽略了group_keys;(2)它自动排序结果并像“transform”方法一样重置了索引。我想理解为什么会发生这种情况,还想知道是否有其他方法可以实现上述结果。
3个回答

0

如果我理解正确,您想要具有移动日期的滚动总和,或者换句话说,所有列都移动但不包括日期。

您代码中的问题解释

df.groupby('group').rolling("1d", on='date')['column1'].sum() 返回一个带有 groupdate 的 MultiIndex。如果没有 ['column1'],它将返回一个 DataFrame,但是 date 仍然是一列。 .groupby('group').shift(fill_value=0) 移动了所有列,但保留了(多)索引,并使用 0 填充缺失值,即日期为 1970-01-01。

解决方案

分开处理。进行滚动操作,并将所需列应用于移位。

# define the columns which should be shifted
columns = df.columns.difference(['date', 'group'])

# get the rolling for all columnns
df_i = df.groupby('group').rolling("1d", on='date').sum()

# do the shift only for specific columns - but not others, here the 'date' column
df_i[columns] = df_i[columns].groupby('group').shift(fill_value=0)

         column1  column2       date
group                               
A     0      0.0      0.0 2019-01-01
      2      0.0      0.0 2019-01-03
      4      2.0      2.0 2019-01-05
B     1      0.0      0.0 2019-01-02
      3      1.0      1.0 2019-01-04
      5      3.0      3.0 2019-01-06

替代方案:您也可以将date添加到多索引中并进行移位(尽管可能会更慢)

df_i = df.groupby('group').rolling("1d", on='date').sum()
df_i.set_index([df_i.index, 'date']).groupby('group').shift(fill_value=0)

速度测试

看来性能很重要,这里提供一个比较 - 其他解决方案也没有提供结果。将DataFrame长度从6增加到1e4,并使用davidbilla的答案进行测试以测量性能:

%%timeit -n 3 -r 3
df_i = df.reset_index().rename(columns={df.index.name: 'index'})
(df_i.groupby(['group', 'index']).rolling("1d", on='date')[['column1', 'column2']].sum()).groupby('group').shift(fill_value=0)

以及楼主的:

%timeit -n 3 -r 3 df.groupby('group').apply(lambda x: x.rolling("1d", on='date')[['column1','column2']].sum().shift(fill_value=0))
解决方案 时间(平均值±3次运行的标准差,每次循环3次)
davidbilla 每次循环522毫秒±15.3毫秒
nrcjea001 (OP) 每次循环255毫秒±6.25毫秒
此解决方案 每次循环22.3毫秒±2.03毫秒

0
它运行得很好。 试试这个:
df.groupby('group').apply(lambda x: x.set_index('date')[columns_to_roll].rolling("1d").sum().shift(fill_value=0))

0

我采用了你最初的方法并进行了一些更改。你能检查一下这是否是你想要的吗?

重置原始数据框的索引并为原始索引分配一个列名。

df = df.reset_index().rename(columns={df.index.name: 'index'})

现在,您有相同的原始数据框,但它具有名为index的附加列,该列是原始索引。

在按groupindex列分组的groupby数据框上应用rolling到2个列column1column2

(df.groupby(['group', 'index']).rolling("1d", on='date')[['column1', 'column2']].sum()).groupby('group').shift(fill_value=0)

结果:

                        column1  column2
group index date                        
A     0     2019-01-01      0.0      0.0
      2     2019-01-03      0.0      0.0
      4     2019-01-05      2.0      2.0
B     1     2019-01-02      0.0      0.0
      3     2019-01-04      1.0      1.0
      5     2019-01-06      3.0      3.0

如果您想要恢复原始索引,请重置多重索引并将“index”设置为索引。
(df.groupby(['group', 'index']).rolling("1d", on='date')[['column1', 'column2']].sum()).groupby('group').shift(fill_value=0).reset_index().set_index('index')

结果:

      group       date  column1  column2
index                                   
0         A 2019-01-01      0.0      0.0
2         A 2019-01-03      0.0      0.0
4         A 2019-01-05      2.0      2.0
1         B 2019-01-02      0.0      0.0
3         B 2019-01-04      1.0      1.0
5         B 2019-01-06      3.0      3.0

如果你想要排序,可以添加 .sort_index()
      group       date  column1  column2
index                                   
0         A 2019-01-01      0.0      0.0
1         B 2019-01-02      0.0      0.0
2         A 2019-01-03      0.0      0.0
3         B 2019-01-04      1.0      1.0
4         A 2019-01-05      2.0      2.0
5         B 2019-01-06      3.0      3.0

希望这可以帮到你!如果我漏掉了什么,请告诉我。

感谢您抽出时间来处理这个问题。但是,只有在滚动窗口为“1d”时才有效。一旦您使用“3d”或更长的时间间隔,结果就会不正确。这是因为您按索引进行分组,这意味着每行都被视为单个组。 - nrcjea001
@nrcjea001 当我使用“1d”、“2d”、“3d”或“4d”对这个样本数据集进行计算时,我得到了相同的结果。你有更好的例子可以尝试一下三天或更长时间滚动窗口吗? - davidbilla
尝试使用我的方法 (df.groupby('group').rolling("3d", on='date')['column1'].sum()).groupby('group').shift(fill_value=0) 并将其与您的相同数据示例进行比较。 - nrcjea001

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接