摘要
业务数据中大量字段都是文本,例如姓名、地区、渠道、订单状态、商品名称和备注。文本字段看似简单,却经常因为空格、大小写、别名、全角半角、非法字符和分类不统一,导致分组统计、筛选和匹配结果错误。
本文围绕 pandas 的字符串处理能力,介绍文本字段标准化、分类值映射、正则提取、非法值识别和清洗报告生成,帮助把混乱的文本字段整理为可分析、可复用的数据。
一、背景与问题
文本字段常见问题包括:
- 同一地区出现
华东、华东、East China。 - 状态字段出现
Paid、paid、已支付、付款完成。 - 商品名称中包含多余空格、换行符和特殊符号。
- 手机号、订单号、门店编码格式不统一。
- 人工填写的字段存在错别字和同义词。
- 分类值数量膨胀,导致透视表和分组结果失真。
如果不处理这些问题,分组统计会变成:
text
paid 100
Paid 32
已支付 8
付款完成 5
这些取值可能表示同一业务状态,却被当成不同类别。
文本清洗的目标不是"把字符串变好看",而是让文本字段具备稳定的业务含义。
二、核心概念
1. 字符串标准化
标准化是将不同写法统一为固定格式,例如:
- 去除首尾空格。
- 统一大小写。
- 统一全角半角。
- 去除多余换行和制表符。
- 替换特殊符号。
标准化应尽量可解释,不要用过度复杂的规则误伤有效内容。
2. 分类值
分类值通常有有限取值集合,例如:
- 订单状态:已支付、待支付、已取消、退款。
- 地区:华东、华南、华北。
- 渠道:官网、App、门店、第三方平台。
分类字段应该建立取值字典和映射规则。
3. 映射表
映射表用于把多个别名统一为标准值:
| 原始值 | 标准值 |
|---|---|
| paid | 已支付 |
| Paid | 已支付 |
| 付款完成 | 已支付 |
| pending | 待支付 |
映射表比散落在代码里的 if 判断更容易维护。
4. 正则表达式
正则适合处理格式化文本,例如提取编号、判断手机号、清理多余空白和识别特定模式。
正则很强大,但可读性较差。复杂规则应写注释并配套样例测试。
三、工作原理
1. 文本清洗流程
text
读取原始文本
→ 转换为 string 类型
→ 基础标准化
→ 分类映射
→ 合法值检查
→ 无法识别值输出报告
→ 生成清洗字段
推荐保留原始列,并新增清洗后的标准列:
text
status_raw 原始值
status_clean 标准值
2. 原始值与标准值
不要直接覆盖原始值。保留原始值有助于:
- 追踪数据来源问题。
- 优化映射表。
- 审核清洗规则。
- 处理争议记录。
3. 合法值校验
清洗后仍然需要检查是否存在未识别分类:
text
标准值 ∈ 预定义枚举
未识别值不应该被悄悄归为"其他",除非业务明确允许。
四、实战示例
1. 准备示例数据
python
import pandas as pd
df = pd.DataFrame(
{
"order_id": [" A001 ", "A-002", "A003\n", "a004"],
"region": ["华东", "华东 ", "East China", "华南"],
"status": ["Paid", "已支付", "付款完成", "pending"],
"product": [" iPhone 15 ", "iPhone-15", "iphone 15", "苹果手机15"],
"phone": ["138-0000-0000", "13800000001", " 13800000002 ", "invalid"],
}
)
2. 转换为字符串类型
python
text_columns = ["order_id", "region", "status", "product", "phone"]
for column in text_columns:
df[column] = df[column].astype("string")
使用 pandas 的 string 类型可以更明确地处理缺失值和字符串方法。
3. 基础清洗
python
def normalize_text(series: pd.Series) -> pd.Series:
return (
series.astype("string")
.str.strip()
.str.replace(r"\s+", " ", regex=True)
)
df["order_id_clean"] = normalize_text(df["order_id"]).str.upper()
df["region_clean"] = normalize_text(df["region"])
df["status_clean"] = normalize_text(df["status"]).str.lower()
df["product_clean"] = normalize_text(df["product"]).str.lower()
\s+ 可以把多个连续空白字符替换为一个普通空格,包括换行和制表符。
4. 分类值映射
python
status_mapping = {
"paid": "已支付",
"已支付": "已支付",
"付款完成": "已支付",
"pending": "待支付",
"待支付": "待支付",
"cancelled": "已取消",
"已取消": "已取消",
}
df["status_standard"] = df["status_clean"].map(status_mapping)
映射失败的记录会得到缺失值,需要单独检查。
5. 地区映射
python
region_mapping = {
"华东": "华东",
"east china": "华东",
"华南": "华南",
"south china": "华南",
}
df["region_standard"] = (
df["region_clean"]
.str.lower()
.map(region_mapping)
)
如果映射表较大,可以把它维护在 Excel、CSV 或数据库中,由数据清洗程序读取。
6. 清洗手机号
python
df["phone_clean"] = (
df["phone"]
.astype("string")
.str.strip()
.str.replace("-", "", regex=False)
.str.replace(" ", "", regex=False)
)
df["phone_valid"] = df["phone_clean"].str.match(r"^1\d{10}$")
print(df[["phone", "phone_clean", "phone_valid"]])
手机号通常不应作为数值处理,否则可能丢失前导零或格式信息。
7. 提取订单编号
python
df["order_number"] = (
df["order_id_clean"]
.str.extract(r"([A-Z]+[-]?\d+)", expand=False)
)
正则提取后要检查缺失结果,确认是否存在不符合格式的订单号。
8. 商品名称标准化
python
product_mapping = {
"iphone 15": "iPhone 15",
"iphone-15": "iPhone 15",
"苹果手机15": "iPhone 15",
}
df["product_standard"] = (
df["product_clean"]
.str.replace("-", " ", regex=False)
.map(product_mapping)
)
商品名称标准化常常需要业务字典或商品主数据支持,不能完全依赖字符串规则。
9. 未识别值报告
python
unknown_status = df.loc[
df["status_standard"].isna(),
["order_id", "status", "status_clean"],
]
unknown_region = df.loc[
df["region_standard"].isna(),
["order_id", "region", "region_clean"],
]
print(unknown_status)
print(unknown_region)
未识别值应进入人工复核或映射表维护流程。
10. 字段级清洗报告
python
report = pd.DataFrame(
[
{
"field": "status",
"unknown_count": int(df["status_standard"].isna().sum()),
"unique_raw": int(df["status"].nunique(dropna=True)),
"unique_standard": int(df["status_standard"].nunique(dropna=True)),
},
{
"field": "region",
"unknown_count": int(df["region_standard"].isna().sum()),
"unique_raw": int(df["region"].nunique(dropna=True)),
"unique_standard": int(df["region_standard"].nunique(dropna=True)),
},
]
)
print(report)
标准化后,唯一值数量通常会下降。如果下降过多,也可能说明规则过度合并。
五、常见问题与实践建议
1. 是否应该把所有文本都转小写?
不一定。状态、渠道等分类值可以统一大小写;姓名、品牌、产品型号等字段可能需要保留展示格式。可以使用一个字段做匹配,一个字段做展示。
2. 未识别值是否都归为"其他"?
不建议默认这样做。未识别值可能代表新业务类型或数据质量问题。可以先输出报告,确认后再决定是否归为"其他"。
3. 正则表达式越复杂越好吗?
不是。复杂正则很难维护,应优先使用简单、可解释的规则。复杂规则需要样例测试和注释。
4. 如何管理映射表?
小项目可以使用字典;稳定项目建议使用 CSV、Excel、数据库表或配置文件,并记录版本、维护人和更新时间。
5. 文本清洗会不会误伤数据?
会。比如去除所有空格可能让产品型号或地址失真。因此每条规则都要结合字段语义设计。
六、进阶思考
1. 建立标准字典
文本标准化的长期方案是建立主数据或标准字典,例如:
- 地区字典。
- 商品字典。
- 渠道字典。
- 状态枚举。
- 组织机构字典。
清洗程序应尽量引用标准字典,而不是在代码中堆规则。
2. 模糊匹配
当文本存在错别字或相似名称时,可以使用模糊匹配辅助人工修正。但模糊匹配不应自动覆盖高风险字段,应输出候选项和置信度。
3. 清洗规则测试
为映射规则准备测试样例:
python
def test_status_mapping() -> None:
source = pd.Series(["Paid", "已支付", "付款完成"])
result = source.astype("string").str.strip().str.lower().map(status_mapping)
assert result.tolist() == ["已支付", "已支付", "已支付"]
规则变化后,测试可以避免旧数据被错误处理。
4. 从字段清洗到数据治理
如果同一个字段在多个系统中长期不一致,单靠分析端清洗无法根治。应推动数据源系统统一字段规范和枚举约束。
结论
文本字段清洗的核心是标准化、映射和校验。pandas 的字符串方法可以处理空格、大小写、替换和正则提取,映射表可以把别名统一为标准分类,清洗报告可以帮助持续优化规则。
下一篇将进入数据筛选与排序,学习如何构建可靠的数据子集,并为后续分组统计和业务分析准备干净的数据范围。
参考资料
- pandas 字符串方法:https://pandas.pydata.org/docs/user_guide/text.html
- pandas
Series.str文档:https://pandas.pydata.org/docs/reference/series.html#string-handling - Python
re文档:https://docs.python.org/3/library/re.html