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

wen python案例 1

本文目录导读:

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

  1. 目录导读
  2. 什么是分组滚动(Groupby Rolling)?
  3. 为什么需要分组滚动?
  4. 案例准备:数据集与安装环境
  5. 核心实现:Pandas分组滚动操作全解
  6. 高级技巧:自定义窗口与边界处理
  7. 实战案例:销售数据的月度滚动统计
  8. 常见问题与避坑指南
  9. 问答环节

Python实战案例:如何用Pandas做数据分组滚动(Groupby Rolling)详细指南

目录导读


什么是分组滚动(Groupby Rolling)?

在许多数据分析场景中,我们需要对数据按某个类别进行分组,然后在每个分组内对时间序列或顺序数据进行滚动窗口计算(例如移动平均、滚动总和、滚动标准差等),Pandas 提供了 groupby().rolling() 组合方法,能够高效完成这一需求。

分组滚动 = 先分组,再在每个组内做滚动计算,对每个销售员,计算最近3天的平均销售额;或对每只股票,计算5日移动平均线。

注意:分组滚动要求数据已经按时间或顺序排好序,否则计算结果会出现逻辑错误。


为什么需要分组滚动?

场景 说明 实例
金融分析 每只股票按日期计算5日均线 股票代码分组 + 日K线滚动平均
销售分析 每个门店计算7天滚动销售额 门店ID分组 + 日期滚动求和
传感器数据 每个设备计算过去1小时的平均温度 设备ID分组 + 时间窗口滚动
用户行为 每个用户计算最近3次登录间隔 用户ID分组 + 带偏移量的滚动

优点:避免手动为每个组写循环,性能高且代码简洁。


案例准备:数据集与安装环境

我们先创建一个模拟销售数据集,包含多个门店的每日销售记录。

import pandas as pd
import numpy as np
# 设置随机种子
np.random.seed(42)
# 生成日期序列
date_range = pd.date_range('2024-01-01', periods=30, freq='D')
# 创建模拟数据:3个门店,每个门店30天
data = []
for store in ['A', 'B', 'C']:
    for d in date_range:
        data.append({
            'store': store,
            'date': d,
            'sales': np.random.randint(100, 500)
        })
df = pd.DataFrame(data)
print(df.head(10))

输出示例(前10行):

  store       date  sales
0     A 2024-01-01    263
1     A 2024-01-02    224
2     A 2024-01-03    451
3     A 2024-01-04    117
4     A 2024-01-05    190
...

注意:Pandas 版本建议 >= 1.3.0,本案例在 Pandas 2.1.0 上测试通过。


核心实现:Pandas分组滚动操作全解

1 基础语法:groupby().rolling()

基本语法结构:

df.groupby('分组列')[['值列']].rolling(window=n).mean()

关键参数说明:

  • 分组列:如 store,决定数据如何划分。
  • 值列:需要做滚动的数值列名。
  • window:滚动窗口大小(整数表示行数,或时间偏移量字符串如 '7D')。
  • 聚合函数:支持 .mean().sum().std().apply() 等。

2 单列滚动计算:移动平均

计算每个门店的3天移动平均销售额:

# 必须按分组列和日期排序
df_sorted = df.sort_values(['store', 'date'])
# 计算滚动平均值
df_sorted['rolling_avg_3d'] = (
    df_sorted.groupby('store')['sales']
    .rolling(window=3)
    .mean()
    .reset_index(level=0, drop=True)  # 去除多余的分组索引
)
print(df_sorted[df_sorted['store'] == 'A'].head(10))

输出:

   store       date  sales  rolling_avg_3d
0      A 2024-01-01    263             NaN
1      A 2024-01-02    224             NaN
2      A 2024-01-03    451    312.666667
3      A 2024-01-04    117    264.000000
4      A 2024-01-05    190    252.666667
...

解释:

  • 第1、2天由于窗口不足3天,返回 NaN
  • 第3天为前3天 (263+224+451)/3 = 312.67。

3 多列同时滚动计算

假设我们有销售额(sales)和顾客数(customers)两列,需要同时计算滚动平均值:

# 添加顾客数列
df['customers'] = np.random.randint(10, 50, size=len(df))
# 多列滚动
df_sorted2 = df.sort_values(['store', 'date'])
result = (
    df_sorted2.groupby('store')[['sales', 'customers']]
    .rolling(window=3)
    .mean()
    .reset_index(level=0, drop=True)
)
df_sorted2[['sales_rolling', 'cust_rolling']] = result
print(df_sorted2.head(10))

高级技巧:自定义窗口与边界处理

1 使用时间偏移窗口

如果日期不连续(例如只工作日有数据),用固定行数窗口可能会有偏差,使用时间偏移量可以解决:

# 设置日期为索引
df_ts = df.set_index('date')
# 按门店分组,使用7天滚动窗口(时间偏移)
rolling_7d = (
    df_ts.groupby('store')['sales']
    .rolling(window='7D')  # 注意:使用字符串 '7D' 而非数字
    .mean()
)
print(rolling_7d.reset_index().head(10))

关键点:时间偏移窗口要求日期列是 DatetimeIndex 类型,且每个组内日期已排序。

2 自定义滚动函数

使用 .apply() 实现更复杂的计算,例如滚动相关系数:

def rolling_corr(x):
    # 假设需要计算销售额和顾客数的滚动相关系数
    if len(x) < 3:
        return np.nan
    return x['sales'].corr(x['customers'])
result_corr = (
    df_sorted2.groupby('store')
    .rolling(window=5)
    .apply(rolling_corr)
)

3 滚动窗口边界处理

默认情况下,窗口不足时不返回值(NaN),可以通过 min_periods 参数控制:

df.groupby('store')['sales'].rolling(window=5, min_periods=2).mean()

min_periods=2 表示至少需要2个值才能计算,否则返回 NaN。


实战案例:销售数据的月度滚动统计

任务:计算每个门店每月累计至当前日期的滚动总和(即运行总和),按日计算。

# 确保按门店和日期排序
df_sorted = df.sort_values(['store', 'date'])
# 方法1:使用 expanding() 替代 rolling()
df_sorted['cumulative_sales'] = (
    df_sorted.groupby('store')['sales']
    .expanding()
    .sum()
    .reset_index(level=0, drop=True)
)
# 方法2:滚动窗口设为全部历史(用 large window 模拟)
# 但 expanding() 更直接
print(df_sorted[df_sorted['store'] == 'A'].head(10))

输出:

   store       date  sales  cumulative_sales
0      A 2024-01-01    263             263.0
1      A 2024-01-02    224             487.0
2      A 2024-01-03    451             938.0
...

常见问题与避坑指南

问题 原因 解决方案
结果出现大量 NaN 窗口大小大于分组内数据量 检查分组是否合理,或使用 min_periods
结果顺序混乱 没有对数据排序 sort_values(['分组列', '时间列'])
报错 IndexingError 滚动后索引层级混乱 使用 .reset_index(level=0, drop=True) 清理
性能慢(大数据) 默认窗口计算效率低 尝试使用 Numba 加速或选择合适的窗口类型

特别提醒:在 groupby().rolling() 之后,结果会多出一个分组索引层级,如果直接赋值回原 DataFrame,需要先 drop 掉多余的索引层级,否则会报 ValueError


问答环节

Q1:分组滚动比直接循环快吗?

A:通常快 10~50 倍,Pandas 对 groupby().rolling() 做了 C 级别优化,尤其是当窗口大小固定时,建议用 apply() 代替 Python 原生循环。

Q2:如果分组内有缺失值怎么办?

A:Pandas 默认在计算时跳过 NaN(取决于具体聚合函数)。mean() 会自动忽略 NaN,但如果想要精确控制,可以使用 min_periodsskipna=False

Q3:时间偏移窗口和固定行数窗口有什么区别?

A:固定行数窗口(如 window=3)只看最近的3行数据,不考虑时间间隔;时间偏移窗口(如 window='3D')按日期向前推算3天,适合不规则时间序列。

Q4:能否对多个分组键同时做滚动?

A:可以,df.groupby(['store', 'product']).rolling(window=3).mean(),但要注意这会生成多层索引。

Q5:滚动窗口能否向前看(未来数据)?

A:默认是向后看(基于过去的数据),如果想看未来数据(未来3天平均」),可以在分组后对时间排序进行反向,或者使用 rolling()center=True 参数(中心对齐窗口)。


Pandas 的 groupby().rolling() 是时间序列分组分析的瑞士军刀,掌握本文的核心语法、时间偏移窗口、以及常见避坑指南,你就能轻松应对 80% 的分组滚动计算场景,实际使用时,务必先排序、注意索引层级处理,并根据数据特性选择合适的窗口类型。

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