在机器学习项目中,“数据预处理” 是决定模型效果的关键环节 —— 据统计,70% 以上的建模时间都消耗在数据清洗与预处理上。如果把建模比作 “烹饪”,原始数据就是 “食材”,预处理则是 “食材清洗与切割”,没有合格的预处理,再优秀的模型也无法发挥作用。

系统拆解机器学习中最核心的 4 大预处理任务:缺失值处理数据标准化特征编码数据二值化,结合 Pandas 与 Scikit-learn 工具,提供可直接复用的代码示例与原理解析,帮你彻底搞定数据预处理流程。

一、缺失值处理:让 “不完整” 的数据变可用

原始数据中常存在 “空值”(如NaNn/ana),若直接输入模型会导致报错或结果偏差。缺失值处理的核心是 “识别空值” 并 “合理填充 / 删除”,常用工具为 Pandas 与 Scikit-learn 的SimpleImputer

1. 第一步:识别缺失值(Pandas 篇)

首先需明确:Pandas 默认仅识别NaN为缺失值,但实际数据中可能存在n/ana-等 “自定义空值”,需先统一转换为NaN,再用isnull()判断。

场景 1:基础识别(仅处理NaN
import pandas as pd

# 读取数据(property-data.csv包含房产信息,如卧室数、面积等)
df = pd.read_csv('property-data.csv')

# 查看某一列(如卧室数NUM_BEDROOMS)的原始数据
print("NUM_BEDROOMS原始数据:")
print(df['NUM_BEDROOMS'])

# 判断每个单元格是否为空(True表示空值)
print("\nNUM_BEDROOMS空值判断:")
print(df['NUM_BEDROOMS'].isnull())

输出结果
原始数据中NaN会被直接识别为空值,但n/ana会被当作普通字符串(非空),导致判断不准确。

场景 2:自定义空值识别(关键!)

通过na_values参数,将n/ana-等统一转为NaN,确保空值无遗漏:

import pandas as pd

# 定义需要识别的自定义空值列表
missing_values = ["n/a", "na", "-"]

# 读取数据时,将自定义空值转为NaN
df = pd.read_csv('property-data.csv', na_values=missing_values)

# 再次查看卧室数列与空值判断结果
print("处理后NUM_BEDROOMS数据:")
print(df['NUM_BEDROOMS'])
print("\n处理后NUM_BEDROOMS空值判断:")
print(df['NUM_BEDROOMS'].isnull())

输出结果
原本的n/ana均转为NaNisnull()可准确识别所有空值(示例中索引 2、5、8 的单元格显示True)。

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

识别空值后,需根据数据量与业务场景选择 “删除” 或 “填充”,避免直接丢弃有价值的数据。

方案 1:删除空值(dropna ())

当空值占比极低(如 < 1%)时,可直接删除包含空值的行,语法如下:

# 语法格式:DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
# axis=0:删除含空值的行;how='any':有一个空值就删除;inplace=True:直接修改原数据

import pandas as pd
missing_values = ["n/a", "na", "-"]
df = pd.read_csv('property-data.csv', na_values=missing_values)

# 删除所有包含空值的行,生成新数据集
new_df = df.dropna()
print("删除空值后的数据集:")
print(new_df)

关键参数说明

参数作用
how='all'仅删除 “所有单元格均为空” 的行(适合多列空值场景)
thresh=3保留 “至少 3 个非空值” 的行(避免删除过多有效数据)
subset=['NUM_BEDROOMS', 'SQ_FT']仅检查指定列(卧室数、面积)的空值,其他列空值不影响删除判断
方案 2:填充空值(fillna () + SimpleImputer)

当空值占比较高(如 5%-20%)时,用 “均值、中位数、常数” 等填充更合理。Pandas 的fillna()适合简单场景,Scikit-learn 的SimpleImputer支持更标准化的填充(适配建模流程)。

(1)Pandas 填充:快速上手
import pandas as pd
missing_values = ["n/a", "na", "-"]
df = pd.read_csv('property-data.csv', na_values=missing_values)

# 1. 用常数填充(如用666填充所有空值)
df.fillna(666, inplace=True)
print("常数填充后的数据:")
print(df[['PID', 'NUM_BEDROOMS', 'SQ_FT']])  # 仅展示关键列

# 2. 用均值填充(适合数值型列,如门牌号ST_NUM)
df = pd.read_csv('property-data.csv', na_values=missing_values)  # 重新读取数据
mean_st_num = df["ST_NUM"].mean()  # 计算门牌号的均值
df["ST_NUM"].fillna(mean_st_num, inplace=True)
print(f"\n门牌号均值:{mean_st_num:.2f}")
print("均值填充后门牌号列:")
print(df['ST_NUM'])

# 3. 用中位数填充(适合含异常值的列,如面积SQ_FT)
median_sq_ft = df["SQ_FT"].median()  # 计算面积的中位数
df["SQ_FT"].fillna(median_sq_ft, inplace=True)
print(f"\n面积中位数:{median_sq_ft}")
print("中位数填充后面积列:")
print(df['SQ_FT'])
(2)Scikit-learn 填充:标准化建模适配

SimpleImputer是 Scikit-learn 专为缺失值设计的工具,支持均值、中位数、众数、常数填充,输出格式为 NumPy 数组(可直接输入模型)。

import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer

# 1. 均值填充(以年龄数据为例)
# 模拟包含缺失值的年龄数据
age = np.array([22, 38, 26, 35, 35, np.nan, 54, 2, 27, 14], dtype=float)

# 初始化SimpleImputer:缺失值为np.nan,填充策略为均值
imp_mean = SimpleImputer(missing_values=np.nan, strategy="mean")

# 拟合并转换数据(reshape(-1,1):将1维数组转为2维,符合Sklearn输入要求)
imp_mean_result = imp_mean.fit_transform(age.reshape(-1, 1))

print("均值填充后前10条年龄数据:")
print(imp_mean_result[:10])  # 输出:缺失值被替换为均值29.699...

# 2. 常数填充(用0填充缺失值)
imp_0 = SimpleImputer(strategy="constant", fill_value=0)
imp_0_result = imp_0.fit_transform(age.reshape(-1, 1))
print("\n常数(0)填充后前10条年龄数据:")
print(imp_0_result[:10])  # 输出:缺失值被替换为0

# 3. 众数填充(适合分类变量,如登船港口Embarked)
# 模拟包含缺失值的登船港口数据
data = pd.DataFrame({
    "Embarked": ["S", "C", "S", "S", np.nan, "C", "Q", np.nan, "S"],
    "Age": [22, 38, 26, 35, np.nan, 54, 2, 27, 14]
})

# 提取登船港口列并转为2维数组
embarked = data.loc[:, "Embarked"].values.reshape(-1, 1)

# 初始化SimpleImputer:填充策略为众数(most_frequent)
imp_mode = SimpleImputer(strategy="most_frequent")

# 拟合并转换数据,赋值回原DataFrame
data.loc[:, "Embarked"] = imp_mode.fit_transform(embarked)

print("\n众数填充后登船港口数据:")
print(data[['Embarked', 'Age']])  # 输出:缺失值被替换为众数"S"

二、数据标准化:消除 “量纲” 影响,让模型更公平

不同特征的 “量纲” 差异会严重干扰模型(如 “年龄” 范围 2-100,“收入” 范围 1000-100000,收入会主导模型学习)。标准化的核心是 “无量纲化”,将特征转换到同一分布或范围,常见方法有最大最小值标准化Z 值标准化

1. 核心概念:为什么需要标准化?

  • 量纲问题:如 “身高(cm)” 和 “体重(kg)”,数值范围差异大,模型会误将 “大数值特征” 当作 “重要特征”;
  • 分布问题:部分模型(如 SVM、逻辑回归)假设数据符合正态分布,标准化可让数据更接近该假设,提升模型效果;
  • 定义:将数据调整为 “均值 0、方差 1”(Z 值标准化)或 “指定范围(如 5-10)”(最大最小值标准化),实现 “同趋化、可比化”。

2. 方法 1:最大最小值标准化(MinMaxScaler)

将数据压缩到[0,1]或指定范围(如[5,10]),公式为:
xscaled​=xmax​−xmin​x−xmin​​×(max_range−min_range)+min_range

代码示例:
from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 1. 基础用法:默认压缩到[0,1]
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]  # 原始数据(2个特征)
scaler = MinMaxScaler()  # 初始化标准化器
result = scaler.fit_transform(data)  # 拟合并转换

print("默认[0,1]范围标准化结果:")
print(result)
# 输出:[[0.  , 0.  ], [0.25, 0.25], [0.5 , 0.5 ], [1.  , 1.  ]]

# 2. 自定义范围:压缩到[5,10]
scaler_custom = MinMaxScaler(feature_range=[5, 10])
result_custom = scaler_custom.fit_transform(data)

print("\n自定义[5,10]范围标准化结果:")
print(result_custom)
# 输出:[[ 5.  ,  5.  ], [ 6.25,  6.25], [ 7.5 ,  7.5 ], [10.  , 10.  ]]

3. 方法 2:Z 值标准化(StandardScaler)

将数据转换为 “均值 0、方差 1” 的标准正态分布,公式为:
xscaled​=σx−μ​
(μ为特征均值,σ为特征标准差)

代码示例:
from sklearn.preprocessing import StandardScaler
import numpy as np

data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]

# 初始化Z值标准化器
scaler = StandardScaler()

# 拟合并转换数据
x_std = scaler.fit_transform(data)

# 验证结果:均值接近0,方差接近1
print("Z值标准化结果:")
print(x_std)
print(f"\n标准化后各特征均值:{x_std.mean(axis=0)}")  # 输出:[0. 0.](浮点数接近0)
print(f"标准化后各特征方差:{x_std.std(axis=0)}")    # 输出:[1. 1.]
适用场景对比:
标准化方法核心优势适用场景
MinMaxScaler保留数据原始分布,范围可控对数据范围有明确要求(如神经网络输入层)
StandardScaler消除均值与方差影响,鲁棒性强模型假设数据正态分布(如 SVM、逻辑回归)

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

机器学习模型仅能处理数值型数据,但原始数据常包含 “分类特征”(如性别 “男 / 女”、学历 “小学 / 初中 / 高中”)。特征编码的核心是 “将分类数据转为数值”,需根据特征类型选择不同方法。

1. 先明确:分类特征的 3 种类型

在编码前,需先判断分类特征的性质,避免错误编码:

  • 名义变量:无顺序关系(如性别 “男 / 女”、血型 “A/B/AB/O”);
  • 有序变量:有明确顺序(如学历 “小学 < 初中 < 高中”、成绩 “低 < 中 < 高”);
  • 有距变量:数值可计算(如分数 “100/90/60”,无需编码,直接使用)。

2. 编码方法全解析(3 种核心方法)

方法 1:独热编码(OneHotEncoder)—— 适配名义变量

对 “无顺序” 的名义变量,用 “N 位二进制向量” 表示 N 个类别(如血型 4 个类别→4 维向量),避免模型误判 “类别顺序”。

代码示例:
from sklearn.preprocessing import OneHotEncoder
import numpy as np

# 原始名义变量:血型(A、B、AB、O)
blood_type = np.array([["A"], ["B"], ["AB"], ["O"], ["A"], ["O"]])

# 初始化独热编码器(sparse_output=False:输出稠密数组,便于查看)
encoder = OneHotEncoder(sparse_output=False)

# 拟合并转换数据
onehot_result = encoder.fit_transform(blood_type)

# 查看编码结果与类别对应关系
print("独热编码结果:")
print(onehot_result)
print(f"\n类别对应关系:{encoder.categories_}")  # 输出:[['A', 'AB', 'B', 'O']]

输出结果

  • A 型 → [1. 0. 0. 0.]
  • B 型 → [0. 0. 1. 0.]
  • AB 型 → [0. 1. 0. 0.]
  • O 型 → [0. 0. 0. 1.]
方法 2:序号编码(OrdinalEncoder)—— 适配有序变量

对 “有顺序” 的变量,用整数表示类别(如成绩 “低→1、中→2、高→3”),保留顺序关系。

代码示例:
from sklearn.preprocessing import OrdinalEncoder
import numpy as np

# 原始有序变量:成绩(低、中、高)
grade = np.array([["低"], ["中"], ["高"], ["中"], ["低"], ["高"]])

# 初始化序号编码器,指定类别顺序(必须与实际顺序一致)
encoder = OrdinalEncoder(categories=[["低", "中", "高"]])

# 拟合并转换数据
ordinal_result = encoder.fit_transform(grade)

print("序号编码结果:")
print(ordinal_result)  # 输出:[[0.], [1.], [2.], [1.], [0.], [2.]]
方法 3:目标标签编码(LabelEncoder)—— 适配目标变量

专门用于 “目标变量(y)” 的编码(如分类任务的 “存活 / 死亡”),不能用于输入特征(X),否则会引入顺序偏差。

代码示例:
from sklearn.preprocessing import LabelEncoder
import pandas as pd

# 原始目标变量:存活状态(Yes/No)
data = pd.DataFrame({
    "Survived": ["No", "Yes", "Yes", "Yes", "No", "Yes"],
    "Age": [22, 38, 26, 35, 54, 27]
})

# 初始化标签编码器
encoder = LabelEncoder()

# 对目标变量编码(仅处理y,不处理X)
data["Survived_encoded"] = encoder.fit_transform(data["Survived"])

print("目标标签编码结果:")
print(data[["Survived", "Sur
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐