供应链数据预处理:数据清洗、缺失值处理、异常值检测、数据标准化与归一化
说实话,做了这么多年供应链数据分析,我最大的感触就是——数据预处理占了我60%以上的时间。你想想看,辛辛苦苦从ERP、WMS、TMS系统里扒出来的数据,直接扔进模型?那基本等于找死。脏数据、缺失值、异常点,哪个都能让你的模型翻车。
我记得刚入行那会儿,有个项目急着交差,我没做预处理就直接跑聚类分析。结果呢?把仓库里一个长期不动的呆滞品识别成了"高价值热销品"……嗯,从那以后,我再也不敢跳过预处理这一步了。
1. 数据清洗——先把"垃圾"倒掉
数据清洗说白了就是给数据"洗澡"。供应链数据里什么妖魔鬼怪都有:供应商把"深圳市"写成"深圳"、"Shenzhen"、"0755-xxxx"混在一起;物料编码大小写不统一;订单日期出现2月30号……
我个人习惯,拿到数据第一件事就是跑个df.info()和df.describe(),先看看整体面貌。然后重点处理三类问题:
- 重复数据:同一个采购订单号出现两次?多半是系统重复推送了。用
df.drop_duplicates()搞定。 - 格式不一致:日期格式统一成
YYYY-MM-DD,文本字段统一小写或大写。我在项目中遇到过供应商编码混用大小写导致关联失败的情况,排查了整整一下午…… - 逻辑错误:比如"发货日期早于下单日期"、"库存数量为负数"。这些需要结合业务规则来清洗。
# 数据清洗示例代码
import pandas as pd
# 加载供应链数据
df = pd.read_csv('supply_chain_data.csv')
# 1. 去除重复行
df = df.drop_duplicates(subset=['order_id'])
# 2. 统一日期格式
df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce')
# 3. 修正逻辑错误:发货日期不能早于下单日期
df = df[df['ship_date'] >= df['order_date']]
print(f"清洗后数据量: {len(df)} 条")
2. 缺失值处理——别让"空"毁了你的模型
供应链数据里缺失值太常见了。供应商评分缺失、运输时长缺失、质检结果缺失……你想想看,如果直接把这些空值扔进模型,大部分算法会直接报错。
处理缺失值,我一般按这个优先级来:
- 先看缺失比例:如果某个字段缺失超过70%,我建议直接删除该字段。留着也没啥用,反而增加噪声。
- 再看缺失模式:是随机缺失还是系统性缺失?比如"质检结果"只在某个供应商那里缺失,那可能是流程问题。
- 最后选填充方法:
| 缺失值类型 | 推荐方法 | 适用场景 |
|---|---|---|
| 数值型(如价格、数量) | 中位数填充 | 数据有偏态分布时 |
| 数值型(如温度、时间) | 均值填充 | 数据近似正态分布时 |
| 类别型(如供应商等级) | 众数填充 | 类别分布较集中时 |
| 时间序列(如日销量) | 前向/后向填充 | 数据有趋势性时 |
| 关键字段(如订单金额) | 模型预测填充 | 缺失值较少且重要时 |
# 缺失值处理示例
from sklearn.impute import SimpleImputer
import numpy as np
# 数值型字段:用中位数填充
num_imputer = SimpleImputer(strategy='median')
df['price'] = num_imputer.fit_transform(df[['price']])
# 类别型字段:用众数填充
cat_imputer = SimpleImputer(strategy='most_frequent')
df['supplier_grade'] = cat_imputer.fit_transform(df[['supplier_grade']])
# 时间序列:前向填充
df['daily_sales'] = df['daily_sales'].fillna(method='ffill')
3. 异常值检测——揪出那些"不听话"的数据
供应链数据里的异常值,有时候是录入错误(比如把1000输成了10000),有时候是真实事件(比如双十一销量暴增)。怎么区分?我一般用"业务+统计"双保险。
常用的检测方法:
- 3σ原则:数据服从正态分布时,超过均值±3倍标准差的值视为异常。适合销量、价格等连续变量。
- IQR箱线图法:不受分布假设限制,用四分位距来判定。适合偏态分布的数据,比如供应商交货周期。
- 业务规则法:比如"订单金额不能超过100万"、"库存周转天数不能为负"。这些规则来自实际业务经验。
# 异常值检测:IQR法
Q1 = df['delivery_days'].quantile(0.25)
Q3 = df['delivery_days'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df['is_outlier'] = (df['delivery_days'] < lower_bound) | (df['delivery_days'] > upper_bound)
# 查看异常值
print(df[df['is_outlier']][['order_id', 'delivery_days']].head())
4. 数据标准化与归一化——让所有特征"平起平坐"
你想想看,供应链数据里,"订单金额"可能是几万,"运输距离"可能是几百公里,"供应商评分"可能是0-100分。这些量纲完全不同的数据,直接扔进模型会怎样?
距离类算法(KNN、K-Means)会天然偏向数值大的特征。说白了,就是"订单金额"会主导模型,而"供应商评分"几乎没发挥作用。所以,标准化和归一化是必须的。
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Z-score标准化 | (x - μ) / σ | 无固定范围,均值为0 | 数据有异常值时,或需要保留分布形态时 |
| Min-Max归一化 | (x - min) / (max - min) | [0, 1] | 数据范围已知,且无极端异常值时 |
| Robust标准化 | (x - median) / IQR | 无固定范围 | 数据包含较多异常值时 |
# 标准化与归一化示例
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# Z-score标准化(适合大多数场景)
scaler_std = StandardScaler()
df['amount_std'] = scaler_std.fit_transform(df[['order_amount']])
# Min-Max归一化(适合神经网络等需要[0,1]输入的算法)
scaler_mm = MinMaxScaler()
df['distance_norm'] = scaler_mm.fit_transform(df[['transport_distance']])
# Robust标准化(适合有异常值的数据)
scaler_rob = RobustScaler()
df['score_rob'] = scaler_rob.fit_transform(df[['supplier_score']])
嗯,到这里,数据预处理的四个核心步骤就讲完了。你可能会问:"这些步骤有先后顺序吗?" 我个人习惯是:先清洗 → 再处理缺失值 → 然后检测异常值 → 最后标准化。因为标准化之前,你得确保数据是"干净"的,否则标准化会把脏数据也"标准化"进去,那就更麻烦了。
记住一句话:数据预处理做得好,模型训练省一半时间。别嫌麻烦,这一步值得你花心思。
公众号:蓝海资料掘金营,微信deep3321