我的方法是调用辅助函数df.groupby('id').transform
。我觉得这比它可能的更复杂、更慢,但它似乎有效。
# test data
date id cum_count_desired
2017-03-01 A 1
2017-03-01 B 1
2017-03-01 C 1
2017-05-01 B 2
2017-05-01 D 1
2017-07-01 A 2
2017-07-01 D 2
2017-08-01 C 1
2017-09-01 B 2
2017-09-01 B 3
# preprocessing
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
# Encode the ID strings to numbers to have a column
# to work with after grouping by ID
df['id_code'] = pd.factorize(df['id'])[0]
# solution
def cumcounter(x):
y = [x.loc[d - pd.DateOffset(months=4):d].count() for d in x.index]
gr = x.groupby('date')
adjust = gr.rank(method='first') - gr.size()
y += adjust
return y
df['cum_count'] = df.groupby('id')['id_code'].transform(cumcounter)
# output
df[['id', 'id_num', 'cum_count_desired', 'cum_count']]
id id_num cum_count_desired cum_count
date
2017-03-01 A 0 1 1
2017-03-01 B 1 1 1
2017-03-01 C 2 1 1
2017-05-01 B 1 2 2
2017-05-01 D 3 1 1
2017-07-01 A 0 2 2
2017-07-01 D 3 2 2
2017-08-01 C 2 1 1
2017-09-01 B 1 2 2
2017-09-01 B 1 3 3
需要adjust
如果同一 ID 在同一天多次出现,我使用的切片方法将会对每个同一天的 ID 进行计数,因为当列表理解遇到日期时,基于日期的切片会立即获取所有同一天的值其上显示多个 ID。使固定:
- 按日期对当前 DataFrame 进行分组。
- 对每个日期组中的每一行进行排名。
- 从这些排名中减去每个日期组中的总行数。这会生成一个按日期索引的升序负整数系列,以 0 结尾。
- 将这些非正整数调整添加到
y
.
这仅影响给定测试数据中的一行——倒数第二行,因为B
在同一天出现两次。
包括或排除时间间隔的左端点
计算行数旧于或更新于4 个日历月前,即include4 个月时间间隔的左端点,保留此行不变:
y = [x.loc[d - pd.DateOffset(months=4):d].count() for d in x.index]
计算行数严格比新4 个日历月前,即exclude4 个月时间间隔的左端点,请改用:
y = [d.loc[d - pd.DateOffset(months=4, days=-1):d].count() for d in x.index]