Python案例如何用Pandas做数据分组重采日志

wen python案例 5

Python案例详解如何用Pandas做数据分组重采样(附实战代码)

📚 目录导读

  1. 为什么需要数据分组重采样?
  2. Pandas核心函数与概念速览
  3. 案例实战:电商订单数据的分组重采样
    • 1 数据准备与清洗
    • 2 按日/周/月重采样单列数据
    • 3 按“地区+时间”双重分组重采样
    • 4 自定义聚合函数的方法
  4. 高频踩坑与性能优化技巧
  5. 常见问题QA
  6. 总结与延伸阅读

为什么需要数据分组重采样?

在数据分析工作中,原始日志数据通常具备两个特征:高频率非结构化,服务器每分钟生成数千条访问日志,或电商平台每秒记录订单变动,直接分析这种细粒度数据会导致:

Python案例如何用Pandas做数据分组重采日志

  • 计算量过大,模型训练效率低下
  • 难以发现宏观趋势(如周环比、月同比)
  • 可视化图表过于杂乱

分组重采样(Grouped Resampling)正是为解决此类问题而生,它允许你按时间维度(如小时、天、周)将数据“压缩”,同时保留分组(如地区、用户类型)的差异性,Pandas 的 resample() 方法结合 groupby(),能优雅地完成这一任务。

核心概念:重采样本质是“时间轴上的分组聚合”,而分组重采样则是“时间轴 + 类别轴”的二维聚合。


Pandas核心函数与概念速览

在动手写代码前,先理解以下关键组件:

函数/属性 作用 典型参数
pd.to_datetime() 将字符串/数值转为时间戳 format, unit
DataFrame.set_index() 将时间列设为索引(重采样必要条件) inplace, drop
.resample() 按时间频率分组,返回 DatetimeIndexResampler 对象 rule (如 'D', 'W', 'M')
.groupby() 按类别分组,返回 GroupBy 对象 level=0 或列名
.agg() 指定多个聚合函数 字典或列表

重要规则resample() 默认作用于 DatetimeIndex,因此需先用 set_index() 将时间字段设为行索引


案例实战:电商订单数据的分组重采样

1 数据准备与清洗

假设你是一家电商公司的数据分析师,手头有一份 orders.csv 文件,包含以下字段:

order_id,order_time,city,payment_amount,payment_type
1,2025-01-05 14:32:00,北京,299.0,微信
2,2025-01-05 05:15:00,上海,159.0,支付宝
...

模拟生成1000条随机数据(实际工作中直接读取CSV即可):

import pandas as pd
import numpy as np
# 生成示例数据
dates = pd.date_range(start='2025-01-01', end='2025-03-31', freq='2h')
sample = pd.DataFrame({
    'order_time': np.random.choice(dates, 1000),
    'city': np.random.choice(['北京', '上海', '广州', '深圳'], 1000),
    'payment_amount': np.round(np.random.uniform(50, 500, 1000), 2),
    'payment_type': np.random.choice(['微信', '支付宝', '银行卡'], 1000)
})
sample.to_csv('orders.csv', index=False)

数据清洗步骤

df = pd.read_csv('orders.csv')
# 关键:将订单时间转为datetime并设为索引
df['order_time'] = pd.to_datetime(df['order_time'])
df.set_index('order_time', inplace=True)

df 的索引是时间戳,可以开始重采样。


2 按日/周/月重采样单列数据

需求:计算每天的总支付金额。

daily_revenue = df['payment_amount'].resample('D').sum()
print(daily_revenue.head())

代码说明

  • 'D' 代表按天(日历日)重采样,其他常用规则:'H'(小时)、'W'(周)、'M'(月)、'Q'(季度)。
  • 可同时聚合多列:df[['payment_amount', 'order_id']].resample('W').agg({'payment_amount': 'sum', 'order_id': 'count'})

输出示例

order_time
2025-01-01    12543.18
2025-01-02     9876.45
...

思考题:若想统计每周的订单数量,应使用 .count() 还是 .size()

  • count() 排除 NaN,size() 包含所有行,若数据无缺失,两者等价。

3 按“地区+时间”双重分组重采样

这是核心需求:既要按城市分组,又要按时间压缩

先 groupby 再 resample(推荐)
# 先按城市分组,再对每组时间序列重采样
grouped_resampled = df.groupby('city').resample('W')['payment_amount'].sum()
print(grouped_resampled.head(10))

输出结构

city    order_time
北京    2025-01-05    7654.32
        2025-01-12    8901.45
...
先 resample 再 groupby(不常用)
# 先按周重采样,再按城市分组聚合
resampled_then_grouped = df.resample('W').groupby('city')['payment_amount'].sum()

两种方法的区别

  • 方法一更直观,分组在前,时间在后,便于理解。
  • 方法二适合需要先“对齐时间轴”的场景(如处理不规则时间间隔)。

实战建议:大多数情况下使用方法一,如果数据量极大(>100万行),方法二因 resample 先压缩数据,内存占用更小。


4 自定义聚合函数的方法

当内置函数(sum, mean, std)无法满足需求时,可使用 apply()agg()

需求示例:计算每周每个城市的支付金额中位数支付方式种类数

def custom_agg(group):
    return pd.Series({
        'median_amount': group['payment_amount'].median(),
        'payment_type_cnt': group['payment_type'].nunique()
    })
result = df.groupby('city').resample('W').apply(custom_agg)
print(result.head())

输出

                  median_amount  payment_type_cnt
city order_time                                  
北京  2025-01-05         287.50                 3
     2025-01-12         312.80                 2

注意apply 性能较慢,若数据量超百万行,优先用 agg 配合 Numpy 函数。


高频踩坑与性能优化技巧

1 常见错误与解决方案

错误现象 原因 解决方法
TypeError: Only valid with DatetimeIndex 时间列未设为索引 先执行 df.set_index('time_col')
重采样后结果为空 时间索引含 NaN 或间隔不规则 df.index = df.index.dropna()df.asfreq() 填充缺失时间点
分组后结果混乱 groupbyresample 顺序颠倒 确保先 groupbyresample

2 性能加速技巧

  1. 使用 pipe() 链式调用:减少中间变量拷贝。

    (df
     .set_index('order_time')
     .pipe(lambda x: x.groupby('city').resample('W')['payment_amount'].sum())
    )
  2. 禁用 inplace:Pandas 链式操作中 inplace 会破坏管道。

  3. groupby 列排序:若分组列是字符串,适当排序可提升性能:

    df.sort_values('city', inplace=True)
  4. 使用 W-MON 代替 W:默认周结束于周日,若要指定周一作为周起始:

    .resample('W-MON')

常见问题QA

Q1:重采样频率如何快速记忆?

A:常见缩写规则:

  • D = Day,H = Hour,T/min = Minute,S = Second
  • 月:M(月末),MS(月初);年:Y(年末),YS(年初)
  • 带偏移:W-MON(每周一),BM(每月最后一个工作日)

Q2:为什么我的 resample 返回的索引是“区间”而不是具体日期?

A:当你使用 'M''Q' 时,Pandas 默认返回 PeriodIndex,可通过 label='right'closed='left' 调整,若需严格 DatetimeIndex,可:

df.resample('M', label='right').sum().to_timestamp()

Q3:分组重采样后如何恢复扁平结构?

A:使用 .reset_index() 即可:

result = df.groupby('city').resample('W')['amount'].sum()
flat_result = result.reset_index()

Q4:能否对非数值列重采样?

A:可以,但需用 first()(取首次出现值)、last()ohlc()(金融OHLC数据)。

df['payment_type'].resample('D').first()

总结与延伸阅读

通过上述案例,你已掌握:

  • ✅ 时间索引的设置与清洗
  • ✅ 单一时间维度的 resample 用法
  • ✅ 结合 groupby 实现“城市 × 时间”双重聚合
  • ✅ 自定义聚合函数及性能优化技巧

延伸方向

  • 进阶:学习 pd.Groupergroupby 混合使用,处理更复杂的多级索引。
  • 可视化:将重采样结果与 matplotlibplotly 结合,绘制时间序列折线图。
  • 大数据:对于超过内存的数据,使用 pysparkgroupby + window 函数替代。

希望这篇指南能将你从“手动处理日志”的泥潭中解救出来,让数据分析变得高效且优雅,如果你在实际应用中遇到特殊的时间分组需求(如“每周四至下周三”等),欢迎在评论区提问,我会持续补充更多案例。

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