如何为RandomForest编码非序数分类变量(不使用LabelEncoder)
针对非序数高基数分类变量的RandomForest优化方案
你遇到的问题本质是高基数非序数分类变量的编码适配问题,同时要兼顾模型效果和部分依赖图(PDP)的生成,以下是针对性的解决思路:
1. 修复目标编码的PDP异常:用正则化/留一法编码
普通目标编码容易过拟合,导致编码值波动剧烈,PDP无法正常生成。试试用留一法目标编码(避免数据泄露)或者带平滑的均值编码:
from category_encoders import LeaveOneOutEncoder from sklearn.ensemble import RandomForestClassifier # 初始化编码器,指定要编码的列 encoder = LeaveOneOutEncoder(cols=['CountryOfExploitation', 'gender', 'citizenship']) # 用留一法编码,训练时不泄露当前样本的目标信息 df_encoded = encoder.fit_transform(df, df['exploitation_type']) # 训练随机森林 rf = RandomForestClassifier(n_estimators=100, random_state=42) rf.fit(df_encoded.drop('exploitation_type', axis=1), df['exploitation_type'])
留一法编码后的数值连续且稳定,不会出现PDP渲染异常的情况,同时解决了LabelEncoder的序数假设问题。
2. 拯救独热编码:类别合并+特征筛选
独热编码后特征爆炸导致模型效果差?先做两步优化:
- 合并低频次类别:把样本量极少的国家归为“其他”,减少特征维度
# 统计国家出现次数,合并频次低于50的类别(阈值根据你的数据调整) country_counts = df['CountryOfExploitation'].value_counts() low_freq_countries = country_counts[country_counts < 50].index df['CountryOfExploitation'] = df['CountryOfExploitation'].replace(low_freq_countries, 'Other') - 筛选重要特征:用随机森林的特征重要性过滤冗余的独热特征
from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.feature_selection import SelectFromModel # 独热编码分类变量 ct = ColumnTransformer( [('onehot', OneHotEncoder(sparse_output=False, drop='first'), ['CountryOfExploitation', 'gender', 'citizenship'])], remainder='passthrough' ) X_encoded = ct.fit_transform(df.drop('exploitation_type', axis=1)) y = df['exploitation_type'] # 用临时随机森林筛选特征 rf_temp = RandomForestClassifier(n_estimators=100, random_state=42) rf_temp.fit(X_encoded, y) selector = SelectFromModel(rf_temp, threshold='median') X_selected = selector.fit_transform(X_encoded, y) # 训练最终模型 rf_final = RandomForestClassifier(n_estimators=100, random_state=42) rf_final.fit(X_selected, y)
优化后的独热编码特征维度大幅降低,模型效果会提升,同时生成PDP也不会因为特征太多报错。
3. 正确生成PDP:映射回原始类别
不管用哪种编码,生成PDP时要把编码值映射回原始类别,不然x轴显示的是编码后的数字毫无意义:
from sklearn.inspection import plot_partial_dependence import matplotlib.pyplot as plt # 以留一法编码为例,获取原始类别和编码值的映射 country_mapping = encoder.mapping['CountryOfExploitation']['mapping'] # 反转映射,得到编码值到原始国家的对应关系 code_to_country = {v: k for k, v in country_mapping.items()} # 生成PDP fig, ax = plt.subplots(figsize=(12, 6)) plot_partial_dependence(rf, df_encoded.drop('exploitation_type', axis=1), features=['CountryOfExploitation'], ax=ax) # 修改x轴标签为原始国家名称 ax.set_xticks(list(code_to_country.keys())) ax.set_xticklabels(list(code_to_country.values()), rotation=45) plt.tight_layout() plt.show()
4. 偷懒方案:用原生支持分类变量的树模型
不想折腾编码?直接用CatBoost或LightGBM,它们能直接处理非序数分类变量,不用手动编码,生成PDP也更简单:
import catboost as cb # 构造CatBoost数据集,指定分类特征列 train_pool = cb.Pool( data=df.drop('exploitation_type', axis=1), label=df['exploitation_type'], cat_features=['CountryOfExploitation', 'gender', 'citizenship'] ) # 训练模型 cat_model = cb.CatBoostClassifier(iterations=100, random_state=42, verbose=0) cat_model.fit(train_pool) # 直接生成PDP,自动用原始类别显示 cat_model.plot_partial_dependence(features=['CountryOfExploitation'], data=train_pool) plt.show()
这种方案最省心,模型对高基数分类变量的处理效率也比RandomForest更高。
内容的提问来源于stack exchange,提问作者tswift1998
相关产品推荐
相关产品推荐

