处理Kaggle太空泰坦尼克数据集时遇AttributeError: ndarray无columns属性
解决Scikit-learn Pipeline中出现的
AttributeError: 'numpy.ndarray' object has no attribute 'columns' 问题描述
处理Kaggle Spaceship Titanic数据集时,使用自定义Scikit-learn Pipeline做数据预处理,触发了上述错误,报错发生在NullImputer的transform方法中——尝试访问X.columns但此时X已经变成了numpy数组,而非预期的Pandas DataFrame。
错误原因
LabelYEncoder类设计错误:它继承了TransformerMixin,但实际作用是处理目标变量y,而非特征数据X。Pipeline中每个步骤的transform方法应接收并返回特征数据(通常是DataFrame),但LabelYEncoder.transform返回的是编码后的y数组,导致后续NullImputer接收到的是数组而非DataFrame,自然没有columns属性。- Pipeline步骤顺序错误:目标变量的编码不应该嵌入特征预处理流程中,特征Pipeline仅负责处理X,y的编码需单独操作。
修复方案
- 移除Pipeline中的
LabelYEncoder步骤,将y的编码从特征预处理流程中分离。 - 单独处理目标变量的编码。
- 配置Scikit-learn强制转换器返回Pandas DataFrame,避免自动转为numpy数组。
修复后完整代码
from sklearn.base import BaseEstimator, TransformerMixin from sklearn.preprocessing import OneHotEncoder, LabelEncoder from sklearn.impute import SimpleImputer from sklearn.pipeline import Pipeline import pandas as pd from sklearn import set_config # 强制Scikit-learn转换器返回Pandas DataFrame,避免自动转为numpy数组 set_config(transform_output="pandas") class Cleaner(BaseEstimator, TransformerMixin): def fit(self, X, y=None): return self def transform(self, X): X = X.drop(["Name", "PassengerId", "FoodCourt", "ShoppingMall", "Cabin"], axis=1) return X class ObjectEncoder(BaseEstimator, TransformerMixin): def fit(self, X, y=None): # 在fit阶段预拟合编码器,符合Scikit-learn API规范 self.columns_to_encode = ["HomePlanet", "CryoSleep", "Destination", "VIP"] self.encoders = {} self.column_names = {} for column in self.columns_to_encode: encoder = OneHotEncoder(sparse_output=False, drop='first') encoder.fit(X[[column]]) self.encoders[column] = encoder self.column_names[column] = [f"{column}_{cat}" for cat in encoder.categories_[0][1:]] return self def transform(self, X): X = X.copy() # 避免修改原始数据 for column in self.columns_to_encode: encoder = self.encoders[column] encoded_df = pd.DataFrame( encoder.transform(X[[column]]), columns=self.column_names[column], index=X.index ) X = pd.concat([X, encoded_df], axis=1) # 删除原分类列 X = X.drop(self.columns_to_encode, axis=1) return X class NullImputer(BaseEstimator, TransformerMixin): def fit(self, X, y=None): self.features_with_missing = X.columns[X.isnull().any()].tolist() self.imputer = SimpleImputer(strategy='median') self.imputer.fit(X[self.features_with_missing]) return self def transform(self, X): X = X.copy() X[self.features_with_missing] = self.imputer.transform(X[self.features_with_missing]) return X # 构建特征预处理Pipeline pipeline = Pipeline([ ("cleaner", Cleaner()), ("object_encoder", ObjectEncoder()), ("null_imputer", NullImputer()), ]) # 分离特征与目标变量(替换为你的目标列名) X_train = strat_train_set.drop("Transported", axis=1) y_train = strat_train_set["Transported"] # 处理特征数据 X_processed = pipeline.fit_transform(X_train) # 单独处理目标变量编码 label_encoder = LabelEncoder() y_processed = label_encoder.fit_transform(y_train)
关键修改说明
- 给
ObjectEncoder补充了fit阶段的编码器拟合逻辑,避免每次transform重复拟合,符合Scikit-learn的API设计规范。 - 在
transform方法中复制输入数据,避免修改原始数据集。 - 使用
set_config(transform_output="pandas")确保所有Scikit-learn转换器返回DataFrame,彻底杜绝自动转为numpy数组的问题。 - 将目标变量的编码从Pipeline中分离,单独处理,明确了特征预处理与目标变量处理的边界。
内容的提问来源于stack exchange,提问作者PRADDYUMN YADAV
相关产品推荐
相关产品推荐

