首页 > 解决方案 > pandas groupby 仅聚合两个连续字段之间共有的行

问题描述

我正在尝试计算每个date字段的总和,但是我只想计算 current 和 next中的 ID 总和date,因此rolling先比较 ID,然后计算groupby总和。目前我必须遍历非常慢的数据帧。

例如我的df:

df = pd.DataFrame({
   'Date': [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4],
   'ID': [ 1, 2, 3, 4 , 2, 3, 4 , 2, 3, 4, 5, 1, 2, 3, 4],
   'Value': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
})

理想情况下,我想对数据框进行分组,Date并且只对两个日期之间的共同点进行求和IDs,例如下面。然而,这是非常缓慢的。

tmpL = df.groupby('Date')['ID'].apply(list)
tmpV = df.groupby('Date')['Value'].sum()
for i in range(1, tmpL.shape[0]):
    res = list(set(tmpL.iloc[i]) - set(tmpL.iloc[i - 1]))
    v = df.loc[ df.ID.isin(res) & (df.Date == tmpL.index[i]), 'Value'].sum()
    tmpV.iloc[i] = tmpV.iloc[i] - v
tmpV

Date
1    10
2    18
3    27
4    42
Name: Value, dtype: int64

有没有办法在pandas不循环数据帧的情况下做到这一点?

标签: pythonpandas

解决方案


使用DataFrame.pivot_tablewith aggregate sum,比较不等于 with DataFrame.diff,最后传递给DataFrame.maskwith sum

df1 = df.pivot_table(index='Date', columns='ID', values='Value', aggfunc='sum')
s = df1.mask(df1.notna().diff().fillna(False)).sum(axis=1)
print (s)
Date
1    10.0
2    18.0
3    27.0
4    42.0
dtype: float64

第一个解决方案,我认为更慢:

您可以通过将 original 转换为 s 来获取所有不匹配的集合set,然后使用Series.diff,并通过、last 聚合和减去Series.explode来获取 original 的所有匹配值:DataFrame.mergesum

tmpL = (df.groupby('Date')['ID'].apply(set)
          .diff()
          .explode()
          .reset_index()
          .merge(df)
          .groupby('Date')['Value']
          .sum())
tmpV = df.groupby('Date')['Value'].sum()

out = tmpV.sub(tmpL, fill_value=0)
print (out)
Date
1    10.0
2    18.0
3    27.0
4    42.0

推荐阅读