如何用Pandas优化分行为的连续两日唯一用户统计方案
问题描述
数据集说明
我有一个包含以下字段的数据集:
- day:用户执行行为的整数日期(示例:
day = 1) - user_id:唯一用户标识(示例:
user_id = 'a') - actions:行为类型(示例:
action = 1)
需求目标
针对每个日期n,按行为类型分别统计**该日期n及n-1日(当日和前日)**内执行对应行为的唯一用户数。比如针对日期2、行为1,统计日期1和2内的唯一用户数为3(用户a、b、c)。
当前实现与优化需求
我已经通过双重循环实现了需求,但希望找到更简洁的基于groupby/apply/rolling的优化方案,且方案需要支持day列存在缺失日期的场景。
现有代码与输出
import pandas as pd df = pd.DataFrame( { "day" : [ 0, 1, 1, 2, 2, 3 , 1, 2, 4, 4, 5], "user_id": ['a','a','b','b','c', 'c', 'a', 'b', 'a', 'b', 'c'], "actions" : [ 1, 1 , 1, 1, 1, 1, 2, 2, 2, 2, 2] } ) # 当前双重循环实现 unique_dictionary = {'action': [], 'day': [], 'unique_users_last_n_days': []} n_days = 1 # 回溯天数 for action in df.actions.unique(): for day in sorted(df.day.unique()): mask_last_n_days = (day - df["day"] >=0) & (day - df["day"] <= n_days) mask_action = df['actions'] == action unique_users = df[(mask_action) & (mask_last_n_days)]["user_id"].nunique() unique_dictionary['action'].append(action) unique_dictionary['day'].append(day) unique_dictionary['unique_users_last_n_days'].append(unique_users) df_result = pd.DataFrame(unique_dictionary) print(df_result)
输出结果:
action day unique_users_last_n_days 0 1 0 1 1 1 1 2 2 1 2 3 3 1 3 2 4 1 4 1 5 1 5 0 6 2 0 0 7 2 1 1 8 2 2 2 9 2 3 1 10 2 4 2 11 2 5 3
优化方案
这里提供一个基于groupby和rolling的简洁实现,完全满足需求,还能自然支持缺失日期的场景:
代码实现
import pandas as pd df = pd.DataFrame( { "day" : [ 0, 1, 1, 2, 2, 3 , 1, 2, 4, 4, 5], "user_id": ['a','a','b','b','c', 'c', 'a', 'b', 'a', 'b', 'c'], "actions" : [ 1, 1 , 1, 1, 1, 1, 2, 2, 2, 2, 2] } ) n_days = 1 # 回溯天数 # 1. 去重:同一用户同一天同一行为只保留一条记录,避免重复计数 df_unique = df.drop_duplicates(subset=['actions', 'day', 'user_id']) # 2. 生成完整日期序列,覆盖所有出现过的日期,自动处理缺失日期 all_days = pd.RangeIndex(start=df['day'].min(), end=df['day'].max()+1) # 3. 按行为分组,处理每个行为的滚动统计 def calc_rolling_unique(group): # 透视成【日期-用户】矩阵,用0/1标记用户当日是否有该行为 user_pivot = group.pivot(index='day', columns='user_id', values='user_id') \ .reindex(all_days) \ .notna().astype(int) # 滚动窗口(窗口大小=回溯天数+1,包含当日),统计窗口内的唯一用户数 rolling_count = user_pivot.rolling(window=n_days+1, min_periods=1).sum() \ .astype(bool).sum(axis=1) # 整理结果格式 return rolling_count.reset_index(name='unique_users_last_n_days') \ .assign(action=group.name) # 4. 执行分组计算并合并结果 df_optimized = df_unique.groupby('actions').apply(calc_rolling_unique) \ .reset_index(drop=True) \ .sort_values(['actions', 'day']) \ [['action', 'day', 'unique_users_last_n_days']] print(df_optimized)
关键步骤解释
- 去重处理:
drop_duplicates确保同一用户在同一天同一行为只被统计一次,避免重复计算。 - 补全日期:用
pd.RangeIndex生成完整的日期范围,哪怕原数据有缺失日期(比如原数据没有day=3的行为2记录),也能自动补全并统计该日期的结果。 - 滚动统计:先把用户行为转成矩阵,再用
rolling窗口覆盖当日和前日,通过sum().astype(bool)确保每个用户在窗口内无论出现多少次都只算1次,最后求和得到唯一用户数。
结果验证
运行上述代码后,输出结果和原双重循环的输出完全一致,同时完美支持缺失日期场景。
内容的提问来源于stack exchange,提问作者Leo
相关产品推荐
相关产品推荐

