如何按组统计每行日期前n天内的唯一值数量
问题描述
现有如下pandas DataFrame:
| groupId | date | value |
|---|---|---|
| 1 | 2023-01-01 | A |
| 1 | 2023-01-05 | B |
| 1 | 2023-01-17 | C |
| 2 | 2023-01-01 | A |
| 2 | 2023-01-20 | B |
| 3 | 2023-01-01 | A |
| 3 | 2023-01-10 | B |
| 3 | 2023-01-12 | C |
需求:按groupId分组,统计每组中当前行date的最近14天内的唯一value数量,期望结果如下:
| groupId | date | value | newColumn |
|---|---|---|---|
| 1 | 2023-01-01 | A | 1 |
| 1 | 2023-01-05 | B | 2 |
| 1 | 2023-01-17 | C | 2 |
| 2 | 2023-01-01 | A | 1 |
| 2 | 2023-01-20 | B | 1 |
| 3 | 2023-01-01 | A | 1 |
| 3 | 2023-01-10 | B | 2 |
| 3 | 2023-01-12 | C | 3 |
尝试过groupby(...).rolling('14d').nunique(),但该方法对字符串/对象类型的value字段无法正确统计唯一值。
生成测试DataFrame的代码:
import pandas as pd df = pd.DataFrame( { 'groupId': [1, 1, 1, 2, 2, 3, 3, 3], 'date': ['2023-01-01', '2023-01-05', '2023-01-17', '2023-01-01', '2023-01-20', '2023-01-01', '2023-01-10', '2023-01-12'], 'value': ['A', 'B', 'C', 'A', 'B', 'A', 'B', 'C'], } )
解决方案
方法1:分组后用apply遍历行筛选日期范围
先将date列转为datetime类型,按groupId分组后,对每组内的每行筛选出当前日期前14天内的记录,统计唯一value数量:
# 转换日期类型 df['date'] = pd.to_datetime(df['date']) def count_unique_last14d(group): group['newColumn'] = group.apply( lambda row: group.loc[ (group['date'] >= row['date'] - pd.Timedelta(days=14)) & (group['date'] <= row['date']), 'value' ].nunique(), axis=1 ) return group df_result = df.groupby('groupId').apply(count_unique_last14d).reset_index(drop=True) print(df_result)
逻辑直观,适合小数据量场景,大数据量下效率较低。
方法2:自连接+日期过滤+分组统计
通过自连接匹配同组所有行,过滤出日期差在14天内的记录,再按原始行索引统计唯一值:
df['date'] = pd.to_datetime(df['date']) # 添加索引列用于后续匹配 df['idx'] = df.index # 同groupId的行两两配对 merged = df.merge(df, on='groupId', suffixes=('', '_other')) # 保留当前日期前14天内的记录 filtered = merged[merged['date'] >= merged['date_other'] - pd.Timedelta(days=14)] # 按原始行索引分组统计唯一value数 unique_counts = filtered.groupby('idx')['value_other'].nunique().reset_index(name='newColumn') # 合并回原DataFrame df_result = df.merge(unique_counts, on='idx').drop('idx', axis=1) print(df_result)
利用矢量化操作,比apply效率更高,适合大数据量场景。
方法3:rolling结合自定义聚合函数
将字符串类型的value转为编码,再用自定义函数实现rolling窗口内的唯一值统计:
df['date'] = pd.to_datetime(df['date']) # 将value转为类别编码 df['value_cat'] = df['value'].astype('category').cat.codes def nunique_rolling(arr): return len(pd.unique(arr)) # 分组后应用rolling窗口+自定义聚合函数 df['newColumn'] = df.groupby('groupId').rolling('14d', on='date')['value_cat'].apply(nunique_rolling).reset_index(0, drop=True) # 删除临时编码列 df = df.drop('value_cat', axis=1) print(df)
保留rolling的使用逻辑,绕开原方法对字符串类型的限制。
内容的提问来源于stack exchange,提问作者confused_pandas
相关产品推荐
相关产品推荐

