2. 时间戳与时间间隔的创建与转换
在 Pandas 的时间序列体系中,真正承载“时间点”和“时间长度”这两个基础概念的,是 Timestamp 与 Timedelta 两个标量类型。前者表示一个绝对时刻,后者表示两个时刻之间的间隔。理解它们的创建方式与相互转换关系,是后续做重采样、滑动窗口、缺失值识别的前提。本节围绕这两个对象展开,并重点解决一个高频实战问题:把各种格式的日期字符串列,稳定地转换为时间戳列。
一、Timestamp:单个时间点的三种创建路径
pd.Timestamp 是 Python 标准库 datetime.datetime 的子类,因此它既能像 datetime 一样参与比较和运算,又额外携带了纳秒级精度、时区信息以及 .year、.dayofweek、.quarter 等便捷属性。创建单个时间戳最直接的方式是构造:
import pandas as pd
t1 = pd.Timestamp("2024-03-15 08:30:00") # ISO 风格字符串
t2 = pd.Timestamp(2024, 3, 15, 8, 30) # 位置参数
t3 = pd.Timestamp("2024/03/15") # 斜杠分隔
print(t1, t1.year, t1.dayofweek, t1.quarter)
三种写法得到的都是同一个时刻。需要注意 dayofweek 的约定:周一为 0,周日为 6,这与部分业务系统中“周日为 1”的习惯不同,跨系统对接时要先统一参照系。若字符串中带有时区偏移,例如 "2024-03-15T08:30:00+08:00",Timestamp 会自动解析为带时区的对象,此时与不带时区的 Timestamp 直接比较会抛出异常,必须先统一时区。
二、to_datetime:批量转换的主力工具
实际数据中,时间几乎总是以“一列字符串”的形式出现,逐条构造 Timestamp 效率极低。pd.to_datetime 才是批量转换的核心函数,它接受标量、列表、Series,返回对应的 DatetimeIndex 或 datetime64 类型的 Series。它的关键参数决定了转换的成败:
format:显式指定格式串,如"%Y-%m-%d",能显著提速并避免歧义;errors:取"raise"(默认,遇错抛异常)、"coerce"(无法解析的置为 NaT)、"ignore"(原样返回);unit:当输入是数值型 Unix 时间戳时,声明其单位,如"s"、"ms"、"us"、"ns";dayfirst:处理"15/03/2024"这类日在前、月在后格式时的开关。
下面这段示例覆盖了三种典型输入:ISO 字符串、中文日期、Unix 秒级时间戳。示例数据为构造的演示数据,用于说明转换行为。
raw = pd.Series([
"2024-03-15 08:30:00", # ISO
"2024年03月16日", # 中文日期
"1710460800", # Unix 秒级时间戳
"not a date" # 脏数据
])
# 中文日期需要显式格式
cn = pd.to_datetime(raw.iloc[1], format="%Y年%m月%d日")
# Unix 时间戳需要声明 unit
unix = pd.to_datetime(raw.iloc[2], unit="s")
# 混合格式 + 容错
mixed = pd.to_datetime(raw, errors="coerce", format="mixed")
print(mixed)
执行后 mixed 的前三项会被正确解析,第四项变为 NaT。这里 format="mixed" 让 Pandas 逐条推断格式,代价是速度较慢;若数据格式统一,应优先给出精确的 format,既快又能防止 "03/04/2024" 这类月日歧义被误判。中文日期中的“年月日”必须写进格式串,否则解析会失败,这是中文数据场景最常见的坑。
result.dt.year 快速抽查。三、Timedelta:时间间隔的创建与运算
pd.Timedelta 表示一段时长,可由带单位的字符串或数值加单位构造,也支持两个 Timestamp 相减自动得到:
d1 = pd.Timedelta("1 days 06:00:00")
d2 = pd.Timedelta(90, unit="min")
d3 = pd.Timestamp("2024-03-16") - pd.Timestamp("2024-03-15")
print(d1, d2, d3)
print(d3.total_seconds() / 3600) # 换算为小时
Timedelta 与 Timestamp 的加减是封闭的:时间点 ± 间隔 = 时间点,时间点 − 时间点 = 间隔。这一组关系构成了时间序列位移、滞后特征、窗口边界计算的基础。批量场景下用 pd.to_timedelta 把一列“间隔字符串”转为 timedelta64 列,再与时间戳列相加,即可实现按行错位的时间对齐。
四、实战:把字符串列转换为时间戳列
假设有一份订单表,order_time 列混有 ISO 与中文两种写法,另有一列 ts_unix 存放秒级 Unix 时间戳。目标是把两列都转为 datetime64,并计算每笔订单距当日零点的偏移量。完整流程如下:
df = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004],
"order_time": ["2024-03-15 08:30:00", "2024年03月15日 14:05:00",
"2024-03-16 00:00:00", "bad_value"],
"ts_unix": [1710460800, 1710492300, 1710547200, 1710576000]
})
# 1) 中文与 ISO 混合:先统一分隔符再解析
s = df["order_time"].str.replace("年", "-").str.replace("月", "-").str.replace("日", "")
df["order_dt"] = pd.to_datetime(s, errors="coerce")
# 2) Unix 秒级时间戳
df["unix_dt"] = pd.to_datetime(df["ts_unix"], unit="s")
# 3) 计算距当日零点的偏移
df["offset"] = df["order_dt"] - df["order_dt"].dt.normalize()
print(df[["order_id", "order_dt", "unix_dt", "offset"]])
print(df["order_dt"].isna().sum())
这段代码里有几个值得注意的判断点。第一,str.replace 把中文日期规整为 2024-03-15 14:05:00,之后统一用默认解析即可,比逐条指定格式更省事;若数据量很大,仍建议改用 format="%Y-%m-%d %H:%M:%S" 提速。第二,errors="coerce" 让 "bad_value" 变成 NaT,随后 isna().sum() 返回 1,这就是缺失值识别的第一道入口——时间列中的非法值会以 NaT 形式暴露出来。第三,dt.normalize() 把时间戳截断到当日零点,相减得到 Timedelta,再通过 .dt.components 或 .dt.total_seconds() 可取出小时、分钟等分量。
errors="ignore" 处理生产数据。它会把整列原样返回为 object 类型,后续 .dt 访问器直接报错,问题被推迟到更远的地方才暴露。容错场景一律用 "coerce",再单独统计 NaT 数量。五、转换后的类型校验
转换是否真正成功,不能只看有没有报错,应检查三件事:df.dtypes 中目标列是否为 datetime64[ns];isna().sum() 是否与预期的脏数据条数一致;抽样比对 unix_dt 与 order_dt 在同一天的记录是否落在同一日期。若类型仍是 object,说明存在无法解析且未被 coerce 的值,或格式串与实际数据不匹配。完成校验后,这一列才可安全进入重采样、按时间索引切片等后续环节。
把本节的两条主线合起来看:Timestamp 与 to_datetime 负责把“文本”变成“时刻”,Timedelta 与时间戳相减负责把“两个时刻”变成“间隔”。前者解决数据能不能用,后者解决时间差怎么算,二者共同构成时间序列处理的地基。