当你拿到一份新数据时,最常遇到的烦恼是什么?对我来说,最头疼的就是打开数据集发现里面到处都是缺失值。这些讨厌的NaN就像数据里的黑洞,不仅影响分析结果,还会让机器学习模型直接报错。这时候就该SimpleImputer出场了——它是sklearn工具箱里专门对付缺失值的"瑞士军刀"。
我第一次用SimpleImputer是在处理电商用户数据时。那份数据里用户的年龄列有近30%的缺失,当时我手动写了好长一段填充代码,结果发现SimpleImputer三行就搞定了。这个类的基本用法非常简单,核心就是指定两个参数:missing_values告诉它什么是缺失值(默认是numpy.nan),strategy决定怎么填充(均值、中位数、众数或固定值)。
python复制from sklearn.impute import SimpleImputer
import numpy as np
# 模拟一份有缺失值的用户数据
user_data = np.array([
[25, 1, 3000],
[np.nan, 0, 4500],
[32, 1, np.nan],
[28, 0, 6200]
])
# 用均值填充年龄,用众数填充性别,用中位数填充收入
imputer = SimpleImputer(strategy='mean') # 默认处理np.nan
filled_data = imputer.fit_transform(user_data)
实际使用时有个小技巧:如果数据集同时包含数值型和分类型特征,建议对不同类型的列创建多个SimpleImputer实例。比如用户性别适合用most_frequent策略,而收入则适合用median策略抗干扰。这比统一用一种填充策略效果要好得多。
SimpleImputer看似简单,但每个参数背后都有设计考量。先说最容易被忽视的copy参数——默认True会创建数据副本,这在处理大型数据集时可能消耗双倍内存。我第一次处理百万级数据时就因为没注意这个参数导致内存爆满。
strategy参数有四个选项,但实际应用中远不止四种可能:
python复制# 特殊场景:用-1标记缺失的ID值
id_imputer = SimpleImputer(
missing_values=None, # 可以指定多种缺失标记
strategy='constant',
fill_value=-1
)
# 处理文本数据时
text_imputer = SimpleImputer(
strategy='constant',
fill_value='missing'
)
最近我发现add_indicator参数特别有用。当设置为True时,它会给原始数据添加新列来标记哪些位置原本是缺失值。这些标记在后续建模中能帮助算法识别数据缺失模式,我在一个预测项目中加入这个特征后,模型AUC提升了3%。
去年我参与了一个电商价格分析项目,数据情况非常典型:15%的商品缺少价格,30%缺少上架时间,还有各种不规范的缺失标记(比如"暂无"、"-"等)。下面分享我的完整处理流程:
首先创建差异化的填充策略:
python复制price_imputer = SimpleImputer(strategy='median') # 价格用中位数
date_imputer = SimpleImputer(strategy='constant', fill_value='2023-01-01')
category_imputer = SimpleImputer(strategy='most_frequent')
然后用ColumnTransformer构建处理管道:
python复制from sklearn.compose import ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('price', price_imputer, ['price']),
('date', date_imputer, ['date']),
('category', category_imputer, ['category'])
],
remainder='passthrough'
)
处理特殊缺失标记时需要先转换:
python复制import pandas as pd
df = pd.read_csv('ecommerce.csv')
df.replace(['暂无', '-', 'unknown'], np.nan, inplace=True)
最后验证填充效果时,我发现一个常见陷阱:如果某列全部都是缺失值,SimpleImputer会报错。这时需要先检查每列的缺失比例:
python复制missing_ratio = df.isnull().mean()
all_missing_cols = missing_ratio[missing_ratio == 1].index
df.drop(columns=all_missing_cols, inplace=True)
在真实项目中用SimpleImputer,这些经验可能会帮你省下几小时debug时间:
python复制df.replace(['NULL', 'NA', ''], np.nan, inplace=True)
python复制from sklearn.impute import KNNImputer
time_imputer = KNNImputer(n_neighbors=3) # 用邻近时间点填充
python复制original_categories = set(df['category'].dropna())
filled_categories = set(preprocessor.fit_transform(df)['category'])
assert filled_categories.issubset(original_categories)
python复制from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipeline = Pipeline([
('imputer', SimpleImputer(strategy='mean')),
('scaler', StandardScaler())
])
最近遇到的一个坑是:在交叉验证时直接在完整数据上fit,这会导致数据泄露。正确做法是在每个训练fold内部进行fit_transform,就像这样:
python复制from sklearn.model_selection import cross_val_score
# 错误做法:
imputer.fit(X_train)
scores = cross_val_score(model, imputer.transform(X), y)
# 正确做法:
pipeline = Pipeline([('imputer', SimpleImputer()), ('model', model)])
scores = cross_val_score(pipeline, X, y)
当数据量超过内存大小时,常规的SimpleImputer用法就会崩溃。这时有几种解决方案:
python复制import dask.dataframe as dd
ddf = dd.from_pandas(df, npartitions=10)
ddf = ddf.fillna(df.mean()) # dask版的均值填充
python复制imputer = SimpleImputer(strategy='mean')
for chunk in pd.read_csv('huge_file.csv', chunksize=100000):
imputer.partial_fit(chunk)
python复制sample_mean = df.sample(frac=0.1).mean()
df.fillna(sample_mean, inplace=True)
在最近的一个用户画像项目中,我开发了一个动态填充策略:对活跃用户用近期行为均值填充,对沉默用户用全局中位数填充。这种业务逻辑强相关的填充方式,效果比简单统计量要好很多:
python复制def dynamic_impute(row):
if row['last_active_days'] < 30:
return active_user_median[row['feature']]
else:
return global_median[row['feature']]
df['feature'].fillna(df.apply(dynamic_impute, axis=1), inplace=True)
SimpleImputer虽然好用,但有时需要与其他工具配合才能发挥最大效果。比如处理时间序列数据时,我经常先用pandas的插值方法,再用SimpleImputer处理剩余缺失值:
python复制df['sales'] = df['sales'].interpolate(method='time')
imputer = SimpleImputer(strategy='constant', fill_value=0)
df[['sales']] = imputer.fit_transform(df[['sales']])
另一个常用组合是SimpleImputer+Feature-engine。Feature-engine的EndTailImputer可以自动识别数据分布尾部的极端值:
python复制from feature_engine.imputation import EndTailImputer
imputer = EndTailImputer(imputation_method='iqr', tail='right')
df[['income']] = imputer.fit_transform(df[['income']])
在特征工程阶段,我习惯先用SimpleImputer填充缺失值,再用KBinsDiscretizer进行分箱处理。这里要注意处理顺序:
python复制pipeline = Pipeline([
('imputer', SimpleImputer()),
('discretizer', KBinsDiscretizer()),
('scaler', MinMaxScaler())
])
最近在NLP项目中,我发现一个有趣的组合:先用SimpleImputer填充缺失的文本为"missing",再用TfidfVectorizer提取特征。这样模型就能学习到缺失模式的信息:
python复制text_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('tfidf', TfidfVectorizer())
])
不同业务场景下,填充策略的选择逻辑完全不同。在金融风控中,我通常更保守地使用constant 0填充,避免引入虚假信息。而在推荐系统中,用用户历史行为均值填充可能更合理。
一个实际的案例是处理用户评分数据:
python复制# 用户-商品评分矩阵
user_mean = df.mean(axis=1)
item_mean = df.mean(axis=0)
global_median = df.stack().median()
def custom_imputer(row):
if row.user_is_new:
return item_mean[row.item_id]
elif row.item_is_new:
return user_mean[row.user_id]
else:
return global_median
在医疗数据中,缺失本身可能就是重要信息。比如未检测的指标可能表示医生认为不需要检查,这时我会用add_indicator保留缺失模式,而不是简单填充:
python复制medical_imputer = SimpleImputer(
strategy='mean',
add_indicator=True
)
填充缺失值后,如何评估效果?我常用的方法包括:
python复制original_stats = df.describe()
filled_stats = pd.DataFrame(imputed_data).describe()
python复制sample = df.sample(10)
imputed_sample = imputer.transform(sample)
python复制strategies = ['mean', 'median', 'constant']
for strategy in strategies:
pipeline = Pipeline([
('imputer', SimpleImputer(strategy=strategy)),
('model', RandomForestClassifier())
])
scores = cross_val_score(pipeline, X, y)
在生产环境中,我建议设置填充监控:
python复制class MonitoringImputer(SimpleImputer):
def transform(self, X):
imputed = super().transform(X)
self.monitor_fill_rate(X)
return imputed
def monitor_fill_rate(self, X):
fill_rate = np.mean(np.isnan(X))
logging.info(f'Fill rate: {fill_rate:.2%}')
最近我开发了一个自动化测试流程,每次数据更新后自动运行: