Chapter 08

时间序列

掌握 Datetime/Duration 类型、dt 命名空间的时间提取、group_by_dynamic 时间窗分组、rolling 滚动窗口,以及时区与重采样。

8.1 时间类型

Date
只含年月日的日期,底层是"距 1970-01-01 的天数"(Int32)。
Datetime
时间戳,带精度(ms/us/ns)和可选时区。底层是自纪元起的整数计数。
Duration
时间差/时间跨度。两个 Datetime 相减得到 Duration,可加回 Datetime。
Time
一天内的时刻(不含日期),如 09:30:00。
dt 命名空间
时间列上的方法集合 pl.col("t").dt.*,用于提取年/月/日/小时、截断、格式化等。
时间字符串 duration
Polars 用字符串表示时间间隔:"1d"=1 天、"1h"=1 小时、"30m"=30 分、"1w"=1 周、"1mo"=1 月。用于窗口、偏移。
group_by_dynamic
按固定时间窗(如每天、每 5 分钟)对时间列分组聚合,等价 pandas 的 resample

8.2 解析与构造时间

import polars as pl

# 从字符串解析
df = pl.DataFrame({"ts": ["2026-07-01 09:30", "2026-07-02 14:00"]})
df = df.with_columns(
    pl.col("ts").str.to_datetime("%Y-%m-%d %H:%M")
)

# 生成时间范围
rng = pl.datetime_range(
    start=pl.datetime(2026, 7, 1),
    end=pl.datetime(2026, 7, 7),
    interval="1d",
    eager=True,
)

8.3 dt 命名空间:提取与运算

df.with_columns(
    pl.col("ts").dt.year().alias("year"),
    pl.col("ts").dt.month().alias("month"),
    pl.col("ts").dt.weekday().alias("weekday"),  # 1=周一
    pl.col("ts").dt.hour().alias("hour"),
    # 截断到天/小时(对齐窗口)
    pl.col("ts").dt.truncate("1d").alias("day"),
    # 格式化成字符串
    pl.col("ts").dt.strftime("%Y-%m").alias("ym"),
)

# 时间差(Duration)
df.with_columns(
    (pl.col("end") - pl.col("start")).dt.total_hours().alias("hours")
)

8.4 group_by_dynamic:时间窗聚合

# 数据须按时间列排序
df = df.sort("ts")

# 按每天聚合销量(等价 resample("1d"))
df.group_by_dynamic(
    "ts",
    every="1d",       # 窗口步长
).agg(
    pl.col("sales").sum()
)

# 每小时窗口,且按 symbol 分开
df.group_by_dynamic(
    "ts", every="1h", group_by="symbol",
).agg(pl.col("price").mean())
every / period / offset

every 是窗口间隔(多久开一个新窗),period 是窗口时长(默认等于 every),二者不等就能做重叠窗口offset 平移窗口起点。这比 pandas resample 灵活得多。

8.5 rolling:滚动窗口

# 7 天滚动均值(基于时间,非固定行数)
df.sort("ts").with_columns(
    pl.col("sales")
      .rolling_mean_by("ts", window_size="7d")
      .alias("ma7")
)

# 基于固定行数的滚动
df.with_columns(
    pl.col("sales").rolling_sum(window_size=3).alias("sum3")
)

8.6 时区处理

# 给无时区的时间赋予时区(假定它就是该时区的墙上时间)
df.with_columns(
    pl.col("ts").dt.replace_time_zone("Asia/Shanghai")
)

# 转换到另一时区(时刻不变,显示变)
df.with_columns(
    pl.col("ts").dt.convert_time_zone("UTC")
)
replace vs convert

replace_time_zone 只改标签、不改时刻("这个 09:00 其实是上海时间");convert_time_zone 改显示、保持绝对时刻("上海 09:00 = UTC 01:00")。混淆二者是时区 bug 头号来源。

8.7 小结与下一步