Python案例如何用Pandas做数据分组滑动

wen python案例 1

Python案例:如何用Pandas做数据分组滑动(实战详解)

目录导读

  1. 为什么需要分组滑动?——业务场景与痛点
  2. Pandas核心函数解析:groupby + rolling的巧妙结合
  3. 实战案例一:股票月度移动平均收益率计算
  4. 实战案例二:电商订单按用户分组后统计7日滑动客单价
  5. 常见错误与性能优化技巧
  6. 问答环节:你关心的分组滑动问题

1. 为什么需要分组滑动?——业务场景与痛点

在日常数据分析中,我们经常需要针对不同类别(如用户、地区、产品),在时间维度上计算滑动统计量(移动平均、累计求和等)。

Python案例如何用Pandas做数据分组滑动

  • 电商场景:计算每个用户最近7天的订单金额平均值,用于识别活跃用户。
  • 金融场景:计算每只股票过去20天的收益率标准差,用于风险控制。
  • 生产场景:按设备ID统计过去24小时的故障次数趋势。

很多新手会用df.groupby().rolling()连续链式调用,但容易踩坑:滑动窗口如果跨组计算会出错,导致数据污染,Pandas其实提供了简洁的解决方案,下面通过两个完整案例带你掌握。


2. Pandas核心函数解析:groupby + rolling的巧妙结合

1 基础语法

df.groupby('分组列')['数值列'].rolling(window=窗口大小).统计方法()

关键点

  • 先按分组列(如user_id)分组。
  • 再对每组内的数值列(如amount)执行滑动计算。
  • 默认滑动窗口会包含当前行在内的前N行(N=window),且滑动是按分组内的排序进行的

2 必须注意的顺序问题

rolling()默认按索引顺序滑动,如果分组内的数据不是按时间排序的,你需要先排序,否则计算结果错乱,正确姿势:

df_sorted = df.sort_values(['分组列', '时间列'])
result = df_sorted.groupby('分组列')['数值列'].rolling(window=5).mean()

3. 实战案例一:股票月度移动平均收益率计算

假设我们有一份A股历史交易数据(daily_stock.csv),包含字段:stock_code, trade_date, close_price,需要计算每只股票过去20个交易日的移动平均收益率

1 数据加载与预处理

import pandas as pd
import numpy as np
# 模拟数据(实际读取CSV)
df = pd.read_csv('daily_stock.csv', parse_dates=['trade_date'])
df = df.sort_values(['stock_code', 'trade_date']).reset_index(drop=True)

2 计算每日收益率

df['daily_return'] = df.groupby('stock_code')['close_price'].pct_change()

3 分组滑动计算20日移动平均收益率

window = 20
df['ma_return_20'] = (df.groupby('stock_code')['daily_return']
                      .rolling(window=window, min_periods=1)
                      .mean()
                      .reset_index(level=0, drop=True))  # 重要:去掉分组level索引

注意.reset_index(level=0, drop=True)是为了把分组产生的多层索引降维,保持与原始DataFrame行对齐。

4 验证结果

# 查看股票"000001.SZ"前30行
print(df[df['stock_code']=='000001.SZ'][['trade_date','close_price','daily_return','ma_return_20']].head(30))

预期输出

  • 前19行ma_return_20可能是NaN(除非min_periods设置小)。
  • 第20行开始表现为过去20天的均值。

4. 实战案例二:电商订单按用户分组后统计7日滑动客单价

有一份电商订单表(orders.csv),字段:user_id, order_date, amount,要求:计算每个用户最近7天(窗口=7,按天为单位)的滑动客单价。

1 难点:如何让窗口按“天”而不是按“行”滑动?

Pandas的rolling()默认是按行数滑动的,如果用户下单天数不连续(比如今天下2单,后天1单),按行数滑动的结果不等于“过去7天”,此时需要重采样设置日期索引+rolling

2 解法:先按日期聚合,再填充缺失值

# 1. 按用户+日期聚合每日总金额和订单数
daily_agg = df.groupby(['user_id', 'order_date'])['amount'].agg(['sum','count']).reset_index()
daily_agg.columns = ['user_id','order_date','daily_amount','order_count']
# 2. 将order_date设为索引,并对每个用户按日期重采样(填充缺失日期)
def resample_user(group):
    group = group.set_index('order_date')
    # 以天为频率重采样,缺失日期填充0
    group = group.resample('D').asfreq().fillna(0)
    return group
resampled = daily_agg.groupby('user_id').apply(resample_user)

3 计算滑动7日客单价

# 在重采样后的基础上,按每个用户滑动计算7天总金额和总订单数
result = (resampled.groupby('user_id')[['daily_amount','order_count']]
          .rolling(window=7, min_periods=1)
          .sum()
          .reset_index(level=0, drop=True))
# 计算客单价(金额/订单数,防止除以0)
result['7day_avg_amt'] = result['daily_amount'] / result['order_count'].replace(0, np.nan)

4 合并回原始数据

# 去掉resample产生的额外日期,只保留原订单日期
final = df.merge(result[['7day_avg_amt']], left_on=['user_id','order_date'], right_index=True, how='left')

5. 常见错误与性能优化技巧

错误1:忘记排序导致滑动混乱

  • 现象:结果表现为数值忽高忽低。
  • 解决:在groupby前必须sort_values(['分组列','时间列'])

错误2:数据量太大时内存溢出

  • 技巧:如果分组数巨大(如100万用户),建议先用apply分批处理,或使用dask进行分布式计算。

错误3:窗口包含未来数据(lookahead bias)

  • 原因rolling()默认包含当前行,如果想用过去N天的均值预测未来,需用shift(1)错位。
  • 修正groupby.rolling(window).mean().shift(1)

性能优化:使用min_periods控制起始数据需求

# 只在前10行有足够数据时才计算,减少NaN
rolling(window=20, min_periods=10)

6. 问答环节:你关心的分组滑动问题

Q1:分组滑动后,结果索引变成多层索引,怎么处理?
A:添加.reset_index(level=0, drop=True),它会丢弃第一层分组索引,只保留数值索引。

Q2:如果有缺失值,rolling会怎么处理?
A:默认如果窗口内有一个NaN,结果就是NaN,可以使用min_periods指定至少需要多少个非NaN值。

Q3:能不能同时滑动多个列?
A:可以,df.groupby('分组')[['col1','col2']].rolling(window).mean()会返回多列结果。

Q4:滑动窗口想按“时间区间”而不是“行数”怎么办?
A:先设置日期索引,然后用rolling(window='7D', on='order_date')(注意需要安装新版pandas并指定on参数)。


通过以上案例,你已经掌握了Pandas分组滑动的核心思路:先排序分组→再滑动计算→最后重置索引对齐,实战中遇到不同业务形态(如时间不连续),可以用重采样+滑动组合解决,赶快在自己的数据集上试试吧!

抱歉,评论功能暂时关闭!