当前位置:首页>python>机器学习第一步:数据预处理全攻略(附Python代码)

机器学习第一步:数据预处理全攻略(附Python代码)

  • 2026-09-08 23:19:31
机器学习第一步:数据预处理全攻略(附Python代码)

未经处理的数据,就像未经打磨的璞玉,只有经过精心雕琢,才能绽放光彩。

你是否曾经兴致勃勃地跑完一个机器学习模型,却发现准确率惨不忍睹?你是否遇到过明明算法选得没错,可结果就是不如人意?问题很可能出在数据预处理上!

俗话说“垃圾进,垃圾出”,机器学习模型对数据的质量极其敏感。现实世界中的数据往往是脏乱差的:有缺失值、有异常点、量纲不统一……今天,我们就来系统地梳理机器学习中的数据预处理方法,并附上可直接运行的Python代码,让你从数据小白进阶为预处理达人!

一、数据预处理概述

数据预处理,简单来说就是把原始数据转换成适合模型输入的格式。主要包括以下任务:

  • 数据清洗:处理缺失值、重复值、异常值

  • 特征工程:特征缩放、编码、构造、选择

  • 数据变换:偏态处理、离散化、降维

  • 数据集划分:训练/验证/测试集划分

通常,我们用pandas进行数据探索,用scikit-learn完成大部分预处理操作。开始前,先导入必要的库:

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import (StandardScaler, MinMaxScaler,                                    OneHotEncoder, LabelEncoder,                                   PolynomialFeatures, PowerTransformer)from sklearn.impute import SimpleImputerfrom sklearn.feature_selection import (VarianceThreshold, SelectKBest,                                        chi2, mutual_info_classif)from sklearn.decomposition import PCA# 设置随机种子np.random.seed(42)

二、数据清洗:让数据“干净”起来

1. 处理缺失值

缺失值是家常便饭,处理方法主要有两种:删除和填充。

  • 删除:当缺失比例很低(如<5%)或该特征不重要时,可以直接删除缺失行或整列。

  • 填充:用统计量填充(均值、中位数、众数),或用插值、模型预测等方法。

来看代码示例:

# 创建示例数据df = pd.DataFrame({    'A': [1, 2, np.nan, 4, 5],    'B': [10, np.nan, 30, 40, 50],    'C': ['cat', 'dog', 'cat', None, 'dog'],    'target': [0, 1, 0, 1, 0]})print("原始数据:\n", df)# 1. 删除缺失值所在行df_dropna = df.dropna()print("\n删除缺失行后:\n", df_dropna)# 2. 填充缺失值# 数值列用中位数填充imputer_num = SimpleImputer(strategy='median')df[['A', 'B']] = imputer_num.fit_transform(df[['A', 'B']])# 分类列用众数填充imputer_cat = SimpleImputer(strategy='most_frequent')df['C'] = imputer_cat.fit_transform(df[['C']]).ravel()print("\n填充后数据:\n", df)
除了SimpleImputer,还可以用pandas自带的fillna方法进行更灵活的填充,比如向前填充(method='ffill')或向后填充(method='bfill')。

2. 处理重复值

重复数据会让模型对某些样本过拟合,必须去重。

# 检测重复行print(df.duplicated().sum())  # 输出重复行数# 删除重复行(保留第一个)df = df.drop_duplicates()

3. 处理异常值

异常值可能是测量误差或真实极端情况,需要用统计方法识别并处理。

常用方法:

  • Z-score法:假设数据服从正态分布,Z-score绝对值大于3视为异常。

  • IQR法:四分位距,超出Q1-1.5IQR或Q3+1.5IQR为异常。

# 用IQR法识别异常值Q1 = df['A'].quantile(0.25)Q3 = df['A'].quantile(0.75)IQR = Q3 - Q1lower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRoutliers = df[(df['A'] < lower_bound) | (df['A'] > upper_bound)]print("异常值:\n", outliers)# 处理方式:删除或替换为边界值df['A'] = df['A'].clip(lower_bound, upper_bound)  # 截断
对于更复杂的数据,可以用箱线图或散点图辅助判断。

三、特征工程:打造模型喜欢的特征

1. 特征缩放

不同特征往往具有不同的量纲和取值范围,很多模型(如SVM、神经网络、KNN)对特征尺度敏感,需要进行缩放。

  • 标准化(Standardization):将数据缩放成均值为0,标准差为1的分布。

  • 归一化(Normalization):将数据缩放到[0,1]区间(或其他指定范围)。

# 创建示例数据X = np.array([[1, 100], [2, 200], [3, 300], [4, 400]])# 标准化scaler_std = StandardScaler()X_std = scaler_std.fit_transform(X)print("标准化后:\n", X_std)# 归一化scaler_minmax = MinMaxScaler()X_minmax = scaler_minmax.fit_transform(X)print("归一化后:\n", X_minmax)
注意:对训练集进行fit_transform后,要用相同的scaler对测试集进行transform,避免数据泄露。

2. 特征编码

机器学习模型只能处理数值,类别型特征需要转换成数值形式。

  • 标签编码(Label Encoding):将类别映射为0,1,2...适合有序类别。

  • 独热编码(One-Hot Encoding):创建二进制虚拟变量,适合无序类别。

# 标签编码le = LabelEncoder()df['C_encoded'] = le.fit_transform(df['C'])print("标签编码后:\n", df[['C', 'C_encoded']])# 独热编码df_encoded = pd.get_dummies(df, columns=['C'], prefix='C')print("独热编码后:\n", df_encoded)

使用sklearn的OneHotEncoder可以保留特征名称,方便后续处理。

3. 特征构造

有时候现有特征不足以表达复杂关系,需要构造新特征,比如多项式特征、组合特征等。

# 多项式特征(生成交互项和幂次项)poly = PolynomialFeatures(degree=2, include_bias=False)X_poly = poly.fit_transform(X)print("原始特征:\n", X)print("多项式特征:\n", X_poly)
还可以根据业务知识构造,比如从日期中提取星期几、是否为节假日等。

4. 特征选择

特征太多会导致维度灾难,且可能包含冗余特征。特征选择可以降维、减少过拟合、提升模型效率。

常用方法:

  • 方差过滤:删除方差低于阈值的特征(通常先归一化)。

  • 统计检验:卡方检验、互信息等,选择与目标变量最相关的K个特征。

  • 基于模型的选择:用随机森林等模型的特征重要性进行选择。

# 假设X为特征,y为目标X = df[['A', 'B']]  # 示例特征y = df['target']# 方差过滤selector_var = VarianceThreshold(threshold=0.1)X_var = selector_var.fit_transform(X)# 卡方检验(适用于分类,特征非负)selector_chi2 = SelectKBest(chi2, k=1)X_chi2 = selector_chi2.fit_transform(X, y)print("卡方选择后特征:", X_chi2)# 互信息selector_mi = SelectKBest(mutual_info_classif, k=1)X_mi = selector_mi.fit_transform(X, y)

四、数据变换:让分布更“友好”

1. 偏态处理

许多模型假设特征服从正态分布,偏态严重的特征需要变换。常用对数变换、Box-Cox变换。

# 生成偏态数据skewed_data = np.exp(np.random.randn(1000) * 0.5)  # 正偏态plt.hist(skewed_data, bins=30)plt.title("原始偏态分布")plt.show()# 对数变换log_data = np.log1p(skewed_data)  # log(1+x)plt.hist(log_data, bins=30)plt.title("对数变换后")plt.show()# Box-Cox变换(要求数据>0)pt = PowerTransformer(method='box-cox')boxcox_data = pt.fit_transform(skewed_data.reshape(-1, 1))plt.hist(boxcox_data, bins=30)plt.title("Box-Cox变换后")plt.show()

2. 离散化

将连续变量分段,变成有序类别,有助于处理非线性关系。

  • 等宽离散化:将数据分成k个等宽的区间。

  • 等频离散化:每个区间包含相同数量的样本。

  • 聚类离散化:用KMeans聚类得到区间。

# 等宽离散化df['A_binned'] = pd.cut(df['A'], bins=3, labels=['low', 'mid', 'high'])# 等频离散化df['A_qcut'] = pd.qcut(df['A'], q=3, labels=['Q1', 'Q2', 'Q3'])

3. 降维

当特征数量巨大时,可以用PCA等算法将特征投影到低维空间。

# PCA降维pca = PCA(n_components=2)  # 降到2维X_pca = pca.fit_transform(X)print("降维后数据形状:", X_pca.shape)print("解释方差比:", pca.explained_variance_ratio_)
PCA前最好先对特征进行标准化。

五、数据集划分:训练、验证、测试

永远不要用测试数据来指导预处理或模型调参!正确做法是先划分数据集,再分别预处理。

X = df.drop('target', axis=1)y = df['target']# 先划分训练集和测试集X_train, X_test, y_train, y_test = train_test_split(    X, y, test_size=0.2, random_state=42, stratify=y)# 再划分训练集和验证集(可选)X_train, X_val, y_train, y_val = train_test_split(    X_train, y_train, test_size=0.25, random_state=42, stratify=y_train)print(f"训练集大小:{X_train.shape}")print(f"验证集大小:{X_val.shape}")print(f"测试集大小:{X_test.shape}")

预处理时的注意事项:

  • 对训练集进行fit(计算均值、方差等)和transform

  • 对验证/测试集只进行transform(使用训练集的参数)

  • 避免使用任何未来信息(如用整个数据集的均值填充缺失值,这是常见错误)

六、完整预处理流程示例(以泰坦尼克号数据集为例)

下面我们用Kaggle的Titanic数据集,演示一个完整的预处理流程。

# 加载数据train = pd.read_csv('train.csv')  # 假设文件在当前目录test = pd.read_csv('test.csv')# 初步探索print(train.info())print(train.describe())# 选择特征(简化处理)features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']X = train[features]y = train['Survived']# 划分训练集和测试集(这里用全部训练集做训练,测试集留给最终提交)X_train, X_test, y_train, y_test = train_test_split(    X, y, test_size=0.2, random_state=42)# 预处理流水线(手工逐步实现)# 1. 缺失值处理# Age用中位数填充age_median = X_train['Age'].median()X_train['Age'].fillna(age_median, inplace=True)X_test['Age'].fillna(age_median, inplace=True)# Embarked用众数填充embarked_mode = X_train['Embarked'].mode()[0]X_train['Embarked'].fillna(embarked_mode, inplace=True)X_test['Embarked'].fillna(embarked_mode, inplace=True)# 2. 特征编码# Sex: male->0, female->1X_train['Sex'] = X_train['Sex'].map({'male': 0, 'female': 1})X_test['Sex'] = X_test['Sex'].map({'male': 0, 'female': 1})# Embarked: 独热编码X_train = pd.get_dummies(X_train, columns=['Embarked'], prefix='Emb')X_test = pd.get_dummies(X_test, columns=['Embarked'], prefix='Emb')# 3. 特征缩放(对数值特征标准化)num_cols = ['Age', 'SibSp', 'Parch', 'Fare']scaler = StandardScaler()X_train[num_cols] = scaler.fit_transform(X_train[num_cols])X_test[num_cols] = scaler.transform(X_test[num_cols])# 4. 可选:特征构造(如家庭大小)X_train['FamilySize'] = X_train['SibSp'] + X_train['Parch'] + 1X_test['FamilySize'] = X_test['SibSp'] + X_test['Parch'] + 1# 查看预处理后的数据print(X_train.head())# 现在可以用X_train, y_train训练模型了

七、总结与注意事项

数据预处理没有固定的公式,需要根据数据和模型灵活调整。以下是几点提醒:

  1. 先理解数据,再动手预处理:可视化、描述性统计能帮你发现很多问题。

  2. 避免数据泄露:所有从训练集计算得到的参数(均值、缩放因子等)都要应用到测试集上,但不能让测试集参与fit。

  3. 保存预处理参数:对于部署模型,你需要保存训练时的scaler、imputer等对象,以便对新数据做相同处理。

  4. 尝试多种方法:不同的预处理组合可能会带来模型性能的差异,可以用交叉验证来评估。

  5. 注意模型对数据的假设:树模型不需要特征缩放,线性模型需要;KNN对尺度敏感等。

掌握数据预处理,你就已经走在了正确的机器学习道路上。希望这篇攻略能帮你少踩坑,多出成果!如果你有独到的预处理技巧,欢迎在评论区分享交流~


代码部分可直接复制运行,但建议根据实际数据调整参数。如果你喜欢这篇文章,记得点赞、在看、分享三连哦!

最新文章

随机文章