机器学习数据预处理全攻略:从缺失值到特征编码,一步搞定数据清洗
数据加载与探索:用 Pandas 读取数据,info()查看缺失值;缺失值处理:少量缺失→删除,大量缺失→填充(均值 / 中位数 / 众数);特征编码:有序变量→序号编码,名义变量→独热编码,目标变量→标签编码;数据标准化:树模型除外,其他模型→Z-Score/Min-Max 标准化;特征简化(可选):连续特征→二值化 / 离散化(
机器学习数据预处理全攻略:从缺失值到特征编码,一步搞定数据清洗
在机器学习流程中,“数据预处理” 往往占据 70% 以上的时间 —— 原始数据通常充满缺失值、格式混乱、特征维度不一,直接建模会导致结果偏差甚至失败。本文结合 Pandas 和 Scikit-learn 工具,系统讲解数据预处理的四大核心任务:缺失值处理、数据标准化、特征编码、数据二值化,帮你把 “脏数据” 变成建模可用的 “干净数据”。
一、缺失值处理:数据的 “第一块绊脚石”
原始数据中最常见的问题就是缺失值(如NaN、n/a、NA),比如房产数据中的 “卧室数量”“面积” 缺失。处理缺失值的核心思路是 “要么删,要么补”,具体方法需根据数据特点选择。
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. 标准预处理流程
- 数据加载与探索:用 Pandas 读取数据,
info()/isnull().sum()查看缺失值; - 缺失值处理:少量缺失→删除,大量缺失→填充(均值 / 中位数 / 众数);
- 特征编码:有序变量→序号编码,名义变量→独热编码,目标变量→标签编码;
- 数据标准化:树模型除外,其他模型→Z-Score/Min-Max 标准化;
- 特征简化(可选):连续特征→二值化 / 离散化(
KBinsDiscretizer)。
2. 核心工具清单
| 任务 | 工具(Pandas) | 工具(Scikit-learn) |
|---|---|---|
| 缺失值识别 | isnull()/notnull() |
- |
| 缺失值处理 | dropna()/fillna() |
SimpleImputer(均值 / 中位数 / 众数) |
| 标准化 | - | StandardScaler(Z-Score)、MinMaxScaler(归一化) |
| 特征编码 | map()(简单映射) |
OrdinalEncoder(序号)、OneHotEncoder(独热)、LabelEncoder(目标) |
| 数据二值化 | - | Binarizer |
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)