科普:pandas 时间偏移别名:ts.resample(“5Min“).sum()

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

解释:

  1. 原始数据:00:03:00‑00:07:00
  2. 第一种:桶 [00:00,00:05) 放03:00‑04:59;桶 [00:05,00:10) 放05:00‑07:00,索引显示区间左端点
  3. 第二种: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为周末,自动跳过。


三、 不支持偏移别名的场景

  1. pd.cut / pd.qcut:普通数值分桶,不能写bins="5Min"
python 复制代码
# 错误示范,运行直接报错
df_num = pd.DataFrame({"amount":range(100)})
# df_num["bin"] = pd.cut(df_num["amount"], bins="5Min")
  1. 直接 df.groupby("5Min"):报错,时间分组必须套pd.Grouper(freq="5Min")
python 复制代码
# 错误示范
# df.groupby("5Min")["value"].sum()
  1. 普通整数、金额、长度列,没有 "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
相关推荐
开源量化GO1 小时前
最新AI辅助量化表达:先理清规则,再按需求选工具
人工智能·python
Zane19941 小时前
为什么 del 一个对象有时立刻消失,有时却纹丝不动?一文讲透引用计数与垃圾回收
后端·python
yichudu1 小时前
Python 装饰器 decorator
开发语言·python
一晌小贪欢1 小时前
Python办公14:批量解压文件夹内的所有 ZIP/RAR 并自动分类
开发语言·python·excel·数据可视化·python办公
Python 实战手记2 小时前
企业微信主体变更公证书办理全解析:适用场景、材料规范、踩坑点与Python信息校验脚本
开发语言·python·企业微信
郝学胜_神的一滴2 小时前
Effective Python 条款 2:遵循 PEP 8 编码风格,写出高质量 Python 代码
python·算法
郝学胜-神的一滴2 小时前
Effective Python 条款 1:确认你正在使用的 Python 版本
开发语言·数据结构·python·程序人生·算法
RanMatrix2 小时前
Python列表精讲
python
大模型丫丫2 小时前
FastAPI 入门指南:从零开始构建高性能 Python API
开发语言·python·fastapi