机器学习——数据清洗
数据科学项目中缺失值填充的全面指南
一、数据准备与原始值保留策略
1.1 创建数据副本的最佳实践
在数据清洗过程中,保持原始数据的完整性至关重要。我们建议采用以下两种复制策略:
完全独立副本:使用Python的copy模块创建深层副本,确保所有嵌套数据结构都被完全复制
import copy original_data = copy.deepcopy(data) # 完全独立的原始数据副本
工作副本:使用pandas的copy()方法创建工作副本
working_data = data.copy(deep=True) # 深度复制,确保不影响原始数据
应用场景:在金融数据分析项目中,原始交易记录必须保持不可变,而清洗操作在工作副本上进行。
1.2 详细的缺失值标记方法
为每个存在缺失值的列创建标记列,记录原始缺失情况:
for col in working_data.columns:if working_data[col].isnull().any(): # 检查列是否有缺失值working_data[f'{col}_missing'] = working_data[col].isnull().astype(int)# 存储缺失位置信息,1表示缺失,0表示存在
实际应用:在医疗数据集中,标记哪些血压值是估算的,哪些是实际测量的,这对后续分析至关重要。
二、基础统计填充方法的深入解析
2.1 众数填充的完整实现(适合分类变量)
from scipy.stats import modedef fill_with_mode(df, column):# 计算众数(忽略缺失值)mode_result = mode(df[column].dropna())mode_value = mode_result.mode[0] if len(mode_result.mode) > 0 else None# 创建填充列并保留原始值filled_col = f'{column}_filled_mode'df[filled_col] = df[column].fillna(mode_value)return df# 示例:填充产品类别中的缺失值
data = fill_with_mode(data, 'product_category')
注意事项:当所有值都相同时众数可能不存在,需添加异常处理。
2.2 平均数填充的增强版(适合正态分布数值变量)
def fill_with_mean(df, column, groupby_col=None):if groupby_col:# 分组计算均值(如按地区计算平均收入)group_means = df.groupby(groupby_col)[column].transform('mean')df[f'{column}_filled_mean'] = df[column].fillna(group_means)else:# 整体均值填充global_mean = df[column].mean()df[f'{column}_filled_mean'] = df[column].fillna(global_mean)return df# 示例1:整体均值填充年龄
data = fill_with_mean(data, 'age')# 示例2:按部门填充工资均值
data = fill_with_mean(data, 'salary', 'department')
优化建议:对于偏态分布数据,考虑使用对数转换后的均值。
2.3 中位数填充的稳健实现(适合偏态分布数据)
def fill_with_median(df, column, clip_outliers=True):if clip_outliers:# 移除极端值后计算中位数q1 = df[column].quantile(0.25)q3 = df[column].quantile(0.75)iqr = q3 - q1filtered = df[column][(df[column] >= q1-1.5*iqr) & (df[column] <= q3+1.5*iqr)]median_value = filtered.median()else:median_value = df[column].median()df[f'{column}_filled_median'] = df[column].fillna(median_value)return df# 示例:填充房屋价格(考虑去除异常值)
data = fill_with_median(data, 'house_price', clip_outliers=True)
适用场景:在收入、房价等通常右偏的数据中,中位数比平均数更能代表典型值。
三、机器学习填充方法的实现
3.1 数据预处理的完整流程
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.impute import SimpleImputer# 分类变量编码
def encode_categorical(df, cat_cols):le_dict = {}for col in cat_cols:le = LabelEncoder()df[col+'_encoded'] = le.fit_transform(df[col].fillna('Missing'))le_dict[col] = le # 保存编码器供后续使用return df, le_dict# 数值变量标准化
def scale_numeric(df, num_cols):scaler = StandardScaler()df[num_cols] = scaler.fit_transform(df[num_cols])return df, scaler# 示例预处理流程
data, encoders = encode_categorical(data, ['department', 'job_title'])
data, scaler = scale_numeric(data, ['age', 'salary'])
关键点:始终保存预处理对象,以便对新数据应用相同转换。
3.2 线性回归填充的完整实现
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_splitdef fill_with_regression(df, target_col, feature_cols):# 分离完整数据和缺失数据complete = df[df[target_col].notnull()]missing = df[df[target_col].isnull()]if len(complete) < 10: # 样本量不足时退回均值填充return fill_with_mean(df, target_col)# 划分训练测试集X = complete[feature_cols]y = complete[target_col]X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)# 训练模型model = LinearRegression()model.fit(X_train, y_train)# 预测缺失值if not missing.empty:X_missing = missing[feature_cols]predictions = model.predict(X_missing)df.loc[df[target_col].isnull(), f'{target_col}_filled_lr'] = predictionsreturn df, model# 示例:用学历和工作年限预测收入
data, lr_model = fill_with_regression(data, target_col='income',feature_cols=['education_years', 'work_experience']
)
评估指标:建议检查模型在测试集上的R²值,确保预测质量。
3.3 随机森林填充的高级实现
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_errordef fill_with_rf(df, target_col, feature_cols, n_estimators=100):complete = df[df[target_col].notnull()]missing = df[df[target_col].isnull()]if len(complete) < 30: # 随机森林需要更多样本return fill_with_median(df, target_col)X = complete[feature_cols]y = complete[target_col]X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)# 使用交叉验证选择最佳参数model = RandomForestRegressor(n_estimators=n_estimators,random_state=42,n_jobs=-1 # 使用所有CPU核心)model.fit(X_train, y_train)# 评估模型y_pred = model.predict(X_test)mse = mean_squared_error(y_test, y_pred)print(f'Test MSE: {mse:.2f}')# 填充缺失值if not missing.empty:X_missing = missing[feature_cols]predictions = model.predict(X_missing)df.loc[df[target_col].isnull(), f'{target_col}_filled_rf'] = predictionsreturn df, model# 示例:用多种特征预测客户生命周期价值
data, rf_model = fill_with_rf(data,target_col='customer_lifetime_value',feature_cols=['age', 'income', 'purchase_frequency', 'region']
)
优化建议:使用GridSearchCV进行超参数调优,考虑特征重要性分析。
四、填充结果比较与分析
4.1 可视化
import matplotlib.pyplot as plt
import seaborn as snsdef compare_distributions(df, original_col, filled_cols):plt.figure(figsize=(12, 6))# 绘制原始分布(仅非缺失值)sns.kdeplot(df[original_col].dropna(), label='Original', linewidth=3)# 绘制各填充方法分布for method in filled_cols:sns.kdeplot(df[method], label=method.replace('_filled_', ' '))plt.title('Distribution Comparison')plt.xlabel(original_col)plt.ylabel('Density')plt.legend()plt.show()# 示例:比较年龄的不同填充结果
compare_distributions(data,original_col='age',filled_cols=['age_filled_mean', 'age_filled_median', 'age_filled_rf']
)
扩展功能:添加Q-Q图比较分位数分布,或箱线图比较统计量。
4.2 统计比较
def comprehensive_stats_compare(df, original_col, filled_cols):stats = pd.DataFrame()# 原始数据统计(仅非缺失值)stats['Original'] = df[original_col].describe()# 各填充方法统计for col in filled_cols:stats[col.replace('_filled_', ' ')] = df[col].describe()# 添加额外指标additional_stats = pd.DataFrame({'Skewness': [df[original_col].skew()] + [df[col].skew() for col in filled_cols],'Kurtosis': [df[original_col].kurtosis()] + [df[col].kurtosis() for col in filled_cols],'Missing %': [df[original_col].isnull().mean()*100] + [0]*len(filled_cols)}, index=['Original'] + [col.replace('_filled_', ' ') for col in filled_cols])return stats.T.join(additional_stats)# 示例:生成详细的统计比较报告
stats_report = comprehensive_stats_compare(data,original_col='income',filled_cols=['income_filled_mean', 'income_filled_median', 'income_filled_lr']
)print(stats_report.round(2))