第四章:数据清洗实战——缺失值处理、异常值检测、重复数据去重、数据标准化

数据清洗,说白了就是给数据「洗澡」。

我做了这么多年芯片行业的数据挖掘,见过太多人一上来就建模,结果模型跑出来一塌糊涂。为什么?因为数据本身就有问题。你想想看,海关数据这种来源复杂的东西,脏数据是家常便饭。今天这一章,我就带你把这四个最关键的清洗步骤走一遍。

核心观点:数据清洗占整个数据挖掘工作量的60%-80%。别嫌烦,这一步做不好,后面全是白搭。

数据清洗四大核心步骤 缺失值处理 删除 / 填充 / 插值 异常值检测 IQR / Z-Score / 业务规则 重复数据去重 完全匹配 / 模糊匹配 数据标准化 Min-Max / Z-Score归一化 常见方法 • 均值/中位数填充 • 前向/后向填充 • 模型预测填充 检测方法 • 箱线图(IQR) • 3σ原则 • 业务阈值判断 去重策略 • 完全重复删除 • 关键字段去重 • 时间戳保留最新 标准化方法 • Min-Max归一化 • Z-Score标准化 • 小数定标标准化 目标:干净、一致、可用的高质量数据

4.1 缺失值处理——别让「空」坑了你

海关数据里,缺失值太常见了。比如某个芯片型号的「封装形式」字段是空的,或者「供应商国家」没填。我见过最夸张的一次,一个客户拿到的数据里,价格字段有40%都是空的。你说这能直接用吗?

处理缺失值,我个人习惯分三步走:

  1. 先诊断——看看缺失率有多高
  2. 再判断——这个字段重要吗?
  3. 最后动手——选合适的方法处理

来看看代码怎么写:

import pandas as pd
import numpy as np

# 加载海关数据
df = pd.read_csv('chip_import_data.csv')

# 第一步:查看缺失情况
print(df.isnull().sum())
print(df.isnull().sum() / len(df) * 100)  # 缺失百分比

# 第二步:根据情况处理
# 方案A:缺失率 < 5%,直接删除
df_clean = df.dropna(subset=['price', 'quantity'])

# 方案B:缺失率 5%-30%,用均值或中位数填充
df['price'].fillna(df['price'].median(), inplace=True)

# 方案C:时间序列数据,用前向填充
df['ship_date'].fillna(method='ffill', inplace=True)

# 方案D:分类数据,用众数填充
df['package_type'].fillna(df['package_type'].mode()[0], inplace=True)

我的经验:对于芯片价格这种字段,我一般用中位数而不是均值。为什么?因为价格分布往往有长尾,几个高端芯片能把均值拉得很高,中位数更稳健。我在一个项目中试过,用均值填充后模型偏差大了15%。

注意:千万别一上来就全删。如果某个字段缺失率超过50%,我建议你直接放弃这个字段,而不是硬填。填出来的数据比空着还危险。

4.2 异常值检测——揪出那些「不对劲」的数据

异常值,说白了就是数据里的「刺头」。比如一条记录显示某批芯片单价是0.01美元,或者数量是-1000个。你想想看,这合理吗?

我常用的方法有三种:

方法 适用场景 阈值 优缺点
IQR(四分位距) 偏态分布数据 Q1 - 1.5*IQR 到 Q3 + 1.5*IQR 不受极端值影响,稳健
Z-Score 近似正态分布 |Z| > 3 简单直观,但对小样本不友好
业务规则 有明确业务约束 自定义阈值 最可靠,但需要领域知识

实战代码来了:

# IQR方法检测异常值
Q1 = df['unit_price'].quantile(0.25)
Q3 = df['unit_price'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['unit_price'] < lower_bound) | (df['unit_price'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")

# Z-Score方法
from scipy import stats
z_scores = np.abs(stats.zscore(df['unit_price']))
outliers_z = df[z_scores > 3]

# 业务规则:芯片单价不可能低于0.1美元或高于10000美元
df_valid = df[(df['unit_price'] >= 0.1) & (df['unit_price'] <= 10000)]

关键提醒:异常值不一定是错误值。我曾经处理过一个案例,某批军用级芯片单价是普通芯片的20倍,统计上算异常,但业务上完全合理。所以,先问业务,再动手删

4.3 重复数据去重——别让同一批货算两次

海关数据里重复记录太常见了。同一个报关单号,可能因为系统重传出现两次;或者同一批货,不同关区都录入了。我见过最离谱的,一条记录重复了7次。

去重策略我一般分两种:

  • 完全重复:所有字段都一样,直接删
  • 部分重复:关键字段相同,但其他字段有差异,需要合并或保留最新
# 完全重复去重
df_dedup = df.drop_duplicates()
print(f"去重前:{len(df)} 条,去重后:{len(df_dedup)} 条")

# 基于关键字段去重(比如报关单号+芯片型号)
df_dedup_key = df.drop_duplicates(subset=['declaration_no', 'chip_model'], keep='last')

# 保留最新记录(按时间戳)
df_sorted = df.sort_values('record_date')
df_dedup_latest = df_sorted.drop_duplicates(subset=['declaration_no'], keep='last')

我的习惯:去重之前,先看看重复数据的比例。如果超过10%,我建议你回头检查一下数据源是不是有问题。我曾经遇到一个项目,重复率高达30%,后来发现是ETL脚本写错了,每天全量导入时没做增量判断。

4.4 数据标准化——让不同尺度的数据「对齐」

芯片海关数据里,字段的量纲千差万别。价格是美元,数量是个数,重量是千克。你想想看,如果不做标准化,模型里价格这个特征会主导一切,其他特征基本没贡献。

我常用的两种标准化方法:

方法 公式 输出范围 适用场景
Min-Max归一化 (x - min) / (max - min) [0, 1] 数据有明确边界,如价格
Z-Score标准化 (x - mean) / std 均值为0,标准差为1 数据近似正态分布
from sklearn.preprocessing import MinMaxScaler, StandardScaler

# Min-Max归一化
scaler_minmax = MinMaxScaler()
df['price_normalized'] = scaler_minmax.fit_transform(df[['unit_price']])

# Z-Score标准化
scaler_std = StandardScaler()
df['price_standardized'] = scaler_std.fit_transform(df[['unit_price']])

# 查看效果
print("原始价格范围:", df['unit_price'].min(), "-", df['unit_price'].max())
print("归一化后范围:", df['price_normalized'].min(), "-", df['price_normalized'].max())
print("标准化后均值:", df['price_standardized'].mean(), "标准差:", df['price_standardized'].std())

避坑指南:我曾经犯过一个错误——先做了标准化,然后才拆分训练集和测试集。结果测试集的信息「泄露」到了训练集里,模型评估虚高。正确的做法是:先拆分,再对训练集fit,然后用训练集的参数transform测试集

4.5 清洗流程串联——一条龙搞定

好了,四个步骤都讲完了。在实际项目中,我不会一个一个单独做,而是写一个清洗流水线:

def clean_chip_data(df):
    """芯片海关数据清洗流水线"""
    # 1. 缺失值处理
    df = df.dropna(subset=['declaration_no', 'chip_model'])  # 关键字段不能空
    df['unit_price'].fillna(df['unit_price'].median(), inplace=True)
    df['quantity'].fillna(df['quantity'].median(), inplace=True)
    
    # 2. 异常值检测(业务规则优先)
    df = df[(df['unit_price'] >= 0.1) & (df['unit_price'] <= 10000)]
    df = df[df['quantity'] > 0]
    
    # 3. 重复数据去重
    df = df.drop_duplicates(subset=['declaration_no', 'chip_model'], keep='last')
    
    # 4. 数据标准化
    scaler = MinMaxScaler()
    df[['unit_price', 'quantity', 'weight']] = scaler.fit_transform(
        df[['unit_price', 'quantity', 'weight']]
    )
    
    return df

# 执行清洗
df_clean = clean_chip_data(df)
print(f"清洗完成,剩余 {len(df_clean)} 条有效数据")

嗯,到这里,数据清洗这一关就算过了。你想想看,原始数据就像一块璞玉,清洗就是雕琢的过程。雕好了,后面建模、分析都顺风顺水;雕不好,再好的算法也白搭。

我个人觉得,数据清洗是最能体现一个数据工程师功力的地方。代码谁都会写,但能不能根据业务场景选对方法,这才是真本事。