sklearn OneHotEncoder工作原理及泰坦尼克数据集编码疑问
我最近在泰坦尼克数据集的pandas DataFrame子集上使用sklearn的OneHotEncoder,根据官方文档,默认auto模式会自动从训练数据推导类别,不需要手动指定。于是我写了这段代码:
print(x_train.head()) enc = OneHotEncoder(handle_unknown="ignore") print("_____________") print(x_train.shape) x_train = enc.fit_transform(x_train) print(x_train.shape) print(x_train.toarray())
得到的输出是:
Pclass Sex Age SibSp Parch Fare Cabin Embarked 845 3 male 42.000000 0 0 7.5500 None S 162 3 male 26.000000 0 0 7.7750 None S 630 1 male 80.000000 0 0 30.0000 A23 S 176 3 male 29.699118 3 1 25.4667 None S 115 3 male 21.000000 0 0 7.9250 None S _____________ (712, 8) (712, 460) [[0. 0. 1. ... 0. 0. 1.] [0. 0. 1. ... 0. 0. 1.] [1. 0. 0. ... 0. 0. 1.] ... [0. 0. 1. ... 0. 0. 1.] [1. 0. 0. ... 0. 0. 1.] [0. 0. 1. ... 0. 0. 1.]]
我能看到特征数从8变成了460,符合独热编码的预期,但我有几个疑问:
- 编码器是对所有特征都进行了独热编码吗?
- 如果是的话,像
Age这种虽然有有限取值但本质是连续变量的特征,被编码难道不是问题吗? - 另外,像
Pclass这种类型是int但实际是分类变量的特征,编码器会怎么处理?
问题拆解与解答
1. 编码器是否对所有特征都做了独热编码?
没错,OneHotEncoder默认会对输入的所有特征进行编码——它不会自动识别特征的语义类型,只看输入的数据结构。你输入的是包含8列的DataFrame,它就会把每一列都当作“类别特征”处理,这就是特征数暴涨到460的核心原因:比如Cabin列有大量唯一值,每个值都会被拆成一个新的二进制特征,加上其他列的类别总数,最终就到了460。
2. 连续变量被独热编码的问题
这确实是个严重的问题!像Age、Fare这种连续变量,独热编码会把每个不同数值都当成独立类别,不仅会产生大量冗余特征,还会破坏变量的连续性(比如30岁和31岁本应比30岁和60岁更相似,但独热编码后它们是完全孤立的特征),会直接影响模型的学习效果和训练效率。
3. 类型为int的分类变量(如Pclass)的处理
OneHotEncoder完全不关心特征的pandas数据类型(int、object等),只要是输入的特征列,它都会按唯一值生成类别。比如Pclass的取值是1、2、3,编码器会把它拆成3个二进制特征,分别对应“是否是1等舱”“是否是2等舱”“是否是3等舱”——这其实符合分类变量的编码需求,但前提是你确实把它当作分类变量处理;如果想保留它的有序性(比如1等舱>2等舱>3等舱),那更适合用OrdinalEncoder。
正确的做法:指定需要编码的特征
要解决这个问题,你需要明确区分分类特征和连续特征,针对性处理,sklearn推荐两种方式:
方法一:用ColumnTransformer分特征处理
这是最规范的做法,可以给不同类型特征绑定不同预处理逻辑:
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler # 定义分类特征和连续特征列名 categorical_features = ['Pclass', 'Sex', 'Embarked', 'Cabin'] numeric_features = ['Age', 'SibSp', 'Parch', 'Fare'] # 构建预处理管道 preprocessor = ColumnTransformer( transformers=[ ('cat', OneHotEncoder(handle_unknown="ignore"), categorical_features), ('num', StandardScaler(), numeric_features) ]) # 拟合并转换训练数据 x_train_processed = preprocessor.fit_transform(x_train)
这样分类特征会被独热编码,连续特征会被标准化(你也可以换成其他归一化方法)。
方法二:单独提取分类特征编码后合并
如果你只想对分类特征编码,可以先拆分特征,编码后再合并:
# 提取分类特征 cat_x_train = x_train[['Pclass', 'Sex', 'Embarked', 'Cabin']] # 提取连续特征 num_x_train = x_train[['Age', 'SibSp', 'Parch', 'Fare']] # 编码分类特征 enc = OneHotEncoder(handle_unknown="ignore") cat_x_train_encoded = enc.fit_transform(cat_x_train) # 合并编码后的分类特征与连续特征 import scipy.sparse x_train_processed = scipy.sparse.hstack([cat_x_train_encoded, num_x_train])
额外小技巧:查看编码器生成的类别
如果你想确认编码器到底生成了哪些类别,可以用enc.categories_属性查看:
enc = OneHotEncoder(handle_unknown="ignore") enc.fit(cat_x_train) # 打印每个分类特征对应的类别 for col, categories in zip(categorical_features, enc.categories_): print(f"特征 {col} 的类别: {categories}")
内容的提问来源于stack exchange,提问作者jgklsdjfgkldsfaSDF

