在两个日期范围之间更新的累计和

Cumulative sum that updates between two date ranges

我有这样的数据:(假设开始和结束是日期时间)

id start end
1 01-01 01-02
1 01-03 01-05
1 01-04 01-07
1 01-06 NaT
1 01-07 NaT

我想要一个包含所有日期的数据框,它有一个 'cumulative sum' 只计算它们所在的范围。

dates count
01-01 1
01-02 0
01-03 1
01-04 2
01-05 1
01-06 2
01-07 3

我想到的一个想法是简单地在开始日期使用 cumcount,并使用结束日期做 'reverse cumcount' 减少计数,但我无法解决这个问题在 pandas 中,我想知道是否有更优雅的解决方案。

这里有两个选项。首先考虑只有一个 id 的数据,注意你的列开始和结束必须是日期时间。

d = {'id': [1, 1, 1, 1, 1],
     'start': [pd.Timestamp('2021-01-01'), pd.Timestamp('2021-01-03'),
               pd.Timestamp('2021-01-04'), pd.Timestamp('2021-01-06'),
               pd.Timestamp('2021-01-07')],
     'end': [pd.Timestamp('2021-01-02'), pd.Timestamp('2021-01-05'),
             pd.Timestamp('2021-01-07'), pd.NaT, pd.NaT]}
df = pd.DataFrame(d)

因此,要获得结果,您可以在开始和结束的 get_dummies 之间执行 sub。然后 sum 如果多个开始和/或结束日期相同,cumsum 沿日期,reindex 获取最小和最大日期之间的所有可用日期。创建一个函数。

def dates_cc(df_):
    return (
        pd.get_dummies(df_['start'])
          .sub(pd.get_dummies(df_['end'], dtype=int), fill_value=0)
          .sum()
          .cumsum()
          .to_frame(name='count')
          .reindex(pd.date_range(df_['start'].min(), df_['end'].max()), method='ffill')
          .rename_axis('dates')
    )

现在您可以将此函数应用于您的数据框

res = dates_cc(df).reset_index()
print(res)
#        dates  count
# 0 2021-01-01    1.0
# 1 2021-01-02    0.0
# 2 2021-01-03    1.0
# 3 2021-01-04    2.0
# 4 2021-01-05    1.0
# 5 2021-01-06    2.0
# 6 2021-01-07    2.0

现在如果你有多个id,比如

df1 = df.assign(id=[1,1,2,2,2])
print(df1)
#    id      start        end
# 0   1 2021-01-01 2021-01-02
# 1   1 2021-01-03 2021-01-05
# 2   2 2021-01-04 2021-01-07
# 3   2 2021-01-06        NaT
# 4   2 2021-01-07        NaT

那么你就可以使用上面的函数了

res1 = df1.groupby('id').apply(dates_cc).reset_index()
print(res1)
#    id      dates  count
# 0   1 2021-01-01    1.0
# 1   1 2021-01-02    0.0
# 2   1 2021-01-03    1.0
# 3   1 2021-01-04    1.0
# 4   1 2021-01-05    0.0
# 5   2 2021-01-04    1.0
# 6   2 2021-01-05    1.0
# 7   2 2021-01-06    2.0
# 8   2 2021-01-07    2.0

也就是说,更直接的可能性是 crosstab 每个 id 创建一行,其余的操作大致相同。

res2 = (
    pd.crosstab(index=df1['id'], columns=df1['start'])
      .sub(pd.crosstab(index=df1['id'], columns=df1['end']), fill_value=0)
      .reindex(columns=pd.date_range(df1['start'].min(), df1['end'].max()), fill_value=0)
      .rename_axis(columns='dates')
      .cumsum(axis=1)
      .stack()
      .reset_index(name='count')
)
print(res2)
#     id      dates  count
# 0    1 2021-01-01    1.0
# 1    1 2021-01-02    0.0
# 2    1 2021-01-03    1.0
# 3    1 2021-01-04    1.0
# 4    1 2021-01-05    0.0
# 5    1 2021-01-06    0.0
# 6    1 2021-01-07    0.0
# 7    2 2021-01-01    0.0
# 8    2 2021-01-02    0.0
# 9    2 2021-01-03    0.0
# 10   2 2021-01-04    1.0
# 11   2 2021-01-05    1.0
# 12   2 2021-01-06    2.0
# 13   2 2021-01-07    2.0

这两个选项之间的主要区别在于,这个选项为每个 id 创建了额外的日期,因为例如 2021-01-01 在 id=1 而不是 id=2 中,对于这个版本,你得到这个日期同样对于 id=2 而在 groupby 中它不被考虑在内。