示例

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

更多推荐