机器学习特征预处理之分类缺失填充
·
示例
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
def categorical_imputation(X_train, X_test, cat_cols,
strategy='most_frequent',
fill_value=None,
verbose=True):
"""
分类缺失填充 - 必须在编码前执行!
"""
if not cat_cols:
return X_train, X_test
# ⭐ 关键修复:统一将 None 转换为 np.nan
X_train = X_train.replace({None: np.nan})
X_test = X_test.replace({None: np.nan})
def is_categorical_column(col):
if col not in X_train.columns:
return False
if (pd.api.types.is_object_dtype(X_train[col]) or
pd.api.types.is_categorical_dtype(X_train[col]) or
pd.api.types.is_string_dtype(X_train[col])):
return True
if pd.api.types.is_integer_dtype(X_train[col]):
n_unique = X_train[col].nunique()
n_total = len(X_train[col])
if n_total > 0 and n_unique / n_total < 0.05:
if verbose:
print(f" ℹ️ '{col}' 识别为整数编码分类 (唯一值: {n_unique})")
return True
return False
valid_cat_cols = [col for col in cat_cols if is_categorical_column(col)]
ignored_cols = set(cat_cols) - set(valid_cat_cols)
if ignored_cols and verbose:
print(f" ⚠️ 忽略以下列: {ignored_cols}")
if not valid_cat_cols:
if verbose:
print(f" ⚠️ 没有有效的分类列需要填充")
return X_train, X_test
valid_strategies = ['most_frequent', 'constant']
if strategy not in valid_strategies:
if verbose:
print(f" ⚠️ 策略 '{strategy}' 不适用于分类数据,切换为 'most_frequent'")
strategy = 'most_frequent'
if strategy == 'constant' and fill_value is None:
fill_value = 'UNKNOWN'
if verbose:
print(f" ℹ️ fill_value未指定,使用默认值: '{fill_value}'")
for col in valid_cat_cols:
train_vals = set(X_train[col].dropna().unique())
test_vals = set(X_test[col].dropna().unique())
new_cats = test_vals - train_vals
if new_cats and verbose:
print(f" ⚠️ 测试集新类别 '{col}': {new_cats}")
try:
if strategy == 'constant':
imputer = SimpleImputer(strategy=strategy, fill_value=fill_value)
else:
imputer = SimpleImputer(strategy=strategy)
X_train.loc[:, valid_cat_cols] = imputer.fit_transform(X_train[valid_cat_cols])
X_test.loc[:, valid_cat_cols] = imputer.transform(X_test[valid_cat_cols])
if verbose:
print(f" ✅ 分类填充完成: {len(valid_cat_cols)} 列 (策略: {strategy})")
except Exception as e:
print(f" ❌ 分类填充失败: {e}")
print(f" 问题列: {valid_cat_cols}")
print(f" 数据类型: {X_train[valid_cat_cols].dtypes}")
raise
return X_train, X_test
调用示例
print("=" * 60)
print("测试场景1:基本众数填充")
print("=" * 60)
# 创建训练集
X_train = pd.DataFrame({
'color': ['red', 'blue', None, 'red', 'green', None],
'size': ['M', 'L', 'M', None, 'S', 'M'],
'category': ['A', 'B', 'A', 'C', None, 'A'],
'price': [100, 200, 150, 300, 250, 180] # 数值列
})
# 创建测试集
X_test = pd.DataFrame({
'color': ['blue', None, 'red'],
'size': ['L', 'M', None],
'category': ['B', 'C', 'A'],
'price': [220, 190, 210]
})
print("原始训练集:")
print(X_train)
print("\n原始测试集:")
print(X_test)
# 执行填充
cat_cols = ['color', 'size', 'category']
X_train_filled, X_test_filled = categorical_imputation(
X_train, X_test, cat_cols, strategy='most_frequent'
)
print("\n填充后的训练集:")
print(X_train_filled)
print("\n填充后的测试集:")
print(X_test_filled)
# 验证填充结果
print("\n验证填充结果:")
for col in cat_cols:
mode_val = X_train[col].mode()[0]
print(f" {col}: 众数 = '{mode_val}'")
print(f" 训练集缺失值: {X_train[col].isna().sum()} → 填充为 '{mode_val}'")
print(f" 测试集缺失值: {X_test[col].isna().sum()} → 填充为 '{mode_val}'")
数据缺失填充策略
1. Mean(均值)
-
操作:用该特征(列)的算术平均数来填充缺失值。
-
适用场景:数据呈正态分布或对称分布,且没有明显极端值时。
-
优点:计算简单,能保持数据的整体均值不变。
-
致命缺点:极易受异常值影响。如果数据里有极端大或极端小的数,均值会被“拉偏”,填充出来的结果可能失真。
2. Median(中位数)
-
操作:用该特征(列)的中位数(即按大小排序后位于中间的那个数)来填充缺失值。
-
适用场景:数据分布明显偏斜(如收入数据,大部分人收入低,极少数人收入极高),或者存在异常值时。
-
优点:稳健性极强。因为只看位置,所以极端值的大小对中位数毫无影响,比均值更“抗造”。
3. Most_frequent(众数)
-
操作:用该特征(列)中出现频率最高的那个值来填充缺失值。
-
适用场景:专门用于分类(离散型)数据,比如性别(男/女)、颜色(红/蓝/绿)。
-
注意:如果缺失值很多,或者众数占比不高,填充后可能会过度强化该类别,导致数据多样性降低。在连续型数据中基本不用这个方法。
4. Constant(常量)
-
操作:用一个自定义的固定值(通常是“0”、“未知”、“-999”或“None”)来填充所有缺失值。
-
适用场景:
-
业务上缺失就是有特殊含义的(比如“该用户未消费”,金额填0);
-
作为机器学习中的占位符,告诉算法“这个值是人为补的,不代表真实水平”。
-
-
缺点:如果随意填一个毫无意义的常数(比如用0填年龄),会严重干扰数据分析,必须谨慎使用。
简单粗暴的速记口诀
-
数据像钟形曲线(对称)→ 用 Mean
-
数据有暴发户(极端值)→ 用 Median
-
数据是类别标签(男女/颜色)→ 用 Most_frequent
-
数据缺失本身就是信息(填无/填0)→ 用 Constant
更多推荐
所有评论(0)