日期与时间序列
处理时间数据时,至少涉及四个独立维度:具体时刻、时区、日历频率以及区间语义。在代码中,务必将这四个维度显式区分开来,不要混为一谈。
核心类型
Timestamp:代表一个具体的时间点。DatetimeIndex:由时间戳组成的索引。Timedelta:表示一段持续时长(Duration)。Period:表示日历上的一个区间,如某个月或某个季度。DateOffset:基于日历规则的偏移量,如“月末”或“下一个工作日”。
在数据边界处解析
import pandas as pd
events = pd.DataFrame({
"occurred_at": ["2026-01-01T12:00:00+0000", "2026-01-03T12:00:00+0000"],
"value": [10, 30],
})
events["occurred_at"] = pd.to_datetime(
events["occurred_at"],
format="%Y-%m-%dT%H:%M:%S%z",
errors="coerce",
utc=True,
)
显式指定 format 更容易理解,也能避免日期格式的地区歧义。对于解析失败被强制转换为 NaT 的数据,应视为数据质量问题,务必单独检查而非忽略。
时区处理
tz_localize:为无时区信息(naive)的本地时间打上时区标签。tz_convert:将已有时区信息的时间转换到另一个时区。
条件允许时,可用 UTC 统一存储时间;展示或按业务日期、季度分组时,再转为所需时区。需注意,夏令时切换会导致某些本地时间“不存在”或“重复”,此时必须明确处理策略,而不是让程序静默报错或产生错误数据。
索引与重采样
assert events["occurred_at"].notna().all()
series = events.set_index("occurred_at")["value"].sort_index()
daily = series.resample("D").sum(min_count=1)
rolling_7d = series.rolling("7D").mean()
resample 是将数据按日历规则分箱(binning),适合处理固定周期的聚合;rolling 是围绕每个数据点计算滑动窗口。两者解决的问题不同,切勿混用。
当默认行为会影响业务逻辑时,务必显式设置区间边界和标签规则(如 closed、label 及起始点 origin)。
日历范围与周期
month_ends = pd.date_range("2026-01-01", periods=6, freq="ME", tz="UTC")
quarters = month_ends.tz_localize(None).to_period("Q")
如果业务对象是“某个月”或“某个季度”这种日历区间,而非精确到秒的时刻,优先使用 Period 类型。
日历分箱与时间窗口
示例在 1 月 1–3 日的每日总量为 10、缺失、30;min_count=1 保留无观测日期的缺失状态。滚动结果只对应两个观测时刻,均值为 10、20。"7D" 按此前七天取观测,默认不包含左边界;rolling(7) 则取七条观测,默认要求七个有效值。时间窗口滚动要求日期索引单调且没有 NaT。
处理 Series 中的日期值时,用 .dt.tz_localize(...) 和 .dt.tz_convert(...);不带 .dt 的 Series 同名方法作用于索引。无时区字符串传给 to_datetime(..., utc=True) 会被解释为 UTC,而不是本地时间。24 小时的 Timedelta 表示实际经过的时长;一天的 DateOffset 在夏令时切换时保持本地钟面时间,实际可能经过 23 或 25 小时。
时间序列指南区分日历频率与周期。"ME" 是 pandas 2.2 及更新版本的月末偏移写法。Period 不保留时区:本例先去掉 UTC 时区、保留 UTC 钟面读数,再转为季度。如果季度归属由业务时区决定,应先转换到该时区。