3. 数据清洗与预处理:处理缺失值、去除重复项、统一单位与货币、处理异常价格数据
采购支出分析中,原始数据通常来自ERP、SRM、财务系统等多个异构源,脏数据比例可达15%-30%。数据清洗与预处理是确保节约跟踪准确性的基石——"垃圾进,垃圾出"在采购分析中尤为致命。本节将系统讲解四大核心清洗任务:缺失值处理、重复项去除、单位与货币统一、异常价格识别与修正。
3.1 缺失值处理策略
缺失值在采购数据中常见于物料描述、供应商名称、单价、交货日期等字段。处理策略需根据缺失率与字段重要性选择:
| 缺失率 | 字段类型 | 推荐策略 | 适用场景 |
|---|---|---|---|
| < 5% | 数值型(单价、数量) | 中位数填充 | 价格波动较小的大宗物料 |
| 5% - 20% | 分类型(供应商、物料组) | 众数填充或模型预测 | 供应商集中度高的品类 |
| 20% - 50% | 关键字段(PO编号、物料号) | 关联回填(跨系统匹配) | ERP与SRM系统间数据桥接 |
| > 50% | 非关键描述字段 | 标记为"未知"或删除字段 | 不影响核心分析的辅助信息 |
实战建议:对于单价缺失,优先使用同一物料最近3笔采购订单的加权平均价填充,而非全局中位数。这能保留价格趋势信息,避免节约计算偏差。
3.2 重复项识别与去重
重复数据通常由系统间重复导入、人工录入错误或同一采购单多次记账引起。去重需区分"完全重复"与"业务重复":
- 完全重复:所有字段完全一致,直接删除保留一条。
- 业务重复:PO编号相同但行号不同(正常拆分);或PO编号不同但供应商、物料、数量、单价完全一致(疑似重复下单)。
- 时间窗口重复:同一供应商+同一物料+同一单价,在7天内出现多次,需与业务确认是否为分批交货。
去重规则示例(Python伪代码):
# 基于关键字段的精确去重
df_dedup = df.drop_duplicates(subset=['PO编号', '行号', '物料号', '供应商代码'])
# 基于相似度的模糊去重(适用于描述字段)
from fuzzywuzzy import fuzz
df['物料描述_标准化'] = df['物料描述'].apply(lambda x: standardize_desc(x))
df_dedup_fuzzy = df.groupby('物料号').apply(
lambda g: g[~g['物料描述_标准化'].duplicated()]
)
3.3 单位与货币统一
跨国采购数据中,单位(千克/磅、个/打、米/码)与货币(USD/EUR/CNY)的不统一是节约跟踪的最大陷阱。统一流程分为三步:
- 单位标准化:建立单位转换映射表,将所有采购数量转换为标准计量单位(如统一为千克、个、米)。
- 货币统一:按采购订单日期的即期汇率或月度平均汇率,将所有金额转换为分析基准货币(通常为集团报告货币)。
- 价格归一化:计算"标准单位价格" = 转换后金额 / 转换后数量,用于跨供应商、跨地区的价格对比。
| 原始数据 | 单位转换 | 货币转换 | 归一化结果 |
|---|---|---|---|
| 500 lb, $1,250 USD | 500 lb → 226.8 kg | $1,250 USD → ¥8,750 CNY | ¥38.58 / kg |
| 200 kg, €1,000 EUR | 200 kg → 200 kg | €1,000 EUR → ¥7,800 CNY | ¥39.00 / kg |
常见错误:直接使用采购订单上的"单价"字段进行对比,忽略单位差异。例如"个"与"打"的单价相差12倍,若不转换将导致节约计算完全失真。
3.4 异常价格数据识别与处理
异常价格是节约跟踪中最需要关注的数据点——它们可能代表真正的节约机会,也可能是数据错误。识别方法包括统计规则与业务规则:
- 统计规则:基于IQR(四分位距)或Z-Score识别离群值。通常将超出Q1-1.5×IQR或Q3+1.5×IQR的价格标记为异常。
- 业务规则:价格低于历史最低价的70%或高于历史最高价的130%;或同一物料在不同供应商间价格差异超过50%。
- 上下文规则:紧急采购、小批量采购、试制采购的价格通常偏高,需单独标记而非直接剔除。
异常价格处理流程:
# 步骤1:按物料分组计算统计边界
def flag_price_anomaly(group):
Q1 = group['单价'].quantile(0.25)
Q3 = group['单价'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
group['价格异常标记'] = (group['单价'] < lower) | (group['单价'] > upper)
return group
df = df.groupby('物料号').apply(flag_price_anomaly)
# 步骤2:业务规则二次校验
df['业务异常标记'] = (
(df['采购类型'] == '紧急') & (df['单价'] > df['物料均价'] * 1.5)
) | (
(df['采购数量'] < df['最小起订量']) & (df['单价'] > df['物料均价'] * 1.3)
)
3.5 清洗后的数据验证清单
完成清洗后,建议使用以下清单进行最终验证,确保数据可用于节约跟踪分析:
| 检查项 | 验收标准 | 验证方法 |
|---|---|---|
| 缺失率 | 关键字段缺失率 < 3% | 逐字段统计缺失比例 |
| 重复率 | 完全重复行 = 0 | 基于主键去重后对比行数 |
| 单位一致性 | 同一物料组单位统一 | 按物料组分组检查单位字段 |
| 货币一致性 | 所有金额字段为同一基准货币 | 检查货币字段唯一值 |
| 异常价格占比 | 标记异常 < 5% | 统计异常标记字段比例 |
效率提示:将上述清洗流程封装为可复用的Python函数或SQL存储过程,每次新数据接入时自动执行。建议在清洗前后分别保存数据快照,便于追溯和审计。