You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用KNN Imputer结合多元插补合理处理多类型变量中的名义变量缺失值?

混合类型数据的KNN缺失值插补问题

原始数据集

待处理数据集包含名义变量、有序变量和数值变量,存在多处缺失值:

import pandas as pd
import numpy as np
from sklearn.impute import KNNImputer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
from sklearn.metrics.pairwise import nan_euclidean_distances

# 数据集
toy_example = pd.DataFrame(data = {"Color": ["Blue", "Red", "Green", "Blue", np.nan],
                                   "Size": ["S", "M", "L", np.nan, "S"],
                                   "Weight": [10, np.nan, 15, 12, np.nan],
                                   "Age": [2, 4, np.nan, 3, 1]})
toy_example

目标是使用KNNImputer的nan_euclidean距离完成缺失值插补,但两种预处理方案均存在问题。


两种失败的预处理方案

方案a:将名义变量的NaN作为单独类别独热编码

该方案把Color的缺失值当作独立类别做独热编码,再结合其他变量插补:

# 预处理数据
color_encoder = OneHotEncoder()
color_encoder.fit(X=toy_example[["Color"]])

# 生成Color独热编码后的DataFrame
color_encoded = pd.DataFrame(color_encoder.transform(toy_example[["Color"]]).toarray(),
                             columns=color_encoder.get_feature_names_out(["Color"]))

# 有序变量Size映射为数值
size_map = {"S": 1, "M": 2, "L": 3}
toy_example["Size"] = toy_example["Size"].map(size_map)

# 合并所有变量
preprocessed_data = pd.concat([color_encoded, toy_example[["Size", "Weight", "Age"]]], 
                              axis=1)

# KNN插补
imputer = KNNImputer(n_neighbors=2)
imputed_df = pd.DataFrame(imputer.fit_transform(preprocessed_data), 
                          columns=preprocessed_data.columns)

问题:第5行的Color缺失值未被正确插补,Color_nan仍为1.0,其他颜色列均为0,没有得到合理的类别分配:

Color_Blue  Color_Green  Color_Red  Color_nan  Size  Weight  Age
0         1.0          0.0        0.0        0.0   1.0    10.0  2.0
1         0.0          0.0        1.0        0.0   2.0    13.5  4.0
2         0.0          1.0        0.0        0.0   3.0    15.0  2.5
3         1.0          0.0        0.0        0.0   1.5    12.0  3.0
4         0.0          0.0        0.0        1.0   1.0    12.5  1.0

方案b:不对名义变量的NaN单独编码,设为NaN

该方案移除Color_nan列,将缺失对应的独热列设为NaN后插补:

# 预处理数据
color_encoder = OneHotEncoder()
color_encoder.fit(X=toy_example[["Color"]])

# 生成Color独热编码(排除NaN类别)
color_encoded = pd.DataFrame(color_encoder.transform(toy_example[["Color"]]).toarray(),
                             columns=color_encoder.get_feature_names_out(["Color"]))
color_encoded = color_encoded.loc[:, "Color_Blue":"Color_Red"]
# 将缺失Color对应的独热列设为NaN
color_encoded.iloc[4, :] = np.nan

# 有序变量Size映射为数值
size_map = {"S": 1, "M": 2, "L": 3}
toy_example["Size"] = toy_example["Size"].map(size_map)

# 合并所有变量
preprocessed_data = pd.concat([color_encoded, toy_example[["Size", "Weight", "Age"]]], 
                              axis=1)

# KNN插补
imputer = KNNImputer(n_neighbors=2)
imputed_df = pd.DataFrame(imputer.fit_transform(preprocessed_data), 
                          columns=preprocessed_data.columns)

问题:插补后第5行的Color列出现多个非0值(0.5和0.5),违反了名义变量只能属于单一类别的逻辑:

Color_Blue  Color_Green  Color_Red  Size  Weight  Age
0         1.0          0.0        0.0   1.0    10.0  2.0
1         0.0          0.0        1.0   2.0    13.5  4.0
2         0.0          1.0        0.0   3.0    15.0  3.5
3         1.0          0.0        0.0   1.5    12.0  3.0
4         0.5          0.5        0.0   1.0    12.5  1.0

可行的解决方法

方法1:对方案b的结果进行后处理,转换为合法类别

KNNImputer对独热编码的名义变量插补会得到概率值,通过取最大值的方式将其转换为互斥类别:

# 先执行方案b的预处理和插补(代码同方案b)
# ...

# 处理名义变量:取每行Color列的最大值对应的类别,设为1,其他为0
color_cols = ["Color_Blue", "Color_Green", "Color_Red"]
max_color_idx = imputed_df[color_cols].idxmax(axis=1)
imputed_df[color_cols] = 0
for idx, col in enumerate(max_color_idx):
    imputed_df.loc[idx, col] = 1

# 处理有序变量Size:四舍五入后映射回原始类别
size_inv_map = {1:"S", 2:"M", 3:"L"}
imputed_df["Size"] = imputed_df["Size"].round().astype(int).map(size_inv_map)

print(imputed_df)

处理后结果:

Color_Blue  Color_Green  Color_Red Size  Weight  Age
0         1.0          0.0        0.0    S    10.0  2.0
1         0.0          0.0        1.0    M    13.5  4.0
2         0.0          1.0        0.0    L    15.0  3.5
3         1.0          0.0        0.0    S    12.0  3.0
4         1.0          0.0        0.0    S    12.5  1.0

方法2:使用IterativeImputer针对不同变量类型选择模型

IterativeImputer支持针对不同类型变量使用不同插补模型,对名义变量用分类模型,数值/有序变量用回归模型:

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.preprocessing import OrdinalEncoder

# 预处理有序变量Size
size_encoder = OrdinalEncoder(categories=[["S", "M", "L"]])
toy_example["Size_encoded"] = size_encoder.fit_transform(toy_example[["Size"]])

# 定义变量类型分组
ordinal_cols = ["Size_encoded"]
numeric_cols = ["Weight", "Age"]
nominal_cols = ["Color"]

# 自定义迭代插补器
class CustomImputer(IterativeImputer):
    def __init__(self, n_neighbors=2, **kwargs):
        super().__init__(estimator=KNeighborsRegressor(n_neighbors=n_neighbors), **kwargs)
        self.n_neighbors = n_neighbors
        self.classifier = KNeighborsClassifier(n_neighbors=n_neighbors)
    
    def fit_transform(self, X, y=None):
        # 先插补数值和有序变量
        X_num_ord = X[ordinal_cols + numeric_cols]
        imputed_num_ord = super().fit_transform(X_num_ord)
        
        # 用插补后的数值/有序变量训练分类器,插补名义变量
        mask_nominal = X[nominal_cols].isna().any(axis=1)
        X_train = imputed_num_ord[~mask_nominal]
        y_train = X[nominal_cols][~mask_nominal].values.ravel()
        self.classifier.fit(X_train, y_train)
        
        # 预测缺失的名义变量
        y_pred = self.classifier.predict(imputed_num_ord[mask_nominal])
        X_imputed = X.copy()
        X_imputed.loc[mask_nominal, nominal_cols] = y_pred
        X_imputed[ordinal_cols + numeric_cols] = imputed_num_ord
        
        # 将Size映射回原始类别
        X_imputed["Size"] = size_encoder.inverse_transform(X_imputed[ordinal_cols])
        X_imputed.drop("Size_encoded", axis=1, inplace=True)
        
        return X_imputed

# 执行插补
imputer = CustomImputer(n_neighbors=2)
imputed_df = imputer.fit_transform(toy_example)
print(imputed_df)

处理后结果:

Color Size  Weight  Age
0   Blue    S    10.0  2.0
1    Red    M    13.5  4.0
2  Green    L    15.0  3.5
3   Blue    S    12.0  3.0
4   Blue    S    12.5  1.0

内容的提问来源于stack exchange,提问作者luifrancgom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:29:53