在两个日期范围之间更新的累计和
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
中它不被考虑在内。
我有这样的数据:(假设开始和结束是日期时间)
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
中它不被考虑在内。