将构建一个实用的数据准备管道。此管道会处理一个示例数据集,并应用诸如填充缺失值、数据缩放和编码等常见的预处理步骤。这些步骤随后被整合到一个可重用的Scikit-learn Pipeline中。这种方法确保了一致性,并避免了训练集和测试集之间的数据泄露。准备工作:数据集与目标假设我们有一个包含房屋信息的数据集,目标是预测房屋价格。我们的数据集可能包含的特征有面积(平方英尺)、卧室数量、位置(分类)和房龄。它可能也包含缺失值。让我们创建一个小型、有代表性的Pandas DataFrame来工作:import pandas as pd import numpy as np # 示例房屋数据 data = { 'SquareFeet': [1500, 2100, 1800, np.nan, 2500, 1200, 1900], 'Bedrooms': [3, 4, 3, 3, 5, 2, np.nan], 'Location': ['Urban', 'Suburban', 'Urban', 'Rural', 'Suburban', 'Rural', 'Urban'], 'Age': [5, 10, 8, 25, 1, 15, 7], 'Price': [300000, 450000, 350000, 180000, 550000, 200000, 380000] } df = pd.DataFrame(data) print("原始DataFrame:") print(df) # 分离特征(X)和目标(y) X = df.drop('Price', axis=1) y = df['Price']我们的X DataFrame看起来像这样: SquareFeet Bedrooms Location Age 0 1500.0 3.0 Urban 5 1 2100.0 4.0 Suburban 10 2 1800.0 3.0 Urban 8 3 NaN 3.0 Rural 25 4 2500.0 5.0 Suburban 1 5 1200.0 2.0 Rural 15 6 1900.0 NaN Urban 7请注意SquareFeet和Bedrooms中的缺失值(NaN),以及分类特征Location。步骤1:划分数据在进行任何预处理之前,我们必须将数据划分为训练集和测试集。这样做可以防止测试集的信息无意中影响从训练集学习到的预处理步骤(数据泄露)。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42 # 使用random_state以确保结果可重现 ) print("\n训练集特征形状:", X_train.shape) print("测试集特征形状:", X_test.shape)步骤2:定义不同列类型的预处理步骤我们需要对数值列和分类列采用不同的策略:数值列(SquareFeet、Bedrooms、Age):填充缺失值(例如,使用中位数)。对特征进行缩放(例如,使用StandardScaler)。分类列(Location):填充缺失值(如果有,例如,使用最常见的值)。应用独热编码。Scikit-learn的Pipeline和ColumnTransformer是实现这一点的理想工具。Pipeline按顺序连接步骤,而ColumnTransformer对不同列应用不同的转换。from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer # 识别数值列和分类列 numerical_features = ['SquareFeet', 'Bedrooms', 'Age'] categorical_features = ['Location'] # 为数值特征创建预处理管道 # 步骤1: 使用中位数填充缺失值 # 步骤2: 缩放数值特征 numerical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) # 为分类特征创建预处理管道 # 步骤1: 使用最常见的值填充缺失值(如果有) # 步骤2: 应用独热编码,忽略转换过程中遇到的未知类别 categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore')) ])步骤3:使用ColumnTransformer组合预处理步骤现在,我们使用ColumnTransformer将正确的管道应用于相应的列。# 使用ColumnTransformer创建预处理器对象 # 将numerical_transformer应用于numerical_features # 将categorical_transformer应用于categorical_features preprocessor = ColumnTransformer( transformers=[ ('num', numerical_transformer, numerical_features), ('cat', categorical_transformer, categorical_features) ], remainder='passthrough' # 保留其他列(如果有)——此处不需要但这是一个好习惯 ) print("\n预处理器结构:") print(preprocessor)这个preprocessor对象封装了所有数据准备逻辑。它知道哪些列是数值型的,哪些是分类型的,以及应用于每组的步骤顺序(填充,然后缩放/编码)。步骤4:构建完整管道(可选:包含模型)虽然本章侧重于数据准备,但您通常会将实际的机器学习模型包含在同一个管道中。这确保了整个工作流程(从原始数据到预测)得到简化。让我们添加一个占位符模型(例如,线性回归)进行说明。from sklearn.linear_model import LinearRegression # 创建包含预处理器和模型的完整管道 # 步骤1: 应用预处理器 # 步骤2: 训练线性回归模型 full_pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('regressor', LinearRegression()) # 示例模型 ]) print("\n完整管道结构:") print(full_pipeline)步骤5:将管道应用于数据现在,见证其作用。我们可以在训练数据上fit整个管道。Scikit-learn会正确处理每个步骤的应用:numerical_transformer中的SimpleImputer(中位数)仅在训练数据的数值列上进行拟合,以学习中位数。StandardScaler仅在已填充缺失值的训练数据的数值列上进行拟合,以学习均值和标准差。categorical_transformer中的SimpleImputer(最常见值)仅在训练数据的分类列上进行拟合。OneHotEncoder仅在已填充缺失值的训练数据的分类列上进行拟合,以学习独特的类别。最后,(可选的)LinearRegression模型使用完全转换后的训练数据进行训练。# 将管道拟合到训练数据 # 这将应用填充、缩放、编码和模型训练 full_pipeline.fit(X_train, y_train) print("\n管道在训练数据上成功拟合.") # 现在,您可以使用已拟合的管道转换测试数据 # 并进行预测 X_test_processed = full_pipeline.named_steps['preprocessor'].transform(X_test) print("\n处理后的测试特征形状:", X_test_processed.shape) print("处理后的测试数据示例(第一行):\n", X_test_processed[0]) # 对测试集进行预测 predictions = full_pipeline.predict(X_test) print("\n测试数据上的预测:", predictions)请注意,当我们对测试数据(X_test)调用transform(或predict)时,管道使用仅从训练数据(X_train)学到的参数(中位数、均值、标准差、类别)。这正是我们希望实现的,以防止数据泄露并获得模型性能的可靠评估。输出的X_test_processed是一个NumPy数组,其中缺失值已被填充,数值特征已缩放,分类特征已独热编码。它已准备好直接馈送给机器学习模型。可视化管道结构我们可以使用Graphviz来可视化preprocessor或full_pipeline的结构。from sklearn import set_config # 设置 display='diagram' 以启用图形表示 set_config(display='diagram') # 显示预处理器管道 print("\n预处理器图:") display(preprocessor) # 在Jupyter环境中,这会显示图表 # 显示完整管道 print("\n完整管道图:") display(full_pipeline) # 显示预处理器 + 回归器 # 如果需要,将显示设置回默认 # set_config(display='text')如果您处于支持富文本显示的环境(如Jupyter),您将看到交互式图表。以下是使用Graphviz语法的静态表示:digraph G { rankdir=LR; splines=ortho; node [shape=box, style="rounded,filled", fillcolor="#e9ecef", margin=0.2]; edge [arrowhead=none, color="#495057"]; subgraph cluster_num_pipeline { label = "数值管道"; style="rounded,filled"; fillcolor="#a5d8ff"; // 浅蓝色背景 node [fillcolor="#d0bfff"]; // 紫罗兰色节点 num_imputer [label="SimpleImputer\n(策略='中位数')"]; num_scaler [label="StandardScaler"]; num_imputer -> num_scaler; } subgraph cluster_cat_pipeline { label = "分类管道"; style="rounded,filled"; fillcolor="#b2f2bb"; // 浅绿色背景 node [fillcolor="#ffec99"]; // 黄色节点 cat_imputer [label="SimpleImputer\n(策略='最常见值')"]; cat_encoder [label="OneHotEncoder\n(处理未知='忽略')"]; cat_imputer -> cat_encoder; } subgraph cluster_col_transformer { label = "列转换器"; style="rounded"; node [fillcolor="#ffc9c9"]; // 红色节点 col_trans [label="转换器"]; col_trans -> num_imputer [lhead=cluster_num_pipeline, label="数值特征\n['SquareFeet', 'Bedrooms', 'Age']", color="#1c7ed6"]; col_trans -> cat_imputer [lhead=cluster_cat_pipeline, label="分类特征\n['Location']", color="#37b24d"]; } subgraph cluster_full_pipeline { label = "完整管道"; style=dashed; node [fillcolor="#ffd8a8"]; // 橙色节点 model [label="线性回归"]; col_trans -> model; // 连接转换器输出到模型输入 } }数据准备步骤的结构,使用ColumnTransformer组合,并可选地包含在完整模型管道中。数值特征经过填充和缩放,而分类特征则进行填充和独热编码。本实践练习演示了如何使用Scikit-learn的Pipeline和ColumnTransformer构建数据准备工作流程。通过为不同数据类型定义独立的步骤,并将它们封装在一个单一对象中,您可以创建更易于管理、重用,并且不易出现数据泄露等错误的代码。这是任何机器学习实践者的一项基本技能。