Pandas双DataFrame按注册日期范围统计用户消费额的实现方案
代码问题根因
你原来的实现存在三个核心逻辑错误,直接导致结果数值错位:
- 按
user分组遍历df1子表时,内部的apply循环没有匹配用户维度,直接在分组里循环全量df2的所有行,完全丢失了用户匹配关系 - apply返回的结果索引和df2的行索引没有做对齐,赋值时pandas会按索引自动匹配,最终新列的数值根本不是对应用户的计算结果
- 用固定30天周期计算“注册首月”不符合大多数业务的自然月统计逻辑,遇到大小月、闰月会出现统计偏差
高复用性实现方案
核心思路是先把注册日期关联到每一笔消费记录上,用向量化运算替代逐行循环的apply逻辑,数据量越大效率优势越明显,同时可以灵活扩展不同注册周期的统计需求。
基础预处理
首先要统一日期格式,避免日期运算报错,再把注册日期匹配到每笔交易上:
import pandas as pd # 统一转换日期列格式 df1['date_1'] = pd.to_datetime(df1['date_1']) df2['date_2'] = pd.to_datetime(df2['date_2']) # 左关联匹配每个用户的注册日期到对应交易记录 merged_trade = df1.merge(df2[['user', 'date_2']], on='user', how='left') # 剔除消费日期早于注册日期的异常数据 merged_trade = merged_trade[merged_trade['date_1'] >= merged_trade['date_2']]
计算消费对应的注册周期
根据业务需要选择两种计算口径,后续扩展统计维度完全不用改核心逻辑:
- 自然月口径:比如注册日为2021-02-05,注册首月为2021-02-05到2021-03-04,次月为2021-03-05到2021-04-04
merged_trade['months_since_register'] = ( (merged_trade['date_1'].dt.year - merged_trade['date_2'].dt.year) * 12 + (merged_trade['date_1'].dt.month - merged_trade['date_2'].dt.month) ) - 固定天数口径:比如注册首月为注册后0-30天,次月为31-60天,适合严格按日数算周期的场景
merged_trade['days_since_register'] = (merged_trade['date_1'] - merged_trade['date_2']).dt.days
汇总周期消费并匹配回df2
按用户和周期维度分组求和,就可以得到每个用户不同周期的总消费:
# 以自然月口径为例,按用户、注册后第n月分组汇总 user_period_sum = merged_trade.groupby( ['user', 'months_since_register'], as_index=False )['value'].sum().rename(columns={'value': 'total_amount'}) # 提取注册首月(偏移量为0)的统计结果,匹配回df2 first_month_data = user_period_sum[ user_period_sum['months_since_register'] == 0 ][['user', 'total_amount']] df2 = df2.merge(first_month_data, on='user', how='left') # 注册后首月没有消费的用户填充为0 df2['total_amount'] = df2['total_amount'].fillna(0) df2 = df2.rename(columns={'total_amount': 'first_month_purchase'})
扩展使用说明
后续要统计注册后第二个月、第三个月的消费,只需要调整过滤维度的筛选值即可,不需要重写计算逻辑:
- 统计注册后第二个月消费:过滤条件换成
months_since_register == 1,merge回df2作为新列second_month_purchase - 统计固定天数周期的消费:把分组维度换成之前计算的
days_since_register,先通过pd.cut按区间分桶打标签,再分组求和即可,逻辑完全通用
这种向量化的实现方式,在数据量达到十万、百万级时,运行速度比逐行apply的方案快几十到上百倍,不会随着数据增长出现性能瓶颈。
内容的提问来源于stack exchange,提问作者Colin
相关产品推荐
相关产品推荐

