One-Hot编码应在Train/Test Split前还是后?案例解析
我在纠结One-Hot Encoding(哑变量创建)应该在Train/Test Split前还是后执行,看到的观点互相矛盾:有人说提前编码会导致数据泄漏,但也有行业案例显示全量数据提前编码是惯例,Kaggle上则建议拆分后编码避免泄漏。
我的问题:
- 到底应该在Train/Test Split前还是后执行One-Hot Encoding?
- 下面的案例里数据泄漏发生在什么地方?
示例数据
我们有两列数据:web_views(目标变量)和website(非序数分类特征,假设不删除哑变量),数据框代码如下:
import pandas as pd from sklearn.model_selection import train_test_split import numpy as np df = pd.DataFrame({'web_views': [100,200,300,400], 'website': ['Youtube','Facebook','Instagram', 'Google']})
场景1:拆分前执行One-Hot Encoding
np.random.seed(123) df_before_split = pd.concat([df.drop('website', axis = 1), pd.get_dummies(df['website'])], axis=1) # 创建X和y数据框 X_before_split = df_before_split.drop('web_views', axis = 1) y_before_split = df_before_split['web_views'] # 执行训练测试拆分 X_train_before_split, X_test_before_split, y_train_before_split, y_test_before_split = train_test_split(X_before_split, y_before_split, test_size = 0.20)
查看拆分后的数据集:
# 查看拆分前编码的训练集 X_train_before_split # 查看拆分前编码的测试集 X_test_before_split
场景2:拆分后执行One-Hot Encoding
# 先执行训练测试拆分再编码 X = df.drop('web_views', axis = 1) y = df['web_views'] # 执行数据拆分 np.random.seed(123) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20) # 对训练集和测试集分别编码 X_train = pd.concat([X_train.drop('website', axis = 1), pd.get_dummies(X_train['website'])], axis=1) X_test = pd.concat([X_test.drop('website', axis = 1), pd.get_dummies(X_test['website'])], axis=1)
查看编码后的数据集:
# 拆分后编码的训练集 X_train # 拆分后编码的测试集 X_test
线性回归建模验证
导入线性回归模型:
from sklearn.linear_model import LinearRegression
拆分前编码的模型
regressor_before_split = LinearRegression() regressor_before_split.fit(X_train_before_split, y_train_before_split) y_pred_before_split = regressor_before_split.predict(X_test_before_split) y_pred_before_split
该模型返回符合预期的预测值。
拆分后编码的模型
regressor_after_split = LinearRegression() regressor_after_split.fit(X_train, y_train) y_pred_after_split = regressor_after_split.predict(X_test) y_pred_after_split
场景2报错信息
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-92-c63978a198c8> in <module>() 2 regressor_after_split.fit(X_train, y_train) 3 ----> 4 y_pred_after_split = regressor_after_split.predict(X_test) 5 y_pred_after_split C:\Anaconda3\lib\site-packages\sklearn\linear_model\base.py in predict(self, X) 254 Returns predicted values. 255 """ --> 256 return self._decision_function(X) 257 258 _preprocess_data = staticmethod(_preprocess_data) C:\Anaconda3\lib\site-packages\sklearn\linear_model\base.py in _decision_function(self, X) 239 X = check_array(X, accept_sparse=['csr', 'csc', 'coo']) 240 return safe_sparse_dot(X, self.coef_.T, --> 241 dense_output=True) + self.intercept_ 242 243 def predict(self, X): C:\Anaconda3\lib\site-packages\sklearn\utils\extmath.py in safe_sparse_dot(a, b, dense_output) 138 return ret 139 else: --> 140 return np.dot(a, b) 141 142 <__array_function__ internals> in dot(*args, **kwargs) ValueError: shapes (1,1) and (3,) not aligned: 1 (dim 1) != 3 (dim 0)
我的思考
- 拆分前编码可确保测试集与训练集特征维度一致,模型能正常预测;而拆分后编码会导致测试集与训练集特征维度不匹配(如示例中测试集仅1个特征,训练集有3个)。
- 是否我在某个环节引入了数据泄漏?
解答
问题1:编码时机的正确选择
正确的做法是先拆分数据集,再基于训练集的信息做One-Hot Encoding,然后用训练集的编码规则去转换测试集。
为什么不能拆分前编码?因为提前对全量数据编码,相当于测试集的类别信息被提前暴露给了训练过程——虽然One-Hot本身不会像均值/标准差那样直接泄露统计信息,但如果测试集中存在训练集没有的类别,提前编码会让模型“提前知道”这些类别存在,破坏了测试集作为“未知数据”的独立性。不过在一些简单场景(比如所有类别在训练集里都完整覆盖),提前编码看似没问题,但这是不规范的,一旦遇到测试集有新类别就会出问题,而且不符合机器学习“训练-验证”的核心逻辑。
问题2:案例中的数据泄漏点
场景1中,拆分前对全量数据做One-Hot Encoding,相当于让训练集间接获取了测试集的类别分布信息。比如你的示例里,测试集包含的类别(如Google)在全量编码时被创建了对应的哑变量,训练集的特征空间包含了这个测试集独有的类别特征,这就属于数据泄漏——模型本应该只基于训练数据学习,却提前接触到了测试数据的类别信息。
场景2报错的原因及解决方法
场景2报错是因为你分别对训练集和测试集独立做了One-Hot Encoding,导致两者的特征维度不匹配。正确的做法是用训练集拟合一个One-Hot编码器,再用这个编码器去转换测试集,确保特征维度一致:
修改后的场景2代码:
# 先拆分数据集 X = df.drop('web_views', axis = 1) y = df['web_views'] np.random.seed(123) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20) # 使用sklearn的OneHotEncoder(推荐),基于训练集拟合 from sklearn.preprocessing import OneHotEncoder encoder = OneHotEncoder(sparse_output=False, drop=None) encoder.fit(X_train) # 转换训练集和测试集 X_train_encoded = encoder.transform(X_train) X_test_encoded = encoder.transform(X_test) # 转换为DataFrame方便查看(可选) X_train_df = pd.DataFrame(X_train_encoded, columns=encoder.get_feature_names_out()) X_test_df = pd.DataFrame(X_test_encoded, columns=encoder.get_feature_names_out())
这样处理后,训练集和测试集的特征维度完全一致,模型就能正常预测了。
总结
- 永远遵循“先拆分,再基于训练集做特征工程”的原则,避免数据泄漏
- 不要独立对训练/测试集做编码,要用训练集拟合的编码器统一转换,保证特征维度一致
- 行业中所谓的“全量提前编码”往往是在数据预处理阶段做了类别对齐(比如确保训练/测试集类别完全一致),但这不是通用做法,规范流程还是拆分后编码
内容的提问来源于stack exchange,提问作者Beans On Toast

