在机器学习、数据分析项目中,“数据预处理” 是决定模型效果的关键步骤 —— 原始数据往往存在量纲不一致、类别特征无序、数值范围差异大等问题,直接建模会导致模型收敛慢、预测精度低。而归一化(Min-Max)、标准化(Z-Score)、特征编码(One-Hot)作为数据预处理的三大核心技术,能有效解决这些问题,让数据更适配模型需求。

一、数据预处理基础认知:为什么需要归一化、标准化与特征编码?

在动手实操前,先明确核心问题:为什么要做这些处理?不同场景该选哪种技术?

1. 核心痛点:原始数据的 3 大问题

  • 量纲不一致:比如 “用户年龄(10-80 岁)” 和 “消费金额(100-10000 元)”,数值范围差异极大,会导致模型过度偏向数值大的特征;

  • 类别特征无序:比如 “用户性别(男 / 女)”“商品类别(服饰 / 家电 / 食品)”,这类非数值特征无法直接输入模型;

  • 数据分布不均:比如 “用户收入(大部分集中在 3000-8000 元,少数人收入 10 万 +)”,会影响模型对特征规律的学习。

2. 三大技术核心作用对比

技术类型 核心作用 解决的问题 典型适用模型
归一化(Min-Max) 将数值特征缩放到 [0,1] 或 [a,b] 区间 量纲不一致、数值范围差异大 神经网络、SVM、KNN(对距离敏感的模型)
标准化(Z-Score) 将数值特征转换为 “均值 = 0,标准差 = 1” 的正态分布 数据分布不均、量纲不一致 线性回归、逻辑回归、PCA(对数据分布有要求的模型)
特征编码(One-Hot) 将类别特征转换为二进制数值特征 非数值特征无法输入模型 大部分机器学习模型(除决策树、随机森林等少数可直接处理类别特征的模型)

二、归一化(Min-Max Scaling):缩放到指定区间

1. 核心原理

归一化(又称最小 - 最大缩放)的核心是 “线性变换”,将原始数值特征缩放到指定区间(默认 [0,1]),公式如下:

X\_scaled = (X - X\_min) / (X\_max - X\_min)
  • X:原始特征值

  • X_min:该特征的最小值

  • X_max:该特征的最大值

  • 若需缩放至 [a,b] 区间,公式调整为:X_scaled = a + (X - X_min) * (b - a) / (X_max - X_min)

2. 适用场景与优缺点

适用场景:

  • 模型对特征数值范围敏感(如神经网络的激活函数、SVM 的核函数、KNN 的距离计算);

  • 业务场景需要明确的数值区间(如用户活跃度评分需缩放到 0-100 分);

  • 数据分布无明显异常值(或已处理异常值)。

优缺点:

  • 优点:计算简单、数值范围明确、保留特征原始分布趋势;

  • 缺点:对异常值敏感(如收入数据中出现 100 万的极端值,会导致大部分数据被压缩到极小区间);依赖数据的最大值和最小值,泛化能力弱(测试集数据若超出训练集的 min/max 范围,缩放结果会失真)。

3. Python 实操步骤(以电商用户行为数据为例)

场景:电商平台用户数据,包含 “浏览时长(分钟)”“消费金额(元)” 两个特征,需归一化到 [0,1] 区间

步骤 1:数据准备(Pandas 读取数据)

import pandas as pd

import numpy as np

\# 构造示例数据(真实场景可替换为pd.read\_csv读取数据)

data = {

    "用户ID": \[1,2,3,4,5],

    "浏览时长": \[10, 25, 15, 30, 20],

    "消费金额": \[200, 800, 500, 1000, 600]

}

df = pd.DataFrame(data)

print("原始数据:")

print(df)

步骤 2:归一化处理(使用 Scikit-Learn 的 MinMaxScaler)

from sklearn.preprocessing import MinMaxScaler

\# 1. 选择需要归一化的特征(排除用户ID等非数值特征)

features = \["浏览时长", "消费金额"]

X = df\[features]

\# 2. 初始化MinMaxScaler(默认缩放至\[0,1],如需缩放至\[0,100]可设置feature\_range=(0,100))

scaler = MinMaxScaler(feature\_range=(0, 1))

\# 3. 拟合并转换数据(fit获取X的min/max,transform执行缩放)

X\_scaled = scaler.fit\_transform(X)

\# 4. 将缩放后的数据转换为DataFrame,合并回原数据

df\_scaled = pd.DataFrame(X\_scaled, columns=features)

df\_final = pd.concat(\[df\["用户ID"], df\_scaled], axis=1)

print("\n归一化后的数据(\[0,1]区间):")

print(df\_final)

步骤 3:结果解读

  • 浏览时长原始范围 [10,30],归一化后范围 [0,1]:10→0,30→1,25→0.75;

  • 消费金额原始范围 [200,1000],归一化后范围 [0,1]:200→0,1000→1,500→0.375;

  • 两个特征现在处于同一量纲,可直接输入 SVM、KNN 等模型。

步骤 4:异常值处理(避坑关键)

若数据中存在异常值(如消费金额出现 5000 元的极端值),需先处理异常值再归一化:

\# 模拟含异常值的数据

df\["消费金额"]\[5] = 5000  # 新增用户6,消费金额5000(异常值)

\# 处理异常值:使用中位数替换(或四分位距法剔除)

df\["消费金额"] = df\["消费金额"].replace(5000, df\["消费金额"].median())

\# 再执行归一化(同上步骤2)

三、标准化(Z-Score Standardization):转换为正态分布

1. 核心原理

标准化(又称 Z 分数标准化)的核心是 “基于数据分布” 的转换,将原始数值特征转换为均值(μ)=0、标准差(σ)=1 的正态分布,公式如下:

X\_scaled = (X - μ) / σ
  • X:原始特征值

  • μ:该特征的均值

  • σ:该特征的标准差

2. 适用场景与优缺点

适用场景:

  • 模型对数据分布有要求(如线性回归、逻辑回归、PCA、LDA 等,假设数据服从正态分布);

  • 数据存在异常值(标准化对异常值的敏感度低于归一化);

  • 特征数值范围差异大,但无需限定在固定区间。

优缺点:

  • 优点:对异常值鲁棒性强、保留数据的分布特征、泛化能力强(基于均值和标准差,不受极端值影响);

  • 缺点:数值范围不固定(可能为负数)、无法直接用于需要非负特征的场景(如图像像素值)。

3. Python 实操步骤(以金融风控数据为例)

场景:金融风控模型,包含 “年龄(岁)”“收入(元)”“负债率(%)” 三个特征,需标准化处理

步骤 1:数据准备

import pandas as pd

from sklearn.preprocessing import StandardScaler

\# 构造示例数据(含少量异常值,如收入100万)

data = {

    "客户ID": \[101,102,103,104,105],

    "年龄": \[28, 35, 42, 25, 50],

    "收入": \[8000, 15000, 20000, 12000, 1000000],  # 100万为异常值

    "负债率": \[30, 45, 25, 50, 35]

}

df = pd.DataFrame(data)

print("原始数据:")

print(df)

步骤 2:标准化处理(使用 Scikit-Learn 的 StandardScaler)

\# 1. 选择需要标准化的特征

features = \["年龄", "收入", "负债率"]

X = df\[features]

\# 2. 初始化StandardScaler

scaler = StandardScaler()

\# 3. 拟合并转换数据

X\_scaled = scaler.fit\_transform(X)

\# 4. 合并回原数据

df\_scaled = pd.DataFrame(X\_scaled, columns=features)

df\_final = pd.concat(\[df\["客户ID"], df\_scaled], axis=1)

print("\n标准化后的数据(均值=0,标准差=1):")

print(df\_final)

print(f"\n各特征均值:{np.round(df\_scaled.mean(), 4)}")  # 接近0

print(f"各特征标准差:{np.round(df\_scaled.std(), 4)}")  # 接近1

步骤 3:结果解读

  • 即使收入存在 100 万的异常值,标准化后的数据仍集中在 [-2, 2] 区间(异常值仅表现为较大的 Z 分数,未过度压缩其他数据);

  • 标准化后的数据可直接输入逻辑回归、PCA 等模型,避免异常值对模型的影响。

步骤 4:与归一化的对比(关键选型)

\# 同一数据同时做归一化和标准化,对比结果

from sklearn.preprocessing import MinMaxScaler

\# 归一化

minmax\_scaler = MinMaxScaler()

X\_minmax = minmax\_scaler.fit\_transform(X)

df\_minmax = pd.DataFrame(X\_minmax, columns=features)

print("\n归一化后(受异常值影响大):")

print(df\_minmax\["收入"])  # 大部分数据接近0,异常值100万→1

print("\n标准化后(受异常值影响小):")

print(df\_scaled\["收入"])  # 数据分布更均匀

四、特征编码(One-Hot Encoding):类别特征数值化

1. 核心原理

特征编码(独热编码)的核心是 “将类别特征转换为二进制向量”—— 对于含 n 个类别的特征,将其转换为 n 个二进制列(0 或 1),每个列对应一个类别,仅在该类别出现时标记为 1,其余为 0。

示例:“商品类别” 特征(服饰 / 家电 / 食品)

  • 服饰→[1, 0, 0]

  • 家电→[0, 1, 0]

  • 食品→[0, 0, 1]

2. 适用场景与优缺点

适用场景:

  • 类别特征为 “名义变量”(无顺序关系,如性别、商品类别、地域);

  • 模型无法直接处理非数值特征(如线性回归、神经网络、SVM);

  • 类别数量不多(一般≤10,避免维度爆炸)。

优缺点:

  • 优点:无顺序偏差(不会让模型误认为类别有大小关系)、适配大部分模型;

  • 缺点:类别数量过多时会导致 “维度爆炸”(如 “省份” 特征有 34 个类别,编码后新增 34 列)、增加模型计算量。

3. Python 实操步骤(以电商商品数据为例)

场景:电商商品数据,包含 “商品 ID”“商品类别”“品牌”“价格”,需对 “商品类别”“品牌” 进行 One-Hot 编码

步骤 1:数据准备

import pandas as pd

\# 构造示例数据

data = {

    "商品ID": \[1001, 1002, 1003, 1004, 1005],

    "商品类别": \["服饰", "家电", "食品", "服饰", "家电"],

    "品牌": \["A", "B", "C", "A", "B"],

    "价格": \[299, 1999, 99, 399, 2499]

}

df = pd.DataFrame(data)

print("原始数据:")

print(df)

步骤 2:One-Hot 编码(两种常用方法)

方法 1:使用 Pandas 的 get_dummies(适合快速编码,无需后续建模)
\# 对“商品类别”“品牌”进行编码,忽略“商品ID”“价格”

df\_encoded = pd.get\_dummies(df, columns=\["商品类别", "品牌"], prefix=\["类别", "品牌"])

print("\nPandas get\_dummies编码结果:")

print(df\_encoded)
方法 2:使用 Scikit-Learn 的 OneHotEncoder(适合后续建模,支持训练集 / 测试集统一编码)
from sklearn.preprocessing import OneHotEncoder

from sklearn.compose import ColumnTransformer

from sklearn.pipeline import Pipeline

\# 1. 分离特征和标签(此处无标签,仅处理特征)

X = df\[\["商品类别", "品牌", "价格"]]

\# 2. 定义需要编码的列和保持不变的列

categorical\_features = \["商品类别", "品牌"]  # 类别特征

numeric\_features = \["价格"]  # 数值特征(保持不变)

\# 3. 构造预处理流水线:对类别特征编码,数值特征不变

preprocessor = ColumnTransformer(

    transformers=\[

        ("cat", OneHotEncoder(), categorical\_features),  # 类别特征编码

        ("num", "passthrough", numeric\_features)  # 数值特征保持原样

    ]

)

\# 4. 执行编码

X\_encoded = preprocessor.fit\_transform(X)

\# 5. 转换为DataFrame(便于查看)

\# 获取编码后的列名

cat\_columns = preprocessor.named\_transformers\_\["cat"].get\_feature\_names\_out(categorical\_features)

all\_columns = list(cat\_columns) + numeric\_features

df\_encoded\_sklearn = pd.DataFrame(X\_encoded.toarray(), columns=all\_columns)

print("\nScikit-Learn OneHotEncoder编码结果:")

print(df\_encoded\_sklearn)

步骤 3:结果解读

  • 编码后新增 4 列:类别_服饰、类别_家电、类别_食品、品牌_A、品牌_B、品牌_C(实际为 6 列,因示例中品牌 C 仅出现 1 次);

  • 每一行的二进制向量准确对应原始类别(如商品 1001:服饰→类别_服饰 = 1,品牌 A→品牌_A=1);

  • 编码后的特征可直接输入线性回归、神经网络等模型。

步骤 4:处理高基数类别(避坑关键)

若类别数量过多(如 “用户标签” 有 50 个类别),直接编码会导致维度爆炸,解决方案:

\# 方法1:合并低频类别(如将出现次数<5的类别归为“其他”)

df\["商品类别"] = df\["商品类别"].map(lambda x: x if df\["商品类别"].value\_counts()\[x] >= 2 else "其他")

\# 方法2:使用嵌入层(适合深度学习模型,将高维类别特征映射到低维向量)

\# 此处暂不展开,后续可单独讲解

五、三大技术选型指南:什么时候用哪种?

1. 数值特征:归一化 vs 标准化

选择依据 推荐技术
模型对距离敏感(SVM、KNN、神经网络) 归一化(Min-Max)
模型假设数据服从正态分布(线性回归、逻辑回归、PCA) 标准化(Z-Score)
数据存在较多异常值 标准化(Z-Score)
业务需要固定数值区间(如 0-100 分) 归一化(Min-Max)
训练集和测试集数据分布差异大 标准化(Z-Score)

2. 类别特征:One-Hot 编码 vs 其他编码(如标签编码)

类别特征类型 推荐编码方式 不推荐方式
名义变量(无顺序,如性别、品牌) One-Hot 编码 标签编码(会引入顺序偏差)
有序变量(有顺序,如学历:小学→中学→大学) 标签编码 / 序数编码 One-Hot 编码(浪费维度)
高基数类别(如用户 ID、省份) 嵌入层 / 目标编码 One-Hot 编码(维度爆炸)

六、常见坑与避坑技巧

1. 坑 1:归一化 / 标准化时,训练集和测试集使用不同的参数

  • 现象:训练集用自己的 min/max/ 均值 / 标准差,测试集用自己的,导致模型泛化能力差;

  • 原因:未遵循 “训练集拟合,测试集转换” 的原则;

  • 避坑技巧:

from sklearn.model\_selection import train\_test\_split

\# 先分割数据

X\_train, X\_test = train\_test\_split(X, test\_size=0.2, random\_state=42)

\# 用训练集拟合scaler

scaler = StandardScaler()

X\_train\_scaled = scaler.fit\_transform(X\_train)

\# 测试集直接用训练集的scaler转换(不重新fit)

X\_test\_scaled = scaler.transform(X\_test)
  • 先分割训练集和测试集,再用训练集的 scaler 拟合,然后分别转换训练集和测试集;

2. 坑 2:对类别特征盲目使用 One-Hot 编码

  • 现象:对 “用户 ID”(高基数)、“学历”(有序)等特征编码,导致维度爆炸或模型误判;

  • 避坑技巧:

    • 先判断类别类型:名义变量用 One-Hot,有序变量用标签编码,高基数变量用嵌入层;

    • 高基数类别先做特征筛选(如只保留 Top10 类别,其余归为 “其他”)。

3. 坑 3:标准化后的数据出现负数,影响模型输入

  • 现象:将标准化后的数据输入需要非负特征的模型(如 XGBoost 的某些参数、图像模型),导致报错;

  • 避坑技巧:

    • 改用归一化(缩放到 [0,1]);

    • 对标准化后的数据再做一次 Min-Max 缩放(如缩放到 [0,1]);

    • 检查模型文档,确认是否支持负特征。

4. 坑 4:忽略数据中的缺失值,直接归一化 / 编码

  • 现象:数据存在 NaN 值,导致归一化 / 编码失败或结果失真;

  • 避坑技巧:

\# 数值特征填充中位数

X\["收入"] = X\["收入"].fillna(X\["收入"].median())

\# 类别特征填充众数

X\["商品类别"] = X\["商品类别"].fillna(X\["商品类别"].mode()\[0])
  • 先处理缺失值(数值特征用均值 / 中位数填充,类别特征用众数填充);

七、实战案例:电商用户购买预测模型的数据预处理全流程

场景:基于用户数据预测是否购买商品,数据包含以下特征:

  • 数值特征:年龄(18-60)、浏览时长(0-120 分钟)、历史购买金额(0-5000 元);

  • 类别特征:性别(男 / 女)、会员等级(普通 / 白银 / 黄金);

  • 目标变量:是否购买(0/1)。

预处理步骤:

  1. 数据清洗:处理缺失值(年龄用中位数填充,会员等级用众数填充);

  2. 数值特征标准化:年龄、浏览时长、历史购买金额用 Z-Score 标准化(后续用逻辑回归模型);

  3. 类别特征 One-Hot 编码:性别、会员等级用 One-Hot 编码;

  4. 数据分割:分为训练集(80%)和测试集(20%)。

完整 Python 代码:

import pandas as pd

import numpy as np

from sklearn.model\_selection import train\_test\_split

from sklearn.preprocessing import StandardScaler, OneHotEncoder

from sklearn.compose import ColumnTransformer

from sklearn.pipeline import Pipeline

from sklearn.impute import SimpleImputer

\# 1. 构造数据(含缺失值)

data = {

    "年龄": \[25, 35, np.nan, 42, 28, 50, 33, np.nan, 45, 30],

    "性别": \["男", "女", "男", "女", "男", "女", "男", "女", "男", "女"],

    "浏览时长": \[30, 60, 45, 90, 15, 75, 40, 80, 25, 50],

    "会员等级": \["普通", "白银", "黄金", "白银", np.nan, "黄金", "普通", "白银", "黄金", "普通"],

    "历史购买金额": \[500, 1500, 3000, 2000, 800, 4500, 1200, 3500, 2800, 1000],

    "是否购买": \[0, 1, 1, 1, 0, 1, 0, 1, 1, 0]

}

df = pd.DataFrame(data)

\# 2. 分割特征和目标变量

X = df.drop("是否购买", axis=1)

y = df\["是否购买"]

\# 3. 定义特征类型

numeric\_features = \["年龄", "浏览时长", "历史购买金额"]

categorical\_features = \["性别", "会员等级"]

\# 4. 构造预处理流水线:缺失值填充→标准化/编码

preprocessor = ColumnTransformer(

    transformers=\[

        ("num", Pipeline(steps=\[

            ("imputer", SimpleImputer(strategy="median")),  # 数值特征缺失值用中位数填充

            ("scaler", StandardScaler())  # 标准化

        ]), numeric\_features),

        ("cat", Pipeline(steps=\[

            ("imputer", SimpleImputer(strategy="most\_frequent")),  # 类别特征缺失值用众数填充

            ("encoder", OneHotEncoder())  # One-Hot编码

        ]), categorical\_features)

    ]

)

\# 5. 分割训练集和测试集

X\_train, X\_test, y\_train, y\_test = train\_test\_split(X, y, test\_size=0.2, random\_state=42)

\# 6. 执行预处理(训练集拟合+转换,测试集仅转换)

X\_train\_processed = preprocessor.fit\_transform(X\_train)

X\_test\_processed = preprocessor.transform(X\_test)

\# 7. 查看结果

print("训练集预处理后形状:", X\_train\_processed.shape)

print("测试集预处理后形状:", X\_test\_processed.shape)

\# 后续可直接用X\_train\_processed、X\_test\_processed训练逻辑回归等模型
Logo

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

更多推荐