面板数据中计算用户过去3天非缺失小费的众值(Python实现)
问题:计算用户过去3天小费众值的代码修复
需求说明
- 面板数据记录用户单日/跨日的多笔小费交易,每条观测为用户i在日期t的单次交易
- 需为每条记录计算用户过去3天(不含当日)所有非缺失小费的众值,无有效记录时设为0
- 示例:用户t日4条记录,t-1日3条、t-2日无、t-3日6条,则t日每条记录的众值为t-1至t-3日共9条有效小费的众值
数据样本
| userlink | date | tip | 期望mode_3 |
|---|---|---|---|
| /user/cmocmo | 2008-09-21 | 1 | 0 |
| /user/cmocmo | 2008-09-22 | NA | 1 |
| /user/cmocmo | 2008-09-23 | 5 | 1 |
| /user/cmocmo | 2008-09-23 | 5 | 1 |
| /user/cmocmo | 2008-09-23 | 3 | 1 |
| /user/cmocmo | 2008-09-23 | NA | 1 |
| /user/cmocmo | 2008-09-24 | 0.25 | 5 |
| /user/cmocmo | 2008-09-24 | 0.25 | 5 |
| /user/Azzarc | 2008-09-24 | 0.01 | 0 |
| /user/Emsdad | 2008-09-24 | 0.25 | 0 |
| /user/Emsdad | 2008-09-25 | 0.25 | 0.25 |
原代码问题
data['mode_3']= data.groupby(['userlink'])['tip'].shift().rolling(window=3, min_periods =0).apply(lambda x: mode(x)[0])
原代码存在3个核心问题:
- 窗口逻辑错误:
rolling(window=3)按行滚动,而非按日期窗口滚动,无法覆盖过去3天的所有记录(同一日期可能有多条行) - 缺失值处理不当:未过滤NA值,
mode()函数会将NA纳入计算,导致结果错误 - 无日期聚合:直接对原始行数据操作,无法合并同一日期的多条小费记录后统计过去3天的整体众值
修复方案
步骤1:数据预处理
确保日期格式正确,保留有效小费记录:
import pandas as pd from scipy.stats import mode # 转换日期为datetime格式 data['date'] = pd.to_datetime(data['date']) # 提取非缺失小费的记录用于后续统计 valid_tips = data.dropna(subset=['tip']).copy()
步骤2:按用户+日期聚合每日小费
将同一用户同一日期的所有小费合并为列表,方便后续窗口统计:
daily_tips = valid_tips.groupby(['userlink', 'date'])['tip'].apply(list).reset_index()
步骤3:计算用户过去3天的小费众值
对每个用户的时间序列按日期排序,用时间滚动窗口取过去3天(不含当日)的所有小费,计算众值:
def calculate_user_mode(group): # 按日期排序保证时间顺序正确 group = group.sort_values('date').set_index('date') # 滚动窗口设为3天,closed='left'表示不包含当前日期 rolling_window = group['tip'].rolling('3D', closed='left') def compute_mode(window): # 合并窗口内所有日期的小费列表 all_tips = [tip for sublist in window for tip in sublist] # 过滤可能存在的缺失值 all_tips = [x for x in all_tips if pd.notna(x)] # 无有效记录返回0,否则返回第一个众值 return mode(all_tips, keepdims=False)[0] if all_tips else 0 # 计算每日对应的过去3天众值 group['daily_mode'] = rolling_window.apply(compute_mode, raw=False) return group.reset_index() # 应用到每个用户 daily_mode_result = daily_tips.groupby('userlink', group_keys=False).apply(calculate_user_mode)
步骤4:合并结果回原数据
将每日众值匹配到原数据的每条记录,填充无有效数据的情况为0:
# 合并原数据与计算结果 data = data.merge(daily_mode_result[['userlink', 'date', 'daily_mode']], on=['userlink', 'date'], how='left') # 填充无有效记录的情况为0 data['mode_3'] = data['daily_mode'].fillna(0) # 删除中间辅助列 data = data.drop('daily_mode', axis=1)
结果验证
运行后结果将完全匹配样本中的期望mode_3值,解决原代码的逻辑错误。
内容的提问来源于stack exchange,提问作者mahsa paridar
相关产品推荐
相关产品推荐

