数据格式与类型清洗
本教程共 54 篇 · 第 27 篇 · 更新于 2026-08-11 · 约 5 分钟阅读
本节目标:把”看着像数字其实是字符串”的列转成正确类型,清理字符串里的空格,并把 object 列迁移到 3.0 的新字符串类型。
先看看类型对不对
从文件读进来的数据,类型经常和想象不一样:数字带着千分位逗号、日期格式五花八门、字符串前后带空格。第一步先体检:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"价格": ["1,200", "2,500", "3,000"],
"日期": ["2024-01-01", "2024/01/02", "20240103"],
"城市": [" 北京 ", "上海", "广州 "],
})
print(df.dtypes)
价格是文本(还带逗号),日期是三种格式的字符串,城市带空格。这样的数据没法求和、没法排序、没法按时间分析——清洗的第一步就是把类型修对。
to_numeric:容错转数值
pd.to_numeric 把字符串转成数值。关键参数 errors 有三种取值:
- errors=“raise”(默认):遇到转不了的直接抛错
- errors=“coerce”:转不了的变成 NaN
- errors=“ignore”:转不了就原样返回
s = pd.Series(["1", "2", "NA", "4"])
print(pd.to_numeric(s, errors="coerce"))
“1,200” 这种带千分位逗号的,先去掉逗号再转:
prices = df["价格"].str.replace(",", "", regex=False)
print(pd.to_numeric(prices))
数字字符串还有个隐形坑:前后空格。" 12 " 直接 to_numeric 会转失败,先 strip 再转是标准动作:
s7 = pd.Series(["12", " 34 ", "56 "])
print(pd.to_numeric(s7.str.strip()))
Tiperrors=“coerce” 把脏值变成 NaN 后
,配合 §24 的 isna().sum() 统计脏数据数量——这个数字本身就是一份数据质量报告。
to_datetime:容错转日期
pd.to_datetime 能识别大部分常见日期格式。多种格式混在一列(“2024-01-01”、“2024/01/02”、“20240103”)时,用 format=“mixed” 让它逐条识别:
print(pd.to_datetime(df["日期"], format="mixed"))
转不了的日期用 errors=“coerce” 变成 NaT(日期的缺失值):
s2 = pd.Series(["2024-01-01", "2024-13-45", "2024-02-01"])
print(pd.to_datetime(s2, errors="coerce"))
日期统一后,就能做后续的时间序列分析(§44 起);把日期列设为索引也顺理成章:df.set_index("日期")。
字符串去空格
前后空格是字符串列的”隐形杀手”:肉眼看不出来,但 “北京” 和 “北京 ” 会被当成两个不同的值。用 str.strip() 清理:
df["城市"] = df["城市"].str.strip()
print(df["城市"].unique())
Notestr.strip() 去掉首尾空白,str.lstrip() / str.rstrip() 只去一边。str 访问器的完整家族在 §48 讲。
astype:干净数据直接转
列很干净时,astype 一行搞定;有脏值时会报错,这时改用 to_numeric / to_datetime 的 errors=“coerce” 更稳:
s3 = pd.Series(["1", "2", "3"])
print(s3.astype(int)) # 干净,直接转
s4 = pd.Series(["1", "2", "x"])
# print(s4.astype(int)) # 会报错,改用 to_numeric
print(pd.to_numeric(s4, errors="coerce"))
object 列清理流程
object 列是”垃圾桶”,什么都能装:正常字符串、空字符串、None、各种占位符混在一起。清理步骤:
- 看这一列到底有哪些值:
df["列"].unique()或 value_counts() - 找出脏值:""、“n/a”、”—”、“unknown”、带空格的值
- 统一处理:替换成 NaN 或统一成标准写法
- 转成正确的类型
raw = pd.Series(["北京", "", " 上海 ", "n/a", None, "广州"])
print(raw.value_counts(dropna=False))
cleaned = raw.replace({"": np.nan, "n/a": np.nan}).str.strip()
print(cleaned)
print(cleaned.isna().sum())
3.0 的新字符串类型:str
pandas 3.0 起,字符串列默认使用新的 string 类型(StringDtype),dtype 显示为 str,不再是 object(§07 讲过类型体系)。创建和转换很简单:
s5 = pd.Series(["a", "b", None])
print(s5.dtype) # str
s6 = s5.astype("str") # 显式转换
print(s6.isna())
str 和 object 的完整对比在 §49 详讲,这里只留迁移要点:3.0 默认字符串列就是 str 类型;astype(“str”) 保留缺失值,不会变成字符串 “nan”(老版本的大坑,已修复)。
Warningstr 类型列赋值非字符串会报错。老代码如果依赖 object 列”什么都能塞”(一列里字符串数字混着),需要先清理成统一类型;确实要保留混合内容的,显式转回 object:
df["列"] = df["列"].astype("object")。
老项目里已经写成 dtype=“object” 的代码,3.0 下仍能跑,只是享受不到新类型的性能;新代码直接不写 dtype 或写 dtype=“str” 即可。
类型检查与批量转换
几个常用的类型判断函数(§07 也提过):
- is_numeric_dtype:是不是数值
- is_datetime64_any_dtype:是不是日期时间
- is_string_dtype:是不是字符串(str 或 object 都算)
批量把整个表转成更合理的类型,用 convert_dtypes:
df2 = pd.DataFrame({"a": [1, None], "b": ["x", "y"]})
print(df2.convert_dtypes().dtypes) # a → Int64,b → string
新字符串类型底层优先用 PyArrow 存储,性能更好、内存更省(§04 提过)。清洗完统一复查 df.dtypes 和 df.isna().sum()——类型对了,后面求和、分组、画图才不会莫名报错。
小结
类型清洗的套路:先 dtypes 体检,再按需转换。容错转换用 to_numeric / to_datetime 的 errors=“coerce”,干净数据用 astype;字符串列清理空格后,迁移到 3.0 默认的 str 类型(完整对比见 §49)。清洗完统一复查 dtypes 和 isna().sum()。