scikit-learn数据预处理与清洗方法实战指南 打造高质量数据集的完整流程
scikit-learn数据预处理与清洗方法实战指南 打造高质量数据集的完整流程
引言
在数据科学和机器学习项目中,数据预处理和清洗是构建高质量模型的关键步骤。俗话说,”垃圾进,垃圾出”(Garbage In, Garbage Out),这意味着如果输入数据质量不高,即使是最先进的算法也难以产生准确的结果。Scikit-learn作为Python中最流行的机器学习库之一,提供了丰富的数据预处理和清洗工具,帮助数据科学家和分析师高效地准备数据。
本文将详细介绍使用scikit-learn进行数据预处理和清洗的完整流程,涵盖从数据加载到最终预处理管道构建的各个环节。我们将通过实际代码示例演示各种技术,帮助读者掌握如何打造高质量的数据集。
数据加载与初步检查
在进行任何数据预处理之前,首先需要加载数据并进行初步检查,了解数据的基本情况和潜在问题。
import pandas as pd import numpy as np from sklearn.datasets import fetch_openml # 加载示例数据集 housing = fetch_openml(name="house_prices", as_frame=True) df = pd.DataFrame(housing.data, columns=housing.feature_names) df['SalePrice'] = housing.target # 查看数据基本信息 print(f"数据集形状: {df.shape}") print("n前5行数据:") print(df.head()) # 查看数据类型和缺失值 print("n数据类型和缺失值信息:") print(df.info()) # 查看数值型特征的统计摘要 print("n数值型特征统计摘要:") print(df.describe()) # 检查缺失值 print("n各列缺失值数量:") missing_values = df.isnull().sum() print(missing_values[missing_values > 0])
通过初步检查,我们可以了解数据集的大小、特征类型、缺失值情况以及数值特征的分布等基本信息。这有助于我们确定后续需要采取的预处理步骤。
缺失值处理
缺失值是数据集中常见的问题,可能由数据收集错误、数据录入遗漏或其他原因造成。处理缺失值的方法有多种,选择哪种方法取决于缺失值的数量、特征的性质以及业务背景。
删除含有缺失值的行或列
当缺失值较少或某些列缺失值过多时,可以考虑删除含有缺失值的行或列。
# 删除含有缺失值的行 df_drop_rows = df.dropna() # 删除含有缺失值的列(例如,缺失值超过30%的列) threshold = 0.3 * len(df) df_drop_cols = df.dropna(axis=1, thresh=threshold) print(f"原始数据集形状: {df.shape}") print(f"删除缺失行后数据集形状: {df_drop_rows.shape}") print(f"删除缺失列后数据集形状: {df_drop_cols.shape}")
填充缺失值
填充缺失值是更常用的方法,scikit-learn提供了多种填充策略。
from sklearn.impute import SimpleImputer # 数值型特征填充 # 使用均值填充 num_imputer_mean = SimpleImputer(strategy='mean') # 使用中位数填充 num_imputer_median = SimpleImputer(strategy='median') # 使用众数填充 num_imputer_most_frequent = SimpleImputer(strategy='most_frequent') # 使用固定值填充 num_imputer_constant = SimpleImputer(strategy='constant', fill_value=0) # 分类特征填充 # 使用众数填充 cat_imputer_most_frequent = SimpleImputer(strategy='most_frequent') # 使用固定值填充 cat_imputer_constant = SimpleImputer(strategy='constant', fill_value='Missing') # 示例:对数值型列使用中位数填充 numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns df_numeric = df[numeric_cols].copy() df_numeric_imputed = pd.DataFrame(num_imputer_median.fit_transform(df_numeric), columns=numeric_cols) print("填充前缺失值数量:") print(df_numeric.isnull().sum().sum()) print("n填充后缺失值数量:") print(df_numeric_imputed.isnull().sum().sum())
高级缺失值处理方法
对于更复杂的场景,scikit-learn提供了更高级的缺失值处理方法,如KNN插补和迭代插补。
from sklearn.impute import KNNImputer, IterativeImputer # KNN插补 knn_imputer = KNNImputer(n_neighbors=5) df_knn_imputed = pd.DataFrame(knn_imputer.fit_transform(df_numeric), columns=numeric_cols) # 迭代插补(基于回归模型) iterative_imputer = IterativeImputer(random_state=42) df_iter_imputed = pd.DataFrame(iterative_imputer.fit_transform(df_numeric), columns=numeric_cols) print("KNN插补后缺失值数量:", df_knn_imputed.isnull().sum().sum()) print("迭代插补后缺失值数量:", df_iter_imputed.isnull().sum().sum())
特征编码(处理分类变量)
大多数机器学习算法要求数值型输入,因此需要将分类变量转换为数值形式。scikit-learn提供了多种编码方法。
标签编码(Label Encoding)
标签编码将每个类别映射到一个整数。适用于有序分类变量。
from sklearn.preprocessing import LabelEncoder # 示例:对有序分类变量进行标签编码 df_ordinal = df[['ExterQual']].copy() # 假设ExterQual是有序分类变量 # 创建标签编码器 le = LabelEncoder() df_ordinal['ExterQual_encoded'] = le.fit_transform(df_ordinal['ExterQual']) print("原始值与编码值的映射:") for i, category in enumerate(le.classes_): print(f"{category}: {i}") print("n编码结果:") print(df_ordinal.head())
独热编码(One-Hot Encoding)
独热编码为每个类别创建一个新的二元(0/1)特征。适用于无序分类变量。
from sklearn.preprocessing import OneHotEncoder # 示例:对无序分类变量进行独热编码 df_nominal = df[['Neighborhood']].copy() # 假设Neighborhood是无序分类变量 # 创建独热编码器 ohe = OneHotEncoder(sparse=False, handle_unknown='ignore') ohe_features = ohe.fit_transform(df_nominal[['Neighborhood']]) ohe_df = pd.DataFrame(ohe_features, columns=ohe.get_feature_names_out(['Neighborhood'])) print("独热编码结果:") print(ohe_df.head())
目标编码(Target Encoding)
目标编码使用目标变量的统计信息(如均值)来表示分类变量。这种方法特别适用于高基数分类变量。
from sklearn.preprocessing import TargetEncoder # 示例:使用目标编码 df_target = df[['Neighborhood', 'SalePrice']].copy() # 创建目标编码器 te = TargetEncoder(target_type='continuous') df_target['Neighborhood_encoded'] = te.fit_transform(df_target[['Neighborhood']], df_target['SalePrice']) print("目标编码结果:") print(df_target[['Neighborhood', 'Neighborhood_encoded']].drop_duplicates().head())
二进制编码(Binary Encoding)
二进制编码是一种介于独热编码和哈希编码之间的方法,它首先将类别转换为整数,然后将这些整数表示为二进制形式。
from category_encoders import BinaryEncoder # 示例:使用二进制编码 df_binary = df[['Neighborhood']].copy() # 创建二进制编码器 be = BinaryEncoder() df_binary_encoded = be.fit_transform(df_binary['Neighborhood']) print("二进制编码结果:") print(df_binary_encoded.head())
特征缩放
特征缩放是确保不同特征具有相似尺度的重要预处理步骤,这对于许多机器学习算法(如SVM、KNN、神经网络等)的性能至关重要。
标准化(Standardization)
标准化将特征缩放为均值为0,标准差为1的分布。
from sklearn.preprocessing import StandardScaler # 示例:对数值型特征进行标准化 numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns df_numeric = df[numeric_cols].copy() # 创建标准化器 scaler = StandardScaler() df_scaled = pd.DataFrame(scaler.fit_transform(df_numeric), columns=numeric_cols) print("标准化前统计信息:") print(df_numeric.describe().loc[['mean', 'std']]) print("n标准化后统计信息:") print(df_scaled.describe().loc[['mean', 'std']])
归一化(Normalization)
归一化将特征缩放到一个指定的范围,通常是[0, 1]。
from sklearn.preprocessing import MinMaxScaler # 示例:对数值型特征进行归一化 # 创建归一化器 min_max_scaler = MinMaxScaler() df_normalized = pd.DataFrame(min_max_scaler.fit_transform(df_numeric), columns=numeric_cols) print("归一化前统计信息:") print(df_numeric.describe().loc[['min', 'max']]) print("n归一化后统计信息:") print(df_normalized.describe().loc[['min', 'max']])
鲁棒缩放(Robust Scaling)
鲁棒缩放使用中位数和四分位数范围进行缩放,对异常值不敏感。
from sklearn.preprocessing import RobustScaler # 示例:对数值型特征进行鲁棒缩放 # 创建鲁棒缩放器 robust_scaler = RobustScaler() df_robust_scaled = pd.DataFrame(robust_scaler.fit_transform(df_numeric), columns=numeric_cols) print("鲁棒缩放前统计信息:") print(df_numeric.describe().loc[['25%', '50%', '75%']]) print("n鲁棒缩放后统计信息:") print(df_robust_scaled.describe().loc[['25%', '50%', '75%']])
异常值检测与处理
异常值是显著偏离其他观测值的数据点,可能会影响模型的性能。检测和处理异常值是数据预处理的重要环节。
基于统计方法的异常值检测
# 基于Z-score的异常值检测 from scipy import stats def detect_outliers_zscore(df, column, threshold=3): z_scores = np.abs(stats.zscore(df[column].dropna())) outliers = df[column][z_scores > threshold] return outliers # 示例:检测SalePrice列的异常值 outliers_zscore = detect_outliers_zscore(df, 'SalePrice') print(f"基于Z-score检测到的异常值数量: {len(outliers_zscore)}") # 基于IQR的异常值检测 def detect_outliers_iqr(df, column): Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)][column] return outliers # 示例:检测SalePrice列的异常值 outliers_iqr = detect_outliers_iqr(df, 'SalePrice') print(f"基于IQR检测到的异常值数量: {len(outliers_iqr)}")
基于机器学习方法的异常值检测
from sklearn.ensemble import IsolationForest from sklearn.neighbors import LocalOutlierFactor from sklearn.svm import OneClassSVM # 准备数据(仅使用数值型列) numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns df_numeric = df[numeric_cols].copy() df_numeric = df_numeric.dropna() # 移除缺失值 # Isolation Forest iso_forest = IsolationForest(contamination=0.05, random_state=42) outliers_iso = iso_forest.fit_predict(df_numeric) print(f"Isolation Forest检测到的异常值数量: {sum(outliers_iso == -1)}") # Local Outlier Factor lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05) outliers_lof = lof.fit_predict(df_numeric) print(f"LOF检测到的异常值数量: {sum(outliers_lof == -1)}") # One-Class SVM oc_svm = OneClassSVM(nu=0.05) outliers_svm = oc_svm.fit_predict(df_numeric) print(f"One-Class SVM检测到的异常值数量: {sum(outliers_svm == -1)}")
异常值处理方法
# 删除异常值 def remove_outliers_iqr(df, column): Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR df_clean = df[(df[column] >= lower_bound) & (df[column] <= upper_bound)] return df_clean # 示例:删除SalePrice列的异常值 df_clean = remove_outliers_iqr(df.copy(), 'SalePrice') print(f"删除异常值前数据集大小: {df.shape}") print(f"删除异常值后数据集大小: {df_clean.shape}") # 替换异常值 def replace_outliers_with_median(df, column): Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR median = df[column].median() df[column] = np.where((df[column] < lower_bound) | (df[column] > upper_bound), median, df[column]) return df # 示例:用中位数替换SalePrice列的异常值 df_replaced = replace_outliers_with_median(df.copy(), 'SalePrice') # 比较替换前后的分布 print("n替换前的统计信息:") print(df['SalePrice'].describe()) print("n替换后的统计信息:") print(df_replaced['SalePrice'].describe())
特征转换
特征转换可以改变特征的分布,使其更适合特定的机器学习算法。
对数转换
对数转换可以处理右偏分布的数据,使其更接近正态分布。
import matplotlib.pyplot as plt import seaborn as sns # 示例:对SalePrice进行对数转换 df_log = df.copy() df_log['SalePrice_log'] = np.log1p(df_log['SalePrice']) # 使用log1p避免log(0)的问题 # 可视化转换前后的分布 fig, axes = plt.subplots(1, 2, figsize=(15, 5)) sns.histplot(df['SalePrice'], kde=True, ax=axes[0]) axes[0].set_title('原始SalePrice分布') sns.histplot(df_log['SalePrice_log'], kde=True, ax=axes[1]) axes[1].set_title('对数转换后SalePrice分布') plt.tight_layout() plt.show()
Box-Cox转换
Box-Cox转换是一系列幂转换,可以使数据更接近正态分布。
from sklearn.preprocessing import PowerTransformer # 示例:对SalePrice进行Box-Cox转换 df_boxcox = df.copy() # Box-Cox转换要求所有值必须为正数 # 如果数据中有0或负数,可以使用Yeo-Johnson转换 pt = PowerTransformer(method='box-cox') sale_prices = df_boxcox[['SalePrice']].values df_boxcox['SalePrice_boxcox'] = pt.fit_transform(sale_prices) # 可视化转换前后的分布 fig, axes = plt.subplots(1, 2, figsize=(15, 5)) sns.histplot(df['SalePrice'], kde=True, ax=axes[0]) axes[0].set_title('原始SalePrice分布') sns.histplot(df_boxcox['SalePrice_boxcox'], kde=True, ax=axes[1]) axes[1].set_title('Box-Cox转换后SalePrice分布') plt.tight_layout() plt.show()
多项式特征
多项式特征可以创建特征的非线性组合,帮助模型捕捉特征间的非线性关系。
from sklearn.preprocessing import PolynomialFeatures # 示例:创建多项式特征 df_poly = df.copy() # 选择几个数值型特征 selected_features = ['OverallQual', 'GrLivArea', 'GarageArea'] df_selected = df_poly[selected_features].dropna() # 创建二次多项式特征 poly = PolynomialFeatures(degree=2, include_bias=False) poly_features = poly.fit_transform(df_selected) poly_feature_names = poly.get_feature_names_out(selected_features) df_poly_features = pd.DataFrame(poly_features, columns=poly_feature_names) print("原始特征:") print(df_selected.head()) print("n多项式特征:") print(df_poly_features.head())
分箱(Binning)
分箱将连续变量转换为离散类别,可以帮助处理非线性关系和异常值。
from sklearn.preprocessing import KBinsDiscretizer # 示例:对YearBuilt进行分箱 df_binned = df.copy() # 创建分箱器 kbd = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='quantile') year_built = df_binned[['YearBuilt']].values df_binned['YearBuilt_binned'] = kbd.fit_transform(year_built) # 查看分箱结果 print("分箱边界:") print(kbd.bin_edges_) print("n分箱结果示例:") print(df_binned[['YearBuilt', 'YearBuilt_binned']].head(10)) # 可视化分箱结果 plt.figure(figsize=(10, 6)) sns.countplot(x='YearBuilt_binned', data=df_binned) plt.title('YearBuilt分箱结果') plt.show()
特征选择
特征选择是选择最相关特征子集的过程,可以减少模型复杂度,提高模型性能,并减少训练时间。
基于统计量的特征选择
from sklearn.feature_selection import SelectKBest, f_regression, chi2, mutual_info_regression # 准备数据(仅使用数值型列) numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns df_numeric = df[numeric_cols].copy() df_numeric = df_numeric.dropna() X = df_numeric.drop('SalePrice', axis=1) y = df_numeric['SalePrice'] # 基于F检验的特征选择 selector_f = SelectKBest(score_func=f_regression, k=10) X_f_selected = selector_f.fit_transform(X, y) f_selected_features = X.columns[selector_f.get_support()] print("基于F检验选择的Top 10特征:") print(f_selected_features) # 基于互信息的特征选择 selector_mi = SelectKBest(score_func=mutual_info_regression, k=10) X_mi_selected = selector_mi.fit_transform(X, y) mi_selected_features = X.columns[selector_mi.get_support()] print("n基于互信息选择的Top 10特征:") print(mi_selected_features)
基于模型的特征选择
from sklearn.feature_selection import SelectFromModel from sklearn.linear_model import Lasso, Ridge from sklearn.ensemble import RandomForestRegressor # 基于Lasso的特征选择 lasso = Lasso(alpha=0.01) selector_lasso = SelectFromModel(lasso) selector_lasso.fit(X, y) lasso_selected_features = X.columns[selector_lasso.get_support()] print("基于Lasso选择的特征:") print(lasso_selected_features) # 基于随机森林的特征选择 rf = RandomForestRegressor(n_estimators=100, random_state=42) selector_rf = SelectFromModel(rf, threshold='median') selector_rf.fit(X, y) rf_selected_features = X.columns[selector_rf.get_support()] print("n基于随机森林选择的特征:") print(rf_selected_features) # 可视化特征重要性 rf.fit(X, y) importances = rf.feature_importances_ indices = np.argsort(importances)[::-1] plt.figure(figsize=(12, 8)) plt.title('特征重要性') plt.bar(range(X.shape[1]), importances[indices], align='center') plt.xticks(range(X.shape[1]), X.columns[indices], rotation=90) plt.tight_layout() plt.show()
递归特征消除(RFE)
from sklearn.feature_selection import RFE from sklearn.linear_model import LinearRegression # 递归特征消除 estimator = LinearRegression() selector_rfe = RFE(estimator, n_features_to_select=10, step=1) selector_rfe = selector_rfe.fit(X, y) rfe_selected_features = X.columns[selector_rfe.support_] print("递归特征消除选择的Top 10特征:") print(rfe_selected_features) # 可视化特征排名 rfe_ranking = pd.DataFrame({ 'Feature': X.columns, 'Ranking': selector_rfe.ranking_ }).sort_values('Ranking') print("n特征排名:") print(rfe_ranking)
数据预处理管道构建
将所有预处理步骤组合到一个管道中可以简化代码,避免数据泄露,并使模型部署更加容易。
创建预处理管道
from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split # 准备数据 df_processed = df.copy() # 分离特征和目标变量 X = df_processed.drop('SalePrice', axis=1) y = df_processed['SalePrice'] # 分割训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 识别数值型和分类型特征 numeric_features = X.select_dtypes(include=['int64', 'float64']).columns categorical_features = X.select_dtypes(include=['object']).columns # 创建数值型特征的预处理管道 numeric_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) # 创建分类型特征的预处理管道 categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore')) ]) # 创建列转换器 preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer, numeric_features), ('cat', categorical_transformer, categorical_features) ]) # 应用预处理 X_train_processed = preprocessor.fit_transform(X_train) X_test_processed = preprocessor.transform(X_test) print("预处理后的训练集形状:", X_train_processed.shape) print("预处理后的测试集形状:", X_test_processed.shape)
将预处理与模型训练结合
from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score # 创建完整的管道(预处理+模型) # 线性回归管道 lr_pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('regressor', LinearRegression()) ]) # 随机森林管道 rf_pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('regressor', RandomForestRegressor(n_estimators=100, random_state=42)) ]) # 训练模型 lr_pipeline.fit(X_train, y_train) rf_pipeline.fit(X_train, y_train) # 评估模型 def evaluate_model(pipeline, X_test, y_test): y_pred = pipeline.predict(X_test) mse = mean_squared_error(y_test, y_pred) rmse = np.sqrt(mse) r2 = r2_score(y_test, y_pred) return {'MSE': mse, 'RMSE': rmse, 'R2': r2} lr_metrics = evaluate_model(lr_pipeline, X_test, y_test) rf_metrics = evaluate_model(rf_pipeline, X_test, y_test) print("线性回归模型性能:") print(lr_metrics) print("n随机森林模型性能:") print(rf_metrics)
保存和加载预处理管道
import joblib # 保存管道 joblib.dump(rf_pipeline, 'rf_pipeline.pkl') # 加载管道 loaded_pipeline = joblib.load('rf_pipeline.pkl') # 使用加载的管道进行预测 y_pred_loaded = loaded_pipeline.predict(X_test) mse_loaded = mean_squared_error(y_test, y_pred_loaded) print(f"使用加载的管道预测的MSE: {mse_loaded}")
实战案例:端到端的数据预处理流程
让我们通过一个完整的案例来演示如何应用上述所有技术来处理真实数据集。
# 加载数据集 from sklearn.datasets import fetch_openml # 加载Ames Housing数据集 housing = fetch_openml(name="house_prices", as_frame=True, parser='auto') df = pd.DataFrame(housing.data, columns=housing.feature_names) df['SalePrice'] = housing.target # 1. 数据初步检查 print("数据集形状:", df.shape) print("n数据类型:") print(df.dtypes.value_counts()) # 检查缺失值 missing_values = df.isnull().sum() missing_pct = (missing_values / len(df)) * 100 missing_info = pd.DataFrame({ 'Missing Values': missing_values, 'Percentage': missing_pct }).sort_values('Missing Values', ascending=False) print("n缺失值信息 (Top 10):") print(missing_info.head(10)) # 2. 处理缺失值 # 删除缺失值超过80%的列 cols_to_drop = missing_info[missing_info['Percentage'] > 80].index df = df.drop(columns=cols_to_drop) print(f"n删除了 {len(cols_to_drop)} 个缺失值过多的列") # 分离数值型和分类型特征 numeric_features = df.select_dtypes(include=['int64', 'float64']).columns.drop('SalePrice') categorical_features = df.select_dtypes(include=['object']).columns # 3. 异常值检测与处理 # 使用IQR方法检测SalePrice的异常值 Q1 = df['SalePrice'].quantile(0.25) Q3 = df['SalePrice'].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR # 标记异常值但不删除,因为我们将在后续步骤中处理 df['Price_Outlier'] = ((df['SalePrice'] < lower_bound) | (df['SalePrice'] > upper_bound)).astype(int) print(f"n检测到 {df['Price_Outlier'].sum()} 个价格异常值") # 4. 特征工程 # 创建一些新特征 df['TotalSF'] = df['TotalBsmtSF'] + df['1stFlrSF'] + df['2ndFlrSF'] df['TotalBathrooms'] = df['FullBath'] + 0.5 * df['HalfBath'] + df['BsmtFullBath'] + 0.5 * df['BsmtHalfBath'] df['YrSinceRemodel'] = df['YrSold'] - df['YearRemodAdd'] # 5. 数据分割 X = df.drop('SalePrice', axis=1) y = df['SalePrice'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 6. 构建预处理管道 # 更新数值型和分类型特征列表 numeric_features = X_train.select_dtypes(include=['int64', 'float64']).columns categorical_features = X_train.select_dtypes(include=['object']).columns # 数值型特征处理管道 numeric_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()) # 使用RobustScaler处理异常值 ]) # 分类型特征处理管道 categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) ]) # 列转换器 preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer, numeric_features), ('cat', categorical_transformer, categorical_features) ]) # 7. 特征选择 # 使用随机森林进行特征选择 feature_selector = Pipeline(steps=[ ('preprocessor', preprocessor), ('feature_selection', SelectFromModel(RandomForestRegressor(n_estimators=100, random_state=42), threshold='median')) ]) # 8. 模型训练与评估 # 创建完整管道 model_pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('feature_selection', SelectFromModel(RandomForestRegressor(n_estimators=100, random_state=42), threshold='median')), ('regressor', RandomForestRegressor(n_estimators=200, random_state=42)) ]) # 训练模型 model_pipeline.fit(X_train, y_train) # 评估模型 y_pred = model_pipeline.predict(X_test) mse = mean_squared_error(y_test, y_pred) rmse = np.sqrt(mse) r2 = r2_score(y_test, y_pred) print("n模型性能:") print(f"MSE: {mse:.2f}") print(f"RMSE: {rmse:.2f}") print(f"R2: {r2:.2f}") # 9. 可视化预测结果 plt.figure(figsize=(10, 6)) plt.scatter(y_test, y_pred, alpha=0.5) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--') plt.xlabel('Actual Price') plt.ylabel('Predicted Price') plt.title('Actual vs Predicted Prices') plt.show() # 10. 保存模型 joblib.dump(model_pipeline, 'ames_housing_model.pkl') print("n模型已保存为 'ames_housing_model.pkl'")
总结与最佳实践
数据预处理和清洗是机器学习项目成功的关键。通过本文,我们详细介绍了使用scikit-learn进行数据预处理和清洗的完整流程,包括:
- 数据加载与初步检查:了解数据的基本情况,识别潜在问题。
- 缺失值处理:使用删除、填充或高级插补方法处理缺失值。
- 特征编码:将分类变量转换为数值形式,包括标签编码、独热编码、目标编码等。
- 特征缩放:确保不同特征具有相似尺度,包括标准化、归一化和鲁棒缩放。
- 异常值检测与处理:识别并处理异常值,包括基于统计和机器学习的方法。
- 特征转换:改变特征分布,使其更适合模型,包括对数转换、Box-Cox转换、多项式特征和分箱。
- 特征选择:选择最相关的特征子集,提高模型性能。
- 数据预处理管道构建:将所有预处理步骤组合到一个管道中,简化代码并避免数据泄露。
最佳实践
- 始终分割数据:在应用任何预处理技术之前,先将数据分割为训练集和测试集,以避免数据泄露。
- 使用管道:将预处理步骤和模型训练组合到管道中,确保一致的预处理和简化部署。
- 了解数据:在应用任何技术之前,先深入了解数据的特性、分布和业务背景。
- 迭代处理:数据预处理是一个迭代过程,可能需要多次尝试不同的方法和技术。
- 文档记录:记录所有预处理步骤和决策,以便重现结果和团队协作。
- 考虑计算效率:对于大型数据集,考虑预处理步骤的计算效率,可能需要采样或分布式处理。
- 处理类别不平衡:如果目标变量存在类别不平衡问题,考虑使用过采样、欠采样或类别权重等方法。
- 特征工程:不要仅限于原始特征,尝试创建有意义的组合特征或转换特征。
- 验证预处理效果:在应用预处理后,验证其对模型性能的影响。
- 保持一致性:确保训练集和测试集应用相同的预处理步骤。
通过遵循这些最佳实践和使用scikit-learn提供的强大工具,您可以有效地预处理和清洗数据,为机器学习模型构建高质量的数据集,从而提高模型的性能和可靠性。