4、数据清洗入门:处理缺失值、去除重复记录、统一日期格式、处理异常字符
各位同行,欢迎来到数据清洗这一章。说实话,我见过太多人拿到海关数据就急着做分析,结果被脏数据坑得欲哭无泪。我自己就吃过这个亏——有一回分析某品牌芯片的进口趋势,发现某个季度数据突然暴涨,兴奋了半天,最后发现是日期格式混乱,把2023年1月的单子误标成了2024年。嗯,从那以后,我养成了一个习惯:数据不洗三遍,绝不上模型。
数据清洗,说白了就是给数据「搓澡」。海关数据从各个口岸、不同报关行汇总过来,格式五花八门,缺胳膊少腿是常态。今天我就把四个最核心的清洗动作拆开揉碎了讲给你听。
核心原则:清洗不是删数据,而是让数据「可用」。每删一条记录前,先问自己:这条数据真的没用吗?
4.1 缺失值处理:别急着删,先看看为什么缺
海关数据里,缺失值最常见的地方是「供应商名称」「HS编码」「成交金额」这几列。我个人习惯,拿到数据先跑一个 df.isnull().sum(),看看每列缺了多少。
但这里有个坑——缺失不等于无效。举个例子,有些小分销商在报关时「品牌」字段留空,不代表他没品牌,可能是报关员偷懒。我曾经处理过一批数据,发现「制造商」字段缺失率高达30%,差点一键删除了。后来一查,原来是某口岸的报关系统升级,老数据没迁移过来。最后我通过「型号+封装」反向匹配,补回了八成。
我的处理原则:
- 关键字段缺失(如HS编码、金额): 如果缺失率 < 5%,直接删除该行;如果 > 5%,尝试用同类产品的均值或中位数填充。
- 非关键字段缺失(如备注、联系人): 填充「未知」或「N/A」,保留记录。
- 时间字段缺失: 必须追查!没有日期的交易记录毫无价值。
# Python 示例:缺失值处理
import pandas as pd
import numpy as np
# 读取海关数据
df = pd.read_csv('customs_data.csv')
# 查看缺失情况
print(df.isnull().sum())
# 删除金额缺失的行(关键字段)
df = df.dropna(subset=['成交金额(USD)'])
# 用中位数填充单价缺失
df['单价(USD)'] = df['单价(USD)'].fillna(df['单价(USD)'].median())
# 非关键字段填充
df['备注'] = df['备注'].fillna('N/A')
4.2 去除重复记录:小心「假重复」
重复记录是海关数据的家常便饭。原因很多:同一批货被两个口岸重复申报、报关行系统重传、或者干脆就是人工录入时手抖了一下。
但这里有个容易翻车的地方——完全重复的记录好去,难的是「部分重复」。比如两行数据,供应商、型号、数量都一样,但「报关日期」差了一天。这是真重复还是分批出货?
我建议这样操作:
- 先做完全去重: 所有字段完全一致的,直接删掉,保留第一条。
- 再做关键字段去重: 针对「供应商+型号+数量+金额」这四个字段,如果完全一致,大概率是重复申报。保留日期最早的那条。
- 人工复核: 对于金额大、数量大的疑似重复记录,我建议拉出来人工看一眼。我曾经因为偷懒没复核,误删了一笔200万美元的订单,后来被老板骂惨了。
# 完全去重
df = df.drop_duplicates()
# 关键字段去重(保留最早日期)
df = df.sort_values('报关日期').drop_duplicates(
subset=['供应商名称', '型号', '数量', '成交金额'],
keep='first'
)
警告: 去重前一定要备份原始数据!我习惯在文件名上加个 _raw 后缀,比如 customs_data_raw.csv。万一去重逻辑错了,还能回滚。
4.3 统一日期格式:最容易被忽视的坑
海关数据的日期格式,简直是个万花筒。我见过这些写法:
2023-01-15(标准格式)2023/01/15(斜杠分隔)15-JAN-2023(英文缩写)20230115(纯数字)2023.01.15(点分隔)- 甚至还有
2023年1月15日(中文格式)
你想想看,如果这些格式混在一起,你按月份汇总时,系统会把「2023-01-15」和「2023/01/15」当成两回事。我刚开始做分析时就踩过这个坑,汇总出来的月度数据曲线跟锯齿一样,查了两天才发现是日期格式没统一。
我的做法是:一律转成 YYYY-MM-DD 格式,这是国际标准,也是大多数分析工具能自动识别的格式。
# 统一日期格式
df['报关日期'] = pd.to_datetime(df['报关日期'], errors='coerce')
df['报关日期'] = df['报关日期'].dt.strftime('%Y-%m-%d')
# 如果转换失败(errors='coerce' 会变成 NaT),单独拎出来检查
bad_dates = df[df['报关日期'].isna()]
print(f"无法解析的日期共 {len(bad_dates)} 条")
小技巧: 如果数据量很大(几十万行),建议先抽样100条看看日期格式分布。用 df['报关日期'].sample(100).value_counts() 快速摸清情况,再写统一的转换逻辑。
4.4 处理异常字符:那些你看不见的「脏东西」
异常字符是数据清洗里最磨人的环节。海关数据里常见的异常字符包括:
- 不可见字符: 比如制表符
\t、换行符\n、甚至全角空格(看着像空格,但ASCII码不同) - 乱码: 比如
(BOM头)、æ£å¼(编码错误) - 特殊符号: 比如供应商名称里带
®™★等符号 - 前后空格: 这个最常见,比如
" Texas Instruments ",肉眼看不出来,但去重时会被当成两个不同的供应商
我记得有一次,分析某分销商的出货记录,发现「型号」字段里有个型号叫 STM32F103C8T6\n,多了个换行符。结果这个型号在数据库里匹配不到任何价格信息,白白浪费了我半天时间。
我的清洗流程:
- 去除前后空格:
df['供应商名称'] = df['供应商名称'].str.strip() - 替换不可见字符: 用正则把
\t\n\r替换成空格 - 统一编码: 强制转成 UTF-8,遇到乱码行标记出来人工处理
- 过滤特殊符号: 只保留中文、英文、数字、短横线、斜杠,其他一律删除
import re
# 去除前后空格
df['供应商名称'] = df['供应商名称'].str.strip()
df['型号'] = df['型号'].str.strip()
# 替换不可见字符
df['型号'] = df['型号'].str.replace(r'[\t\n\r]+', ' ', regex=True)
# 只保留合法字符(中文、英文、数字、-、/、.)
def clean_text(text):
if pd.isna(text):
return text
# 保留中文、英文、数字、常见符号
return re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\-/\.\(\)]', '', text)
df['供应商名称'] = df['供应商名称'].apply(clean_text)
df['型号'] = df['型号'].apply(clean_text)
避坑指南: 我曾经在处理一批台湾海关数据时,发现「供应商名称」字段里混入了全角括号「(」和半角括号「(」。在Excel里看起来一模一样,但Python去重时认为它们是不同的字符。最后我统一转成半角字符才解决。所以,清洗完一定要做一次 df['字段'].unique() 检查,看看还有没有漏网之鱼。
4.5 清洗后的验证:别急着往下走
数据清洗完了,别急着做分析。我习惯做三件事验证:
| 验证项 | 检查内容 | 预期结果 |
|---|---|---|
| 缺失值检查 | 再次运行 df.isnull().sum() |
所有字段缺失值为0 |
| 重复记录检查 | 运行 df.duplicated().sum() |
结果为0 |
| 日期范围检查 | 查看 df['报关日期'].min() 和 .max() |
日期在合理范围内(比如没有2025年的数据) |
| 异常值检查 | 对金额字段做 df.describe() |
最大值、最小值在合理区间 |
嗯,数据清洗这一步虽然枯燥,但它是整个分析流程的基石。你想想看,如果地基没打好,后面盖的楼再漂亮也是危房。我自己现在做项目,清洗环节至少要占整个项目时间的40%。别嫌慢,慢就是快。