数据预处理基础:缺失值处理、异常值检测、数据标准化与归一化

数据预处理,说白了就是给原始数据「洗澡」。

我做了这么多年环境数据分析,最深的体会就是:数据质量决定了分析的天花板。你模型再牛,算法再花哨,喂进去一堆脏数据,出来的结果也是垃圾。

这一章,咱们就聊聊数据预处理的三个核心环节:缺失值、异常值、还有标准化归一化。嗯,都是基本功,但坑也不少。

核心观点:预处理不是可选项,是必选项。跳过这一步,后面全是白费功夫。

数据预处理 缺失值处理 删除法 填充法(均值/中位数/插值) 异常值检测 IQR四分位法 Z-Score法 标准化与归一化 Z-Score标准化 Min-Max归一化 目标:干净、一致、可比较的数据

一、缺失值处理:别让「空」毁了你的分析

环境数据里,缺失值太常见了。传感器故障、传输丢包、人为漏记……原因五花八门。

我个人习惯,拿到数据第一件事就是跑个 df.isnull().sum(),看看哪些列在「偷懒」。

1.1 缺失值类型

先搞清楚缺失的「性质」,再决定怎么处理:

  • 完全随机缺失(MCAR):缺失跟数据本身没关系,比如仪器偶尔抽风。这种情况最「安全」,直接删或简单填充都行。
  • 随机缺失(MAR):缺失跟其他变量有关。比如PM2.5监测仪在高温天更容易坏,那缺失就跟温度有关了。
  • 非随机缺失(MNAR):缺失跟它自己有关。比如浓度太高把传感器烧了,那高值数据就丢了——这最麻烦。

我的经验:实际项目中,大部分缺失属于MAR。我曾经处理过一批水质监测数据,发现溶解氧的缺失跟水温高度相关——水温超过30°C时传感器就罢工。这种情况,用温度做辅助变量来插值,效果比简单填充好得多。

1.2 常用处理方法

方法 适用场景 优点 缺点
直接删除 缺失比例 < 5%,且随机 简单粗暴 样本量减少,可能引入偏差
均值/中位数填充 数值型,缺失比例不高 计算快,不影响分布中心 会降低方差,扭曲相关性
前向/后向填充 时间序列数据 保留时序趋势 不适用于突变场景
插值法(线性/样条) 连续型,有规律变化 平滑自然 计算量稍大
模型预测填充 缺失比例高,变量间关系复杂 精度高 容易过拟合,耗时
# 我常用的缺失值处理代码
import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('environment_data.csv')

# 1. 查看缺失情况
print(df.isnull().sum())

# 2. 简单填充:用中位数(对异常值更鲁棒)
df['pm25'].fillna(df['pm25'].median(), inplace=True)

# 3. 时间序列用前向填充
df['temperature'].fillna(method='ffill', inplace=True)

# 4. 线性插值(适合连续变化的数据)
df['humidity'].interpolate(method='linear', inplace=True)

注意:千万别一股脑全用均值填充!我曾经见过一个项目,把风速的缺失值全填了平均值,结果原本的风速分布被压扁了,后续的风能评估完全失真。你想想看,风速是偏态分布,用均值填充等于把低频高风速事件全抹掉了。

二、异常值检测:揪出那些「不老实」的数据点

异常值,就是那些明显偏离正常范围的数据点。环境数据里,异常值可能是传感器故障、记录错误,也可能是真实的极端事件——比如突然的污染峰值。

我个人的原则是:先检测,再判断,最后处理。别一上来就删,万一那是重要的科学发现呢?

2.1 常用检测方法

方法一:IQR四分位法

这是我最喜欢的方法,简单、直观、不依赖数据分布。说白了,就是找出「离群太远」的点。

  • 计算Q1(25%分位数)和Q3(75%分位数)
  • IQR = Q3 - Q1
  • 正常范围:[Q1 - 1.5×IQR, Q3 + 1.5×IQR]
  • 超出这个范围的,标记为异常
# IQR异常值检测
Q1 = df['pm25'].quantile(0.25)
Q3 = df['pm25'].quantile(0.75)
IQR = Q3 - Q1

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

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

方法二:Z-Score法

适合数据近似正态分布的情况。Z-Score表示数据点偏离均值多少个标准差。通常 |Z| > 3 就认为是异常。

from scipy import stats

z_scores = np.abs(stats.zscore(df['pm25']))
outliers = df[z_scores > 3]
print(f"Z-Score法检测到 {len(outliers)} 个异常值")

避坑指南:我曾经处理某市的空气质量数据,用Z-Score法检测到一批「异常」高值。差点就删了,后来一查,原来是附近工厂夜间偷排导致的真实污染事件。所以啊,异常值不一定是「错误」,也可能是「信号」。处理前一定要结合业务背景判断。

2.2 异常值处理策略

  • 确认是错误:直接删除或修正(比如传感器故障导致的负值)
  • 确认是真实极端事件:保留,但单独标注
  • 不确定:用盖帽法(Winsorize),把异常值替换为边界值
# 盖帽法处理:将异常值替换为边界值
df['pm25'] = df['pm25'].clip(lower=lower_bound, upper=upper_bound)

三、数据标准化与归一化:让不同量纲的数据「对话」

环境数据里,变量单位五花八门:PM2.5是μg/m³,温度是°C,风速是m/s……数值范围差好几个数量级。如果不做标准化,那些数值大的变量就会在模型里「喧宾夺主」。

嗯,这里要注意:标准化和归一化不是一回事,别搞混了。

3.1 标准化(Z-Score标准化)

把数据变成均值为0、标准差为1的分布。公式很简单:z = (x - μ) / σ

适合数据本身有异常值,或者算法假设数据服从正态分布的场景(比如线性回归、SVM)。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = pd.DataFrame(
    scaler.fit_transform(df[['pm25', 'temperature', 'humidity']]),
    columns=['pm25', 'temperature', 'humidity']
)

3.2 归一化(Min-Max归一化)

把数据压缩到[0, 1]区间。公式:x_norm = (x - min) / (max - min)

适合数据有明确边界,或者算法对数值范围敏感的场景(比如神经网络、KNN)。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
    scaler.fit_transform(df[['pm25', 'temperature', 'humidity']]),
    columns=['pm25', 'temperature', 'humidity']
)
方法 输出范围 对异常值敏感度 适用算法
Z-Score标准化 无固定范围(通常-3~3) 中等 线性回归、SVM、PCA
Min-Max归一化 [0, 1] 高(异常值会压缩正常值) 神经网络、KNN、梯度下降类

重要提醒:标准化和归一化的参数(均值、标准差、最大最小值)一定要只用训练集计算,然后用同样的参数去转换测试集和验证集。我见过有人把整个数据集一起标准化,然后做训练测试划分——这属于数据泄露,会导致模型评估结果虚高,上线就翻车。

四、预处理流程总结

说了这么多,其实就三步:

  1. 先处理缺失值——别让空值干扰后续分析
  2. 再检测异常值——结合业务判断是删是留
  3. 最后标准化/归一化——让所有变量站在同一起跑线上

顺序别搞反了。你想想看,如果先标准化再处理缺失值,那些填充值会影响均值和标准差的计算,一步错步步错。

一句话总结:数据预处理不是「做完就行」,而是「做对才行」。每一步的选择都直接影响后续分析的质量。多花点时间在预处理上,后面建模会省心很多。


公众号:蓝海资料掘金营,微信deep3321