7-2 resample:按频率复诊 本节摘要:resample按新频率把时间索引重新分组——降采样把高频压成低频(日到月),升采样把低频铺成高频(月到日)。本节核心:聚合口径决定指标含义,月的sum是总额、月的mean是日均水平,两者是完全不同的诊断结论。 随访档案建好(7-1),复诊开始。日销售额三千行没人看,管理层要月报。把日粒度重新编排成月粒度,就是重采样的降采样。 降采样:日到月 sum与mean的差距就是口径的差距:总额看规模,日均看水平。一月31天总额最高,可能只是因为天数多——只看总额会被日历骗,只看日均会被大单骗,会诊报告两个都要。这本质上是5-1的GroupBy套了一层时间频率外衣,agg的四种输出形态在这里同样适用。
本节摘要:resample按新频率把时间索引重新分组——降采样把高频压成低频(日到月),升采样把低频铺成高频(月到日)。本节核心:聚合口径决定指标含义,月的sum是总额、月的mean是日均水平,两者是完全不同的诊断结论。
随访档案建好(7-1),复诊开始。日销售额三千行没人看,管理层要月报。把日粒度重新编排成月粒度,就是重采样的降采样。
import pandas as pd idx = pd.date_range('2024-01-01', periods=90, freq='D') daily = pd.Series(range(1000, 1000 + 90 * 10, 10), index=idx) # 降采样到月:sum口径 = 月总额 monthly_sum = daily.resample('MS').sum() print(monthly_sum) # 2024-01-01 70350 # 2024-02-01 118860 # 2024-03-01 52200 # Freq: MS, Name: ..., dtype: int64 # mean口径 = 月内日均 monthly_mean = daily.resample('MS').mean() print(monthly_mean.round(1)) # 2024-01-01 1205.0 # 2024-02-01 1440.0 # 2024-03-01 1740.0
sum与mean的差距就是口径的差距:总额看规模,日均看水平。一月31天总额最高,可能只是因为天数多——只看总额会被日历骗,只看日均会被大单骗,会诊报告两个都要。这本质上是5-1的GroupBy套了一层时间频率外衣,agg的四种输出形态在这里同样适用。
| 频率串 | 含义 | 频率串 | 含义 |
|---|---|---|---|
| D | 日历日 | B | 工作日 |
| W | 周末收于周日 | MS | 月初 |
| M | 月末 | QS / Q | 季初 / 季末 |
| H | 小时 | min | 分钟 |
MS与M的区别要留心:MS标在月初(1月1日),M标在月末(1月31日)。月报标签想对齐月初就MS,与财务对账用M。
# 季度数据铺成月度格子 quarterly = pd.Series([3000, 3600], index=pd.to_datetime(['2024-01-01', '2024-04-01'])) monthly_grid = quarterly.resample('MS').asfreq() print(monthly_grid) # 2024-01-01 3000.0 # 2024-02-01 NaN ← 铺出来的空格子 # 2024-03-01 NaN # 2024-04-01 3600.0 # 三种填法:前值填充、插值、置零 print(monthly_grid.ffill().head(3)) # 2024-01-01 3000.0 # 2024-02-01 3000.0 ← 沿用上季 # 2024-03-01 3000.0 print(monthly_grid.interpolate().head(3)) # 2024-01-01 3000.0 # 2024-02-01 3200.0 ← 线性爬坡 # 2024-03-01 3400.0
升采样制造缺失,填法的选择就是3-1补值策略的时间版:存量指标(库存、费率)用ffill,流量指标(销售额、访问量)绝不ffill——上季的销售额不能算到二头上,流量插值或干脆保持NaN。
⚠️ 常见坑:流量类数据升采样后ffill,等于把季度总额复制成三个月的"月总额",年报一汇总变四倍。判断标准就一条:这个指标是存量还是流量。
# 财经式复诊:开高低收 print(daily.resample('MS').ohlc()['open']) # 2024-01-01 1000 # 2024-02-01 1310 # 2024-03-01 1590 # 多列表的复诊:金额求和、门店数取均值——agg字典口径 df = pd.DataFrame({ '金额': daily.values, '笔数': [i % 5 + 1 for i in range(90)], }, index=idx) report = df.resample('MS').agg({'金额': 'sum', '笔数': 'mean'}) print(report.round(1)) # 金额 笔数 # 2024-01-01 70350 3.0 # 2024-02-01 118860 3.0 # 2024-03-01 52200 3.1

时间索引既能resample('MS')也能groupby(index.month),结果看起来都是月度数,差在哪?三处:resample的输出仍是时间索引(可以继续rolling、可以按时间切片),groupby月份输出的是普通分类索引;resample跨年自然区分(2023年1月与2024年1月是两期),groupby月份会把两年的一月并到一组,跨年数据直接踩坑;resample的频率体系(MS、W、Q)开箱即用,groupby要自己拼。经验法则:数据有时间索引就用resample,按非时间维度分组才用groupby,两者还能嵌套(每月内再按门店分组),那已是报表管线的标准姿势。
# 跨年陷阱演示:groupby月份合并了两年的一月 two_years = pd.Series([10, 20], index=pd.to_datetime(['2023-01-05', '2024-01-05'])) print(two_years.groupby(two_years.index.month).sum()) # 1 30 ← 两年的一月被并成一组 print(two_years.resample('MS').sum()) # 2023-01-01 10 # 2024-01-01 20 ← resample分得清年份
下一节给复诊数据配上持续监护:rolling移动窗口。