机器学习数据预处理全攻略:从缺失值到特征编码,一步搞定数据清洗

在机器学习流程中,“数据预处理” 往往占据 70% 以上的时间 —— 原始数据通常充满缺失值、格式混乱、特征维度不一,直接建模会导致结果偏差甚至失败。本文结合 Pandas 和 Scikit-learn 工具,系统讲解数据预处理的四大核心任务:缺失值处理、数据标准化、特征编码、数据二值化,帮你把 “脏数据” 变成建模可用的 “干净数据”。

一、缺失值处理:数据的 “第一块绊脚石”

原始数据中最常见的问题就是缺失值(如NaNn/aNA),比如房产数据中的 “卧室数量”“面积” 缺失。处理缺失值的核心思路是 “要么删,要么补”,具体方法需根据数据特点选择。

1. 第一步:识别缺失值

首先要用工具检测缺失值,避免 “隐形缺失”(如n/a被识别为字符串而非缺失)。

(1)Pandas 识别缺失值
import pandas as pd
import numpy as np

# 读取数据,指定缺失值标识(避免n/a、na被当作正常字符串)
missing_values = ["n/a", "na", "-"]
df = pd.read_csv("property-data.csv", na_values=missing_values)

# 1. 查看某列缺失值情况
print("卧室数量列的缺失值:")
print(df["NUM_BEDROOMS"])  # 缺失值显示为NaN
print("\n是否为缺失值:")
print(df["NUM_BEDROOMS"].isnull())  # True表示缺失

# 2. 查看整体缺失值统计
print("\n各列缺失值数量:")
print(df.isnull().sum())

2. 第二步:处理缺失值(4 种核心方法)

根据数据量和缺失比例,选择不同的处理策略:

(1)删除法(dropna):简单粗暴,适合缺失少的情况

当缺失值占比极低(如 < 5%),且删除后不影响数据分布时,直接删除含缺失值的行 / 列。

# 删除所有含缺失值的行(默认axis=0,how='any':有一个缺失就删)
new_df = df.dropna()
print("删除缺失值后的行数:", new_df.shape[0])

# 进阶:仅删除“卧室数量”列缺失的行(subset指定列)
new_df = df.dropna(subset=["NUM_BEDROOMS"], how="any")
  • 参数说明
    • axis=0:删除行(默认);axis=1:删除列(谨慎使用,可能丢失关键特征);
    • how='all':仅删除所有值都缺失的行 / 列;
    • inplace=True:直接修改原 DataFrame,无需返回新对象。
(2)固定值填充(fillna):适合离散型特征或无规律缺失

用指定常数(如 “未知”“0”)填充缺失值,适合缺失值无明显规律的场景(如 “业主是否自住” 缺失)。

# 用666填充所有缺失值(连续型特征慎用,可能引入偏差)
df.fillna(666, inplace=True)

# 针对性填充:卧室数量缺失用0,面积缺失用“未知”
df["NUM_BEDROOMS"].fillna(0, inplace=True)
df["SQ_FT"].fillna("未知", inplace=True)
(3)统计值填充:适合连续型特征(均值 / 中位数)

用列的均值(对称分布)或中位数(偏态分布、有异常值)填充,保留数据整体趋势。

# 1. 均值填充(如“门牌号ST_NUM”,假设分布对称)
mean_st_num = df["ST_NUM"].mean()
df["ST_NUM"].fillna(mean_st_num, inplace=True)
print("门牌号均值:", mean_st_num)  # 输出:191.428...

# 2. 中位数填充(如“房价”,避免异常值影响)
median_price = df["PRICE"].median()
df["PRICE"].fillna(median_price, inplace=True)
(4)Scikit-learn 填充(SimpleImputer):标准化填充工具

适合机器学习流水线,支持均值、中位数、众数、固定值四种策略,且能处理二维数组(符合建模输入格式)。

from sklearn.impute import SimpleImputer

# 1. 均值填充(连续型特征,如年龄)
age = df["AGE"].values.reshape(-1, 1)  # 转为二维数组(sklearn要求)
imp_mean = SimpleImputer(missing_values=np.nan, strategy="mean")
df["AGE"] = imp_mean.fit_transform(age)

# 2. 众数填充(离散型特征,如 embark港口)
embarked = df["EMBARKED"].values.reshape(-1, 1)
imp_mode = SimpleImputer(strategy="most_frequent")  # 众数
df["EMBARKED"] = imp_mode.fit_transform(embarked)

# 3. 固定值填充(如用0填充缺失的“子女数量”)
children = df["CHILDREN"].values.reshape(-1, 1)
imp_const = SimpleImputer(strategy="constant", fill_value=0)
df["CHILDREN"] = imp_const.fit_transform(children)

二、数据标准化:消除特征 “量级偏见”

原始数据中,特征的量级差异会严重影响模型(如 “身高(cm)” 取值 150-200,“体重(kg)” 取值 40-100,模型会偏向大数值特征)。标准化的核心是 “无量纲化”,将特征转换到同一尺度。

1. 为什么需要标准化?

  • 模型依赖距离计算(如 KNN、SVM):量级大的特征会主导距离,导致模型误判;
  • 梯度下降类模型(如线性回归、神经网络):标准化能加速收敛,提高训练效率;
  • 特征对比:标准化后可直接比较不同特征的重要性(如 “身高” 和 “体重” 对 BMI 的影响)。

2. 两种常用标准化方法

(1)Min-Max 标准化(归一化):缩放到指定范围

将特征值映射到[a, b](默认[0,1]),公式: \(x_{scaled} = \frac{x - min(x)}{max(x) - min(x)} \times (b - a) + a\) 适合需要固定范围的场景(如图像像素值 0-255)。

from sklearn.preprocessing import MinMaxScaler

# 原始数据(如“收入”和“支出”)
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]

# 1. 默认缩放到[0,1]
scaler_minmax = MinMaxScaler()
data_minmax = scaler_minmax.fit_transform(data)
print("默认Min-Max结果:")
print(data_minmax)  # 输出:[[0. 0.] [0.25 0.25] [0.5 0.5] [1. 1.]]

# 2. 自定义缩放到[5,10]
scaler_custom = MinMaxScaler(feature_range=[5, 10])
data_custom = scaler_custom.fit_transform(data)
print("\n自定义范围结果:")
print(data_custom)  # 输出:[[5. 5.] [6.25 6.25] [7.5 7.5] [10. 10.]]
(2)Z-Score 标准化(标准正态分布):均值 0,方差 1

将特征转换为标准正态分布,公式: \(x_{scaled} = \frac{x - \mu}{\sigma}\) 其中\(\mu\)是均值,\(\sigma\)是标准差。适合大多数机器学习模型(如线性回归、随机森林),且能保留数据的分布特征。

from sklearn.preprocessing import StandardScaler

# 原始数据
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]

# Z-Score标准化
scaler_std = StandardScaler()
data_std = scaler_std.fit_transform(data)

# 验证结果:均值≈0,方差≈1
print("标准化后均值:", data_std.mean(axis=0))  # 输出:[0. 0.]
print("标准化后方差:", data_std.std(axis=0))   # 输出:[1. 1.]

3. 标准化注意事项

  • 训练集和测试集一致性:必须用训练集的均值 / 标准差拟合 scaler,再用同一 scaler 转换测试集(避免数据泄露);
  • 异常值影响:Min-Max 对异常值敏感(如收入中出现 1 亿,会压缩其他值到极小范围),此时用RobustScaler(基于分位数,抗异常值);
  • 无需标准化的场景:树模型(决策树、随机森林)不依赖距离计算,可跳过标准化。

三、特征编码:让模型 “读懂” 分类特征

机器学习模型只能处理数值型数据,而原始数据中常包含分类特征(如 “性别 = 男 / 女”“学历 = 小学 / 初中 / 高中”),需要将其转换为数值 —— 这就是特征编码。根据分类特征的类型,编码方法分为 3 种。

1. 分类特征的 3 种类型

  • 名义变量:无顺序关系(如性别:男 / 女,血型:A/B/AB/O);
  • 有序变量:有明确顺序(如学历:小学 < 初中 < 高中 < 大学);
  • 有距变量:可计算差值(如分数:90 分比 80 分多 10 分,无需编码)。

2. 3 种核心编码方法

(1)序号编码(Ordinal Encoding):适合有序变量

将有序类别映射为连续整数(如小学 = 1,初中 = 2,高中 = 3),保留顺序关系。

from sklearn.preprocessing import OrdinalEncoder

# 原始有序特征(学历)
education = [["小学"], ["初中"], ["高中"], ["大学"], ["初中"]]

# 序号编码
encoder_ordinal = OrdinalEncoder(categories=[["小学", "初中", "高中", "大学"]])
education_encoded = encoder_ordinal.fit_transform(education)
print("序号编码结果:")
print(education_encoded)  # 输出:[[0.] [1.] [2.] [3.] [1.]]
  • 关键参数categories指定类别顺序(避免模型自动按字母排序)。
(2)独热编码(One-Hot Encoding):适合名义变量

将名义变量的每个类别转为一个二进制特征(如性别:男→[1,0],女→[0,1]),避免模型误解 “顺序关系”(如男 = 0,女 = 1 会让模型认为女 > 男)。

from sklearn.preprocessing import OneHotEncoder

# 原始名义特征(血型)
blood_type = [["A"], ["B"], ["AB"], ["O"], ["A"]]

# 独热编码(drop='first'避免多重共线性)
encoder_onehot = OneHotEncoder(sparse_output=False, drop="first")
blood_encoded = encoder_onehot.fit_transform(blood_type)
print("独热编码结果:")
print(blood_encoded)  # 输出:[[0. 0. 0.] [1. 0. 0.] [0. 1. 0.] [0. 0. 1.] [0. 0. 0.]]
  • 优势:无顺序偏见,适合类别数少的名义变量;
  • 缺点:类别数多(如省份 34 个)会导致 “维度爆炸”,此时用Embedding(深度学习)或Target Encoding
(3)目标标签编码(Label Encoding):适合目标变量

仅用于目标变量(如分类任务的 “是否患病 = 0/1”),将类别映射为 0 到 n-1 的整数,不适合输入特征(会引入顺序偏见)。

from sklearn.preprocessing import LabelEncoder

# 原始目标变量(是否患病)
target = ["是", "否", "是", "是", "否"]

# 标签编码
encoder_label = LabelEncoder()
target_encoded = encoder_label.fit_transform(target)
print("标签编码结果:")
print(target_encoded)  # 输出:[1 0 1 1 0]

# 反向解码(查看原始标签)
print("反向解码:")
print(encoder_label.inverse_transform(target_encoded))  # 输出:['是' '否' '是' '是' '否']

四、数据二值化:将连续特征转为 “0/1”

二值化是将连续特征按 “阈值” 分为两类(0 或 1),适合需要简化特征的场景(如 “成年 = 1(年龄≥18),未成年 = 0(年龄 < 18)”)。

from sklearn.preprocessing import Binarizer

# 原始连续特征(年龄)
age = [[22], [38], [16], [26], [14]]

# 二值化(阈值=18:≥18为1,<18为0)
binarizer = Binarizer(threshold=18)
age_binary = binarizer.fit_transform(age)
print("年龄二值化结果:")
print(age_binary)  # 输出:[[1] [1] [0] [1] [0]]
  • 应用场景:图像分割(像素值 > 128 为前景 1,否则为背景 0)、简单规则判断(如收入是否达标)。

五、预处理流程总结与工具清单

1. 标准预处理流程

  1. 数据加载与探索:用 Pandas 读取数据,info()/isnull().sum()查看缺失值;
  2. 缺失值处理:少量缺失→删除,大量缺失→填充(均值 / 中位数 / 众数);
  3. 特征编码:有序变量→序号编码,名义变量→独热编码,目标变量→标签编码;
  4. 数据标准化:树模型除外,其他模型→Z-Score/Min-Max 标准化;
  5. 特征简化(可选):连续特征→二值化 / 离散化(KBinsDiscretizer)。

2. 核心工具清单

任务 工具(Pandas) 工具(Scikit-learn)
缺失值识别 isnull()/notnull() -
缺失值处理 dropna()/fillna() SimpleImputer(均值 / 中位数 / 众数)
标准化 - StandardScaler(Z-Score)、MinMaxScaler(归一化)
特征编码 map()(简单映射) OrdinalEncoder(序号)、OneHotEncoder(独热)、LabelEncoder(目标)
数据二值化 - Binarizer
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐