数据预处理核心技巧:归一化 (Min-Max)、标准化 (Z-Score) 与特征编码 (One-Hot) 实战教程
本文将从实际业务场景出发,详解三种技术的核心原理、适用场景、Python 实操步骤(结合 Pandas、Scikit-Learn),并通过电商用户行为分析、金融风控建模等真实案例,帮助数据分析师、算法工程师快速掌握实操技巧,避开常见坑。
在机器学习、数据分析项目中,“数据预处理” 是决定模型效果的关键步骤 —— 原始数据往往存在量纲不一致、类别特征无序、数值范围差异大等问题,直接建模会导致模型收敛慢、预测精度低。而归一化(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)。
预处理步骤:
-
数据清洗:处理缺失值(年龄用中位数填充,会员等级用众数填充);
-
数值特征标准化:年龄、浏览时长、历史购买金额用 Z-Score 标准化(后续用逻辑回归模型);
-
类别特征 One-Hot 编码:性别、会员等级用 One-Hot 编码;
-
数据分割:分为训练集(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训练逻辑回归等模型
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)