摘要
数据类型决定了字段可以进行什么运算。字符串形式的金额不能可靠求和,未转换的日期不能正确排序,带时区和不带时区的时间也不能直接比较。
本文介绍 pandas 中数字、字符串、布尔值、分类值和日期时间的转换方法,重点讲解错误值处理、日期格式、时间范围筛选、时区、按时间分组和常见陷阱。
一、背景与问题
数据源中的字段经常以不适合分析的形式存在:
- 金额是
"1,200.50"。 - 数量是
"10件"。 - 日期是
"2026/09/30"、"2026-09-30"和"09-30-2026"的混合格式。 - 布尔值是
"是"、"否"、"Y"、"N"。 - 状态字段包含多个别名。
- 时间带有时区,而系统时间没有时区。
如果不先转换类型,可能出现:
values=["2","10","3"]print(sorted(values))# ['10', '2', '3']字符串排序与数值排序不同。数据分析中应明确每个字段的语义和类型。
二、核心概念
1. 数值类型
数值字段包括整数、浮点数和高精度金额。金额是否可以使用二进制浮点数,要根据业务精度要求决定。
常用转换:
pd.to_numericastyperoundclip
2. 字符串类型
pandas 的string类型比传统object更明确,适合处理文本列、ID 和分类值。
常见操作:
- 去除首尾空格。
- 大小写标准化。
- 替换字符。
- 正则提取。
- 判断是否包含关键字。
3. 分类类型
分类字段取值集合有限,例如地区、渠道和订单状态。使用category可以减少内存,并明确合法类别。
4. 日期时间
datetime64能支持日期比较、加减、提取年/月/日、重采样和时间窗口筛选。
日期时间问题通常包括:
- 格式不一致。
- 无效日期。
- 时区不同。
- 夏令时变化。
- 日期边界包含关系不明确。
5. 时间点与时间段
时间点表示某个瞬间,时间段表示开始和结束范围。统计“某月数据”时,要明确使用:
开始时间 <= 时间 < 下个月开始时间左闭右开区间可以避免时分秒和毫秒边界问题。
三、工作原理
1. 类型转换流程
检查原始值 → 选择目标类型 → 执行转换 → 统计转换失败数量 → 处理失败记录 → 校验范围和业务规则不要直接使用errors="ignore"静默跳过转换失败,否则问题会继续留在数据中。
2. 日期解析
pd.to_datetime可以识别多种常见格式,但自动推断并不总是稳定。数据格式明确时,应指定格式;格式混合时,先记录无法解析的记录。
3. 时区
无时区时间只表示一个本地时间字符串,有时区时间才对应明确的时间点。跨地区系统应统一使用 UTC 存储,展示时再转换为用户时区。
4. pandas 时间索引
将日期列设置为索引后,可以使用时间切片、重采样和滚动窗口:
df=df.set_index("event_time").sort_index()monthly=df["amount"].resample("ME").sum()时间索引必须排序,且索引类型应为 DatetimeIndex。
四、实战示例
1. 数字转换
importpandasaspd raw=pd.Series(["1,200.50","800","unknown",None])amount=pd.to_numeric(raw.str.replace(",","",regex=False),errors="coerce",)print(amount)print("failed:",amount.isna().sum())转换失败的值变成缺失后,要根据业务决定保留、修复还是拒绝数据。
2. 整数和可空整数
values=pd.Series(["1","2",None])integer_values=pd.to_numeric(values,errors="coerce")nullable_integer=integer_values.astype("Int64")print(nullable_integer.dtype)普通 NumPy 整数类型不能表示缺失值,pandas 的Int64扩展类型可以同时保存整数和缺失。
3. 字符串标准化
status=pd.Series([" Paid ","paid","PENDING",None])normalized=(status.astype("string").str.strip().str.lower())print(normalized)如果存在业务别名,可以通过映射表转换:
mapping={"paid":"已支付","pending":"待支付","cancelled":"已取消",}display_status=normalized.map(mapping)4. 分类字段
status=normalized.astype(pd.CategoricalDtype(categories=["paid","pending","cancelled"],ordered=False,))print(status)print(status.cat.categories)如果输入中出现未定义类别,会转换为缺失,需要单独检查。
5. 解析统一日期
dates=pd.Series(["2026-09-01","2026/09/02","2026-09-03 08:30:00"])parsed=pd.to_datetime(dates,errors="coerce",)print(parsed)6. 指定日期格式
dates=pd.Series(["2026-09-01","2026-09-02"])parsed=pd.to_datetime(dates,format="%Y-%m-%d",errors="coerce",)格式明确时指定format,可以让规则更清晰,也有助于发现格式不符合预期的数据。
7. 日期范围筛选
df=pd.DataFrame({"order_id":["A001","A002","A003"],"order_date":pd.to_datetime(["2026-09-01","2026-09-15","2026-10-01"]),"amount":[100,200,300],})start=pd.Timestamp("2026-09-01")end=pd.Timestamp("2026-10-01")september=df.loc[(df["order_date"]>=start)&(df["order_date"]<end)]print(september)使用左闭右开区间后,10 月 1 日不会被误计入 9 月。
8. 提取日期字段
df["year"]=df["order_date"].dt.year df["month"]=df["order_date"].dt.month df["weekday"]=df["order_date"].dt.dayofweek df["month_start"]=df["order_date"].dt.to_period("M")dt访问器可以提取日期组成部分,但提取前必须确认列已经是日期类型。
9. 按月统计
monthly=(df.set_index("order_date").sort_index().resample("ME")["amount"].sum())print(monthly)按月统计时要确认使用的是自然月、财务月还是业务周期。
10. 时区转换
events=pd.DataFrame({"event_time":["2026-09-30T08:00:00Z","2026-09-30T12:00:00Z",],})events["event_time"]=pd.to_datetime(events["event_time"],utc=True,)events["beijing_time"]=events["event_time"].dt.tz_convert("Asia/Shanghai")统一保存 UTC,再在展示层转换时区,可以减少跨地区数据比较的歧义。
11. 转换质量报告
defconversion_report(original:pd.Series,converted:pd.Series,)->dict:return{"total":int(len(original)),"converted_missing":int(converted.isna().sum()),"original_missing":int(original.isna().sum()),"new_failures":int(converted.isna().sum()-original.isna().sum()),}转换报告可以帮助判断数据源格式是否发生变化。
五、常见问题与实践建议
1.errors="coerce"会不会隐藏错误?
会。它把失败值转成缺失,方便继续处理,但必须结合失败数量和原始值报告使用。不能把所有转换失败都静默忽略。
2. 为什么日期排序不正确?
通常是因为日期仍然是字符串。先检查:
print(df["order_date"].dtype)只有日期类型才能可靠地按时间排序。
3. 日期格式混合怎么办?
先使用宽松解析得到结果,再筛选无法解析的原始值,针对这些值制定专门规则。不要在没有确认格式的情况下强行指定一个格式。
4. 为什么时区转换后时间变了?
转换时区后,显示的本地时间会变化,但它们表示的是同一个瞬间。需要区分“改变显示时区”和“错误地修改时间数值”。
5. 金额是否应该使用浮点数?
普通分析通常可以使用浮点数并在展示或汇总时控制精度;需要严格账务一致性的场景,应考虑整数分单位或 Decimal,并明确舍入规则。
六、进阶思考
1. 类型契约
可以为数据集定义类型契约:
order_id: string, not null, unique amount: decimal, not null, >= 0 order_date: datetime with timezone status: enum(paid, pending, cancelled, refund)每次数据进入分析流程前执行契约检查,可以提前阻止错误数据传播。
2. 日期时间边界
日、周、月、季度和财务周期的边界可能不同。将边界统一封装成函数,避免每个 Notebook 手写一套筛选条件。
3. 类型转换与性能
大量字符串转换、日期解析和分类转换会消耗时间。对于稳定格式的数据,可以指定格式、只读取需要的列,并把转换后的结果保存为列式格式。
4. 保留原始字段
关键字段转换时,可以暂时保留原始列:
df["order_date_raw"]=df["order_date"]df["order_date"]=pd.to_datetime(df["order_date_raw"],errors="coerce",)在规则稳定后再决定是否删除原始列,但审计和排查阶段保留原始值非常有帮助。
结论
类型转换是数据清洗和统计分析的基础。数字、字符串、分类和日期字段都应有明确的目标类型,转换后还要检查失败值、缺失值和业务范围。
日期时间处理尤其要关注格式、时区和区间边界。完成类型标准化后,后续的数据筛选、分组和时间序列分析才能建立在可靠基础上。
参考资料
- pandas 类型转换文档:https://pandas.pydata.org/docs/user_guide/basics.html
- pandas 时间序列文档:https://pandas.pydata.org/docs/user_guide/timeseries.html
- pandas
to_datetime:https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html