3.电商订单数据清洗:从脏数据到准确反映业务事实
#pandas数据清洗
第1章 为什么电商订单数据需要清洗
1.1 真实订单数据有多“脏”
电商订单数据的常见“脏数据”问题:
-
缺失值:订单金额为空、用户ID缺失
-
重复值:同一个订单号出现多次(系统重复导出)
-
异常值:金额为负数(退款订单)、金额超大(测试订单)
-
格式错误:日期是文本、金额带符号
数据清洗的目标不是“删除所有有问题数据”,而是“让数据准确反映业务事实”。
1.2 学习前的准备工作
步骤1:生成一份包含脏数据的测试文件
在Jupyter中运行以下代码,生成dirty_orders.csv:
import pandas as pd
import numpy as np
np.random.seed(42)
n = 1000
# 制造脏数据
df_dirty = pd.DataFrame({
'order_id': ['ORD' + str(i).zfill(6) for i in range(1, n+1)],
'user_id': np.random.randint(10000, 99999, n),
'amount': np.random.uniform(10, 2000, n).round(2),
'status': np.random.choice(['已支付', '已取消', '已完成'], n, p=[0.7, 0.1, 0.2]),
'order_date': pd.date_range('2025-01-01', periods=n, freq='H')
})
# 人为制造脏数据
# 1. 缺失值:10个订单的amount为空
df_dirty.loc[10:19, 'amount'] = np.nan
# 2. 缺失值:5个订单的order_id为空
df_dirty.loc[20:24, 'order_id'] = np.nan
# 3. 重复值:复制最后5行,造成重复订单号
df_dirty = pd.concat([df_dirty, df_dirty.tail(5)], ignore_index=True)
# 4. 异常值:金额为负数(模拟退款单未过滤)
df_dirty.loc[30:34, 'amount'] = -np.random.uniform(50, 500, 5)
# 5. 异常值:金额超大(测试订单)
df_dirty.loc[35:39, 'amount'] = np.random.uniform(10000, 50000, 5)
# 6. 日期格式错误:将部分日期转为文本
df_dirty.loc[40:49, 'order_date'] = df_dirty.loc[40:49, 'order_date'].dt.strftime('%Y%m%d')
df_dirty.to_csv('dirty_orders.csv', index=False)
print("已生成 dirty_orders.csv,共1005行(含5行重复)")
⚠️ 实操避坑提醒:数据清洗前,一定要备份原始数据。我当初有一次直接在原DataFrame上操作,删错了行,想恢复已经来不及。建议先复制:df_clean = df.copy(),所有清洗操作在副本上做。
📌 电商数据合规提示:清洗过程中如果涉及用户手机号、地址等敏感字段,不要直接打印或导出。只处理必要的分析字段。
第2章 缺失值处理
2.1 识别缺失值
电商场景:订单金额列有空白,需要找出哪些行缺失。
import pandas as pd
df = pd.read_csv('dirty_orders.csv')
print(df.isnull().sum())
输出:
order_id 5
user_id 0
amount 10
status 0
order_date 0
dtype: int64
更多方法:
-
df.isnull().sum() / len(df):计算缺失比例 -
df[df['amount'].isnull()]:查看缺失值的具体行
2.2 删除缺失值:dropna()
电商场景:订单号为空的行无法追踪,直接删除。
# 删除order_id为空的行
df_clean = df.dropna(subset=['order_id'])
print(f"删除后行数:{len(df_clean)}")
参数说明:
-
subset:指定检查哪些列,不写则检查所有列 -
how='any':只要有一个缺失就删除(默认) -
how='all':全部缺失才删除 -
thresh:至少有几个非空值才保留
2.3 填充缺失值:fillna()
电商场景:金额缺失但订单状态是“已取消”,金额应为0。
# 先筛选出已取消且金额缺失的行,填充0
df.loc[(df['status'] == '已取消') & (df['amount'].isnull()), 'amount'] = 0
# 剩余金额缺失的行,可以用平均值填充(谨慎使用)
mean_amount = df['amount'].mean()
df['amount'] = df['amount'].fillna(mean_amount)
常用填充策略:
-
数值列:中位数、均值、0
-
文本列:众数、‘未知’
-
时间列:前向填充
method='ffill'或后向填充method='bfill'
⚠️ 实操避坑提醒:不要盲目用均值填充。比如“已取消”的订单,金额缺失应该填0而不是平均值,否则会虚增GMV。要根据业务逻辑决定填充值。
第3章 重复值处理
3.1 识别重复值
电商场景:同一订单号出现多次,需要去重。
# 检查是否有重复订单号
print(df.duplicated(subset=['order_id']).sum())
# 查看重复的具体行
df[df.duplicated(subset=['order_id'], keep=False)].sort_values('order_id')
3.2 删除重复值:drop_duplicates()
# 按订单号去重,保留第一次出现
df_clean = df.drop_duplicates(subset=['order_id'], keep='first')
print(f"去重后行数:{len(df_clean)}")
参数说明:
-
subset:按哪些列判断重复 -
keep='first':保留第一条;'last'保留最后一次;False删除所有重复
电商场景:如果有多列组合判断重复(如订单号+商品ID),可以传入列表:
df.drop_duplicates(subset=['order_id', 'product_id'])
3.3 我的踩坑经历
有一次去重后,订单数比预期少了100单。我以为是系统重复导出,后来发现是Excel导出时,同一个订单号在不同行后面多了空格(“ORD001”和“ORD001 ”),Pandas认为是不同的。解决方案:先清理空格:
df['order_id'] = df['order_id'].str.strip()
第4章 异常值处理
4.1 识别异常值
电商场景:订单金额为负数(退款单)或超大(测试订单)。
# 查看金额的统计分布
print(df['amount'].describe())
# 找出金额为负的行
negative = df[df['amount'] < 0]
print(f"金额为负的订单数:{len(negative)}")
# 找出金额大于10000的行
too_high = df[df['amount'] > 10000]
print(f"金额超大的订单数:{len(too_high)}")
其他异常值识别方法:
-
箱线图IQR法则:小于Q1-1.5IQR或大于Q3+1.5IQR
-
业务规则:如金额不能超过商品单价×合理数量
4.2 处理异常值
场景一:金额为负数(退款订单)
如果订单状态是“已取消”或“退款中”,金额应改为0(已支付的不计入GMV)。
# 假设退款订单状态为'已取消',将金额改为0
df.loc[(df['amount'] < 0) & (df['status'] == '已取消'), 'amount'] = 0
场景二:金额超大(测试订单)
测试订单通常有特殊标识,可以直接删除。
# 删除金额大于10000且状态为异常的订单
df = df[~((df['amount'] > 10000) & (df['status'] == '测试'))]
# 或者直接删除所有金额>10000的行(根据业务判断)
df = df[df['amount'] <= 10000]
场景三:金额超出合理范围
用百分位法截断(保留1%-99%分位数)。
lower = df['amount'].quantile(0.01)
upper = df['amount'].quantile(0.99)
df = df[(df['amount'] >= lower) & (df['amount'] <= upper)]
⚠️ 实操避坑提醒:不要一刀切删除所有异常值。比如双11大促订单,金额超过10000可能是真实的,需要单独确认。建议先标记异常值,再逐类分析。
第5章 数据类型转换
5.1 转换数据类型:astype()
电商场景:用户ID被读成了整数,需要转为字符串。
# 查看当前类型
print(df['user_id'].dtype) # int64
# 转为字符串
df['user_id'] = df['user_id'].astype(str)
# 订单号确保是字符串
df['order_id'] = df['order_id'].astype(str)
5.2 日期转换:to_datetime()
电商场景:订单日期列混用了日期格式和文本格式。
# 尝试转换,无法转换的会变成NaT(Not a Time)
df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce')
# 查看哪些行转换失败
df[df['order_date'].isnull()]
参数说明:
-
errors='coerce':无效值转为NaT,而不是报错 -
format:指定日期格式,如format='%Y%m%d'可加快转换速度
5.3 金额列清洗:处理符号和文本
电商场景:金额列可能包含“¥”或“元”字。
# 方法1:用正则替换非数字字符
df['amount'] = df['amount'].astype(str).str.replace('¥', '').str.replace('元', '')
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')
我的踩坑经历:有一次用pd.to_datetime转换50万行日期,没加errors='coerce',结果遇到一个“2025-02-30”的脏数据,整个脚本报错中断。加上errors='coerce'后,坏数据变成NaT,不影响其他行,后续再单独处理。
第6章 综合实操案例:完整清洗流程
6.1 案例背景
拿到dirty_orders.csv,需要清洗成可用于分析的标准数据。清洗规则:
-
删除订单号为空的记录
-
按订单号去重,保留第一次出现
-
金额为负数的,如果是“已取消”订单改为0,否则标记待查
-
金额超过10000的订单,先标记(可能是测试订单)
-
将订单日期转为datetime格式
-
保存清洗后的数据
6.2 分步操作
步骤1:读取并备份
import pandas as pd
df = pd.read_csv('dirty_orders.csv')
df_original = df.copy() # 备份
print(f"原始行数:{len(df)}")
步骤2:删除订单号为空的记录
df = df.dropna(subset=['order_id'])
print(f"删除空订单号后:{len(df)}")
步骤3:按订单号去重
df = df.drop_duplicates(subset=['order_id'], keep='first')
print(f"去重后:{len(df)}")
步骤4:处理金额为负的异常值
# 将金额为负且状态为'已取消'的改为0
df.loc[(df['amount'] < 0) & (df['status'] == '已取消'), 'amount'] = 0
# 检查还有没有其他负值
if (df['amount'] < 0).any():
print("警告:仍有负金额订单,请人工复核")
df[df['amount'] < 0].to_csv('negative_amounts.csv', index=False)
步骤5:处理金额超大的异常值
# 标记超大的订单
df['is_abnormal'] = df['amount'] > 10000
print(f"超大订单数:{df['is_abnormal'].sum()}")
步骤6:日期格式转换
df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce')
# 检查转换失败的行
if df['order_date'].isnull().any():
print("部分日期转换失败,已转为NaT")
步骤7:保存清洗后的数据
df.to_csv('cleaned_orders.csv', index=False)
print("清洗完成,已保存到 cleaned_orders.csv")
6.3 清洗结果验证
# 验证各项指标
print(f"最终行数:{len(df)}")
print(f"金额范围:{df['amount'].min()} ~ {df['amount'].max()}")
print(f"日期范围:{df['order_date'].min()} ~ {df['order_date'].max()}")
print(f"缺失值情况:\n{df.isnull().sum()}")
第7章 本章踩坑清单与合规总结
7.1 新手常见踩坑
| 场景 | 错误操作 | 正确做法 |
|---|---|---|
| 缺失值 | 直接删除所有含空值的行 | 按列分析原因,业务逻辑填充 |
| 重复值 | 去重前不检查keep参数 | 确认保留第一次还是最后一次 |
| 异常值 | 一刀切删除 | 先标记,分类处理 |
| 日期转换 | 不设errors参数,报错中断 | 加errors='coerce' |
| 数据类型 | 转换前不确认原格式 | 先用dtype查看,再转换 |
7.2 电商数据合规提示
清洗过程中的数据保护:
-
不要在清洗日志中打印完整的用户ID、手机号
-
如果必须保存异常数据供复核,对敏感字段做脱敏处理
原始数据保留:
-
清洗前必须备份原始数据,清洗后保留清洗脚本(代码),方便追溯
-
不要在原文件上直接修改
异常值上报:
- 金额为负、超大等异常,可能是系统问题或人为错误,清洗后应向数据负责人反馈,从源头解决问题
第8章 结语
数据清洗占电商数据分析60%以上的时间。掌握Pandas的缺失值、重复值、异常值处理和数据类型转换,你就能高效地把脏数据变成干净的分析素材。
下一章我会讲「Pandas数据转换与聚合」,教你如何用groupby和pivot_table做多维度统计。
有问题的评论区留言,我看到会回复。
更多推荐




所有评论(0)