科普: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
相关推荐
“AI国潮设计-小江”6 小时前
《Python+SDXL实战:用ControlNet精准控制“英歌舞翻糖吐司”构图,附批量生成脚本》
开发语言·人工智能·python·prompt·aigc
wenwen_chao6 小时前
Python学习与开发资源合集:实用技巧与工具推荐
python
for_ever_love__7 小时前
函数——参数、lambda、*args与作用域
python·函数·大模型开发·编程基础
东莞市云毅网络有限公司7 小时前
企业资料时效性判定:Last-Modified、正文日期与版本字段三路交叉
python·sqlite·自动化运维·geo·数据监测
Query*7 小时前
深入浅出LangGraph【一】_基础篇
python·ai·langchain
XiaoMaqqqq8 小时前
市面上正规的IP驱动产业新场景新工具哪家强
运维·python·网络协议·tcp/ip
ShyanZh8 小时前
【Python3基础】19-Socket 与 TCP、UDP 编程
python·网络协议·tcp/ip·udp
惊讶的猫9 小时前
工具选对之后还不够:商城 Agent 的参数与权限治理
开发语言·python
天赐范式9 小时前
天赐范式第186天:让方差开始自洽——Bulmer效应的闭式验证
python·数字生命·天赐范式·动态运行时·遗传方差·bulmer效应
泡茶喝茶写代码9 小时前
A股量化数据工程:从 REST 接口到策略信号(第 8 篇):成长能力因子:营收与利润增速
java·python·股票数据api·股票数据api接口·股票量化数据api·股票量化数据接口·股票数据api数据