数据类型转换与日期时间处理

摘要

数据类型决定了字段可以进行什么运算。字符串形式的金额不能可靠求和,未转换的日期不能正确排序,带时区和不带时区的时间也不能直接比较。

本文介绍 pandas 中数字、字符串、布尔值、分类值和日期时间的转换方法,重点讲解错误值处理、日期格式、时间范围筛选、时区、按时间分组和常见陷阱。

一、背景与问题

数据源中的字段经常以不适合分析的形式存在:

  • 金额是 "1,200.50"。
  • 数量是 "10件"。
  • 日期是 "2026/09/30"、"2026-09-30" 和 "09-30-2026" 的混合格式。
  • 布尔值是 "是"、"否"、"Y"、"N"。
  • 状态字段包含多个别名。
  • 时间带有时区,而系统时间没有时区。

如果不先转换类型,可能出现:

python 复制代码
values = ["2", "10", "3"]
print(sorted(values))  # ['10', '2', '3']

字符串排序与数值排序不同。数据分析中应明确每个字段的语义和类型。

二、核心概念

1. 数值类型

数值字段包括整数、浮点数和高精度金额。金额是否可以使用二进制浮点数,要根据业务精度要求决定。

常用转换:

  • pd.to_numeric
  • astype
  • round
  • clip

2. 字符串类型

pandas 的 string 类型比传统 object 更明确,适合处理文本列、ID 和分类值。

常见操作:

  • 去除首尾空格。
  • 大小写标准化。
  • 替换字符。
  • 正则提取。
  • 判断是否包含关键字。

3. 分类类型

分类字段取值集合有限,例如地区、渠道和订单状态。使用 category 可以减少内存,并明确合法类别。

4. 日期时间

datetime64 能支持日期比较、加减、提取年/月/日、重采样和时间窗口筛选。

日期时间问题通常包括:

  • 格式不一致。
  • 无效日期。
  • 时区不同。
  • 夏令时变化。
  • 日期边界包含关系不明确。

5. 时间点与时间段

时间点表示某个瞬间,时间段表示开始和结束范围。统计"某月数据"时,要明确使用:

text 复制代码
开始时间 <= 时间 < 下个月开始时间

左闭右开区间可以避免时分秒和毫秒边界问题。

三、工作原理

1. 类型转换流程

text 复制代码
检查原始值
  → 选择目标类型
  → 执行转换
  → 统计转换失败数量
  → 处理失败记录
  → 校验范围和业务规则

不要直接使用 errors="ignore" 静默跳过转换失败,否则问题会继续留在数据中。

2. 日期解析

pd.to_datetime 可以识别多种常见格式,但自动推断并不总是稳定。数据格式明确时,应指定格式;格式混合时,先记录无法解析的记录。

3. 时区

无时区时间只表示一个本地时间字符串,有时区时间才对应明确的时间点。跨地区系统应统一使用 UTC 存储,展示时再转换为用户时区。

4. pandas 时间索引

将日期列设置为索引后,可以使用时间切片、重采样和滚动窗口:

python 复制代码
df = df.set_index("event_time").sort_index()
monthly = df["amount"].resample("ME").sum()

时间索引必须排序,且索引类型应为 DatetimeIndex。

四、实战示例

1. 数字转换

python 复制代码
import pandas as pd

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. 整数和可空整数

python 复制代码
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. 字符串标准化

python 复制代码
status = pd.Series([" Paid ", "paid", "PENDING", None])

normalized = (
    status.astype("string")
    .str.strip()
    .str.lower()
)

print(normalized)

如果存在业务别名,可以通过映射表转换:

python 复制代码
mapping = {
    "paid": "已支付",
    "pending": "待支付",
    "cancelled": "已取消",
}

display_status = normalized.map(mapping)

4. 分类字段

python 复制代码
status = normalized.astype(
    pd.CategoricalDtype(
        categories=["paid", "pending", "cancelled"],
        ordered=False,
    )
)

print(status)
print(status.cat.categories)

如果输入中出现未定义类别,会转换为缺失,需要单独检查。

5. 解析统一日期

python 复制代码
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. 指定日期格式

python 复制代码
dates = pd.Series(["2026-09-01", "2026-09-02"])
parsed = pd.to_datetime(
    dates,
    format="%Y-%m-%d",
    errors="coerce",
)

格式明确时指定 format,可以让规则更清晰,也有助于发现格式不符合预期的数据。

7. 日期范围筛选

python 复制代码
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. 提取日期字段

python 复制代码
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. 按月统计

python 复制代码
monthly = (
    df.set_index("order_date")
    .sort_index()
    .resample("ME")["amount"]
    .sum()
)

print(monthly)

按月统计时要确认使用的是自然月、财务月还是业务周期。

10. 时区转换

python 复制代码
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. 转换质量报告

python 复制代码
def conversion_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. 为什么日期排序不正确?

通常是因为日期仍然是字符串。先检查:

python 复制代码
print(df["order_date"].dtype)

只有日期类型才能可靠地按时间排序。

3. 日期格式混合怎么办?

先使用宽松解析得到结果,再筛选无法解析的原始值,针对这些值制定专门规则。不要在没有确认格式的情况下强行指定一个格式。

4. 为什么时区转换后时间变了?

转换时区后,显示的本地时间会变化,但它们表示的是同一个瞬间。需要区分"改变显示时区"和"错误地修改时间数值"。

5. 金额是否应该使用浮点数?

普通分析通常可以使用浮点数并在展示或汇总时控制精度;需要严格账务一致性的场景,应考虑整数分单位或 Decimal,并明确舍入规则。

六、进阶思考

1. 类型契约

可以为数据集定义类型契约:

text 复制代码
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. 保留原始字段

关键字段转换时,可以暂时保留原始列:

python 复制代码
df["order_date_raw"] = df["order_date"]
df["order_date"] = pd.to_datetime(
    df["order_date_raw"],
    errors="coerce",
)

在规则稳定后再决定是否删除原始列,但审计和排查阶段保留原始值非常有帮助。

结论

类型转换是数据清洗和统计分析的基础。数字、字符串、分类和日期字段都应有明确的目标类型,转换后还要检查失败值、缺失值和业务范围。

日期时间处理尤其要关注格式、时区和区间边界。完成类型标准化后,后续的数据筛选、分组和时间序列分析才能建立在可靠基础上。

参考资料

  1. pandas 类型转换文档:https://pandas.pydata.org/docs/user_guide/basics.html
  2. pandas 时间序列文档:https://pandas.pydata.org/docs/user_guide/timeseries.html
  3. pandas to_datetime:https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html
相关推荐
Logic1013 小时前
C语言/数据结构位运算题解:异或XOR找出英雄队伍中的“独特战斗力“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
Cccp.1233 小时前
【leetcode】(九)哈希函数与哈希表
数据结构·leetcode·哈希算法
余额瞒着我当琳17 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
程序员南飞17 小时前
win系统安装minio
java·数据结构·算法
w_zero_one19 小时前
链表(4)
java·数据结构·算法
白山编程大哥20 小时前
C语言篇:语法进阶
c语言·数据结构·算法
Logic10121 小时前
C语言/数据结构动态规划题解:爬楼梯(含坏台阶)——避开障碍物的路径计数
c语言·数据结构·动态规划·时间复杂度·空间复杂度·算法题·爬楼梯
All for pursuit.1 天前
【回溯-6】79.单词搜索
数据结构·c++·算法·leetcode
Benny_Tang1 天前
AT_arc180_d [ARC180D] Division into 3 题解
数据结构·c++·算法