ts.resample("5Min").sum()中,参数"5Min"看上去很特别,本文专门分析一下。
一、偏移别名
偏移别名(offset alias) :pandas专用于时间的简写字符串,例如 "5Min"、"2H"、"15S"。
语法格式:[数字]+单位代号,数字省略默认为1。
⚠️仅用于时间 / 时间差(Timedelta) ;米、元、千克等普通物理数值不支持 这套语法。
底层会解析为
pandas.tseries.offsets.DateOffset对象;"5Min"等价Minute(5)。
时间单位
| 别名 | 含义 | 示例 |
|---|---|---|
S |
秒 | 10S |
T/min |
分钟 | 5T / 5Min |
H |
小时 | 2H |
D |
日历天 | 7D |
B |
工作日 | B |
MS |
月初 | MS |
ME |
月末(新版,旧M废弃) | ME |
复合频率支持:"1H30T"(1小时30分)。
二、 支持偏移别名的函数
注意:使用时间字符串参数时,索引/列必须是
DatetimeIndex/TimedeltaIndex。
1. pd.date_range 生成时间索引
参数:freq
python
import pandas as pd
import numpy as np
# 从2026‑01‑01开始,一共20个点,间隔5分钟
idx = pd.date_range("2026-01-01", periods=20, freq="5Min")
print(idx[:5])
程序输出示例:
DatetimeIndex(['2026-01-01 00:00:00', '2026-01-01 00:05:00',
'2026-01-01 00:10:00', '2026-01-01 00:15:00',
'2026-01-01 00:20:00'],
dtype='datetime64[ns]', freq='5min')
2. pd.timedelta_range 时间差索引
参数:freq
python
# 0秒开始,每10秒一个点
td_idx = pd.timedelta_range(start="0s", periods=10, freq="10S")
print(td_idx)
程序输出示例:
TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:10', '0 days 00:00:20',
'0 days 00:00:30', '0 days 00:00:40', '0 days 00:00:50',
'0 days 00:01:00', '0 days 00:01:10', '0 days 00:01:20',
'0 days 00:01:30'],
dtype='timedelta64[ns]', freq='10S')
3. resample() 时间重采样(降采样)
参数:freq,时序最常用
python
# 构造秒级时间序列:100条,从 00:00:00 到 00:01:39,全部落在同一个5分钟桶
rng = pd.date_range("2026-01-01", periods=100, freq="S")
ts = pd.Series(np.arange(1, 101), index=rng) # 值:1,2,...,100
# 按5分钟重采样求和
res = ts.resample("5Min").sum()
print(res)
程序输出示例:
2026-01-01 5050
Freq: 5min, dtype: int64
说明:1+2+...+100 = 5050,全部数据落入唯一的5min窗口,输出仅一行。
closed、label 参数完整举例
closed:区间哪一端闭合(包含边界),可选'left'/'right'label:输出结果的索引使用区间左边界 还是右边界 ,可选'left'/'right'
resample 默认:
closed='left', label='left',区间:[左边界, 右边界),左闭右开。
构造测试数据:包含两个5分钟窗口的数据
python
# 00:03:00 ~ 00:07:00,共240秒,跨两个5min桶:00:00‑00:05,00:05‑00:10
rng_test = pd.date_range(start="2026-01-01 00:03:00", periods=240, freq="1S")
ts_test = pd.Series(np.ones(len(rng_test)), index=rng_test)
# ① 默认配置 closed='left', label='left'
print("===① 默认 closed='left', label='left' [左闭,索引取左边界]===")
print(ts_test.resample("5Min", closed="left", label="left").sum())
# ② closed='right':右边界闭合;label='right':索引打上区间右边界
print("\n===② closed='right', label='right' (右闭,索引取右边界)===")
print(ts_test.resample("5Min", closed="right", label="right").sum())
输出结果:
===① 默认 closed='left', label='left' [左闭,索引取左边界]===
2026-01-01 00:00:00 120.0
2026-01-01 00:05:00 120.0
Freq: 5min, dtype: float64
===② closed='right', label='right' (右闭,索引取右边界)===
2026-01-01 00:05:00 120.0
2026-01-01 00:10:00 120.0
Freq: 5min, dtype: float64
解释:
- 原始数据:
00:03:00‑00:07:00。 - 第一种:桶
[00:00,00:05)放03:00‑04:59;桶[00:05,00:10)放05:00‑07:00,索引显示区间左端点。 - 第二种:
closed="right"区间变为(左边界,右边界];label="right",输出索引显示桶的右端时间点。
业务坑点:做订单、流量聚合,跨零点、跨整点统计,
closed/label设置错误会造成样本归桶错位。
字符串别名等价于实例化偏移对象:
python
from pandas.tseries.offsets import Minute
# 下面两行完全等价
print(ts.resample("5Min").sum())
print(ts.resample(Minute(5)).sum())
两段代码输出完全一致。
4. asfreq() 强制转为指定频率(升/降采样)
参数:freq
python
# 把原秒级序列强制对齐为每5分钟一个点,不在该时刻的数据填充NaN
ts_5min = ts.asfreq("5Min")
print(ts_5min.head())
程序输出示例:
2026-01-01 00:00:00 1.0
2026-01-01 00:05:00 NaN
2026-01-01 00:10:00 NaN
2026-01-01 00:15:00 NaN
2026-01-01 00:20:00 NaN
Freq: 5min, dtype: float64
原始数据只有0~99秒,只有00:00:00存在有效值,其余5分整点无原始数据,得到
NaN。
5. shift(freq=) 时间移位
不带
freq:按行数移位;带freq:按真实时间偏移。
python
idx = pd.date_range("2026-01-01 00:00", periods=3, freq="1H")
print("原始索引:")
print(idx)
# 整体向后偏移2小时
new_idx = idx.shift(2, freq="2H")
print("偏移之后:")
print(new_idx)
程序输出示例:
原始索引:
DatetimeIndex(['2026-01-01 00:00:00', '2026-01-01 01:00:00',
'2026-01-01 02:00:00'],
dtype='datetime64[ns]', freq='H')
偏移之后:
DatetimeIndex(['2026-01-01 02:00:00', '2026-01-01 03:00:00',
'2026-01-01 04:00:00'],
dtype='datetime64[ns]', freq='H')
6. pd.Grouper:普通列做时间分组(时间不是索引)
参数:freq,解决时间只是普通列,不想set_index的场景
python
df = pd.DataFrame({
"time": pd.date_range("2026-01-01", periods=100, freq="1S"),
"value": np.arange(100)
})
# groupby直接按5分钟分组,不需要把time设为index
out = df.groupby(pd.Grouper(key="time", freq="5Min"))["value"].sum()
print(out)
程序输出示例:
time
2026-01-01 4950
Freq: 5min, Name: value, dtype: int64
0+1+2+...+99 = 4950。
7. rolling(window="5Min") 时间滚动窗口
关键区分:
rolling(window=5)→ 5行(行数窗口)rolling(window="5Min")→ 过去5分钟(时间窗口),索引必须时间类型
python
rng = pd.date_range("2026-01-01", periods=300, freq="10S")
s = pd.Series(np.arange(len(rng)), index=rng)
win_rows = s.rolling(window=5).sum() # 最近5行求和
win_time = s.rolling(window="5Min").sum() # 最近5分钟时间窗口求和
print("时间窗口结果前10行:")
print(win_time.head(10))
程序输出示例:
时间窗口结果前10行:
2026-01-01 00:00:00 0.0
2026-01-01 00:00:10 1.0
2026-01-01 00:00:20 3.0
2026-01-01 00:00:30 6.0
2026-01-01 00:00:40 10.0
2026-01-01 00:00:50 15.0
2026-01-01 00:01:00 21.0
2026-01-01 00:01:10 28.0
2026-01-01 00:01:20 36.0
2026-01-01 00:01:30 45.0
Freq: 10S, dtype: float64
全部数据都在5分钟之内,窗口不断累积求和。
8. pd.bdate_range 工作日序列
参数:freq
python
# 生成8个工作日,B代表工作日,跳过周六日
work_idx = pd.bdate_range("2026-01-01", periods=8, freq="B")
print(work_idx)
程序输出示例:
DatetimeIndex(['2026-01-01', '2026-01-02', '2026-01-05', '2026-01-06',
'2026-01-07', '2026-01-08', '2026-01-09', '2026-01-12'],
dtype='datetime64[ns]', freq='B')
2026‑01‑03、04为周末,自动跳过。
三、 不支持偏移别名的场景
pd.cut / pd.qcut:普通数值分桶,不能写bins="5Min";
python
# 错误示范,运行直接报错
df_num = pd.DataFrame({"amount":range(100)})
# df_num["bin"] = pd.cut(df_num["amount"], bins="5Min")
- 直接
df.groupby("5Min"):报错,时间分组必须套pd.Grouper(freq="5Min");
python
# 错误示范
# df.groupby("5Min")["value"].sum()
- 普通整数、金额、长度列,没有
"100""10m"这种字符串简写。
普通数值想要类似resample分桶,使用
pd.cut()+groupby组合。
python
# 数值分桶正确示例
df_num = pd.DataFrame({"amount": list(range(0,100,10))})
df_num["bin"] = pd.cut(df_num["amount"], bins=[0,30,60,100])
res_num = df_num.groupby("bin")["amount"].sum()
print(res_num)
输出:
bin
(0.0, 30.0] 60
(30.0, 60.0] 150
(60.0, 100.0] 260
Name: amount, dtype: int64