我读了https://medium.com/dunder-data/from-pandas-to-scikit-learn-a-new-exciting-workflow-e88e2271ef62这个博客,介绍了scikit中的新事物。带字符串的OneHotEncoder似乎是一个有用的功能。在我尝试使用此功能的下方
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
cols = ['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_df = pd.read_csv('../../data/train.csv', usecols=cols)
test_df = pd.read_csv('../../data/test.csv', usecols=[e for e in cols if e != 'Survived'])
train_df.dropna(inplace=True)
test_df.dropna(inplace=True)
X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test = test_df.copy()
ct = ColumnTransformer([("onehot", OneHotEncoder(sparse=False), ['Sex', 'Embarked'])], remainder='passthrough')
X_train_t = ct.fit_transform(train_df)
X_test_t = ct.fit_transform(test_df)
print(X_train_t[0])
print(X_test_t[0])
# [ 0. 1. 0. 0. 1. 0. 3. 22. 1. 0. 7.25]
# [ 0. 1. 0. 1. 0. 3. 34.5 0. 0. 7.8292]
logreg = LogisticRegression(max_iter=5000)
logreg.fit(X_train_t, Y_train)
Y_pred = logreg.predict(X_test_t) # ValueError: X has 10 features per sample; expecting 11
acc_log = round(logreg.score(X_train, Y_train) * 100, 2)
print(acc_log)
我在使用此代码ValueError: X has 10 features per sample; expecting 11
时遇到python错误,并且还有其他一些担忧。
从头开始..此脚本是为kaggle中的“ titanic”数据集编写的。我们有5个数字列Pclass,Age,SibSp,Parch和Fare。 “性别”和“登载”列是“男性/女性”和“ Q / S / C”类别(这是城市名称的缩写)。
我从OneHotEncoder了解到的是,它通过放置其他列来创建伪变量。好吧,实际上ct.fit_transform()
的输出现在不再是pandas数据帧,而是一个numpy数组。但是从打印调试语句中可以看出,现在的原始列已超过了7列。
我遇到三个问题:
由于某种原因,test.csv的列减少了一列。这将向我表明,其中一种类别的选择较少。为了解决这个问题,我必须在两个训练和测试数据上找到类别中所有可用的选项。然后使用这些选项(例如,男性/女性)分别转换火车和测试数据。我不知道如何使用我正在使用的工具(熊猫,scikit等)来做到这一点。再三考虑..检查了数据之后,我在test.csv中找不到缺少的选项。
我想避免“虚拟变量陷阱”。 https://medium.com/datadriveninvestor/dummy-variable-trap-c6d4a387f10a现在似乎创建了太多列。我原本希望1列用于“性别”(共有2-1条以避免陷入陷阱)和2列。再加上5个数字列,总计8个。
我不再识别转换的输出。我希望有一个新的数据框,其中新的虚拟列已给出了自己的名称,例如Sex_male(1/0)Embarked_Q(1/0)和Embarked_S(1/0)
我只习惯使用gretl,在那里将变量虚拟化,而忽略一个选项是很自然的。我不知道在python中我做错了还是这种情况不是标准scikit工具包的一部分。有什么建议吗?也许我应该为此编写一个自定义编码器?
答案 0 :(得分:10)
我将尝试单独回答您所有的问题。
问题1的答案
在您的代码中,您已经在训练和测试数据中使用了fit_transform
方法,但这并不是正确的方法。通常,fit_transform
仅应用于火车数据集,它返回一个变压器,然后将其用于transform
测试数据集。在测试数据上应用fit_transform
时,仅使用仅在测试数据集中可用的分类变量的选项/级别来转换测试数据,并且很有可能测试数据可能不包含所有所有类别变量的选项/级别,因此,火车和测试数据集的尺寸会有所不同,从而导致出现错误。
所以正确的做法是:
X_train_t = ct.fit_transform(X_train)
X_test_t = ct.transform(X_test)
问题2的答案
如果要避免“虚拟变量陷阱”,可以在{{中创建drop
对象时使用参数first
(通过将其设置为OneHotEncoder
)。 1}},这将导致仅为ColumnTransformer
创建一列,为sex
创建两列,因为它们分别具有两个和三个选项/级别。
所以正确的做法是:
Embarked
问题3的答案
到目前为止,ct = ColumnTransformer([("onehot", OneHotEncoder(sparse=False, drop="first"), ['Sex','Embarked'])], remainder='passthrough')
尚未实现get_feature_names
方法,该方法可以使用新的虚拟列重建数据帧。解决此问题的一种方法是在sklearn
构造中将reminder
更改为drop
并分别构造数据帧,如下所示:
ColumnTransformer
这将导致如下结果:
您的最终代码将如下所示:
ct = ColumnTransformer([("onehot", OneHotEncoder(sparse=False, drop="first"), ['Sex', 'Embarked'])], remainder='drop')
A = pd.concat([X_train.drop(["Sex", "Embarked"], axis=1), pd.DataFrame(X_train_t, columns=ct.get_feature_names())], axis=1)
A.head()
当您import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
cols = ['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_df = pd.read_csv('train.csv', usecols=cols)
test_df = pd.read_csv('test.csv', usecols=[e for e in cols if e != 'Survived'])
cols = ['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_df = train_df.dropna()
test_df = test_df.dropna()
train_df = train_df.reset_index(drop=True)
test_df = test_df.reset_index(drop=True)
X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test = test_df.copy()
categorical_values = ['Sex', 'Embarked']
X_train_cont = X_train.drop(categorical_values, axis=1)
X_test_cont = X_test.drop(categorical_values, axis=1)
ct = ColumnTransformer([("onehot", OneHotEncoder(sparse=False, drop="first"), categorical_values)], remainder='drop')
X_train_categorical = ct.fit_transform(X_train)
X_test_categorical = ct.transform(X_test)
X_train_t = pd.concat([X_train_cont, pd.DataFrame(X_train_categorical, columns=ct.get_feature_names())], axis=1)
X_test_t = pd.concat([X_test_cont, pd.DataFrame(X_test_categorical, columns=ct.get_feature_names())], axis=1)
logreg = LogisticRegression(max_iter=5000)
logreg.fit(X_train_t, Y_train)
Y_pred = logreg.predict(X_test_t)
acc_log = round(logreg.score(X_train_t, Y_train) * 100, 2)
print(acc_log)
80.34
时,您会得到
希望这会有所帮助!
答案 1 :(得分:4)
ct.fit(X_complete)
X_train_t, X_test_t = ct.transform(X_test), ct.transform(X_test)
是的,使用drop='first'
可以解决此问题。同时,请记住,对于诸如神经网络甚至决策树之类的非线性模型来说,多重共线性问题并不是什么大问题。我认为这就是为什么不将其保留为默认arg参数值的原因。
get_feature_names
并未针对sklearn中的管道和其他内容进行详尽的实现。因此,他们也支持ColumnTransformer
中的complete。
根据我的经验,我为ColumnTransfomer
构建了这个包装器,即使它具有pipelines
或reminder=passthrough
也可以支持。
这也为get_feature_names
选择了功能名称,而不是将其称为x0, x1
,因为我们知道ColumnTransformer
中使用_feature_names_in
的实际列名称。
from sklearn.compose import ColumnTransformer
from sklearn.utils.validation import check_is_fitted
def _get_features_out(name, trans, features_in):
if hasattr(trans, 'get_feature_names'):
return [name + "__" + f for f in
trans.get_feature_names(features_in)]
else:
return features_in
class NamedColumnTransformer(ColumnTransformer):
def get_feature_names(self):
check_is_fitted(self)
feature_names = []
for name, trans, features, _ in self._iter(fitted=True):
if trans == 'drop':
continue
if trans == 'passthrough':
feature_names.extend(self._feature_names_in[features])
elif hasattr(trans, '_iter'):
for _, op_name, t in trans._iter():
features=_get_features_out(op_name, t, features)
feature_names.extend(features)
elif not hasattr(trans, 'get_feature_names'):
raise AttributeError("Transformer %s (type %s) does not "
"provide get_feature_names."
% (str(name), type(trans).__name__))
else:
feature_names.extend(_get_features_out(name, trans, features))
return feature_names
现在,例如
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
# you can fetch the titanic dataset using this
X, y = fetch_openml("titanic", version=1,
as_frame=True, return_X_y=True)
# removing the columns which you are not using
X.drop(['name', 'ticket', 'cabin', 'boat', 'body', 'home.dest'],
axis=1, inplace=True)
X.dropna(inplace=True)
X.reset_index(drop=True, inplace=True)
y = y[X.index]
categorical_values = ['sex', 'embarked']
ct = NamedColumnTransformer([("onehot", OneHotEncoder(
sparse=False, drop="first"), categorical_values)], remainder='passthrough')
clf = Pipeline(steps=[('preprocessor', ct),
('classifier', LogisticRegression(max_iter=5000))])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
clf.fit(X_train, y_train)
clf[0].get_feature_names()
# ['onehot__sex_male',
# 'onehot__embarked_Q',
# 'onehot__embarked_S',
# 'pclass',
# 'age',
# 'sibsp',
# 'parch',
# 'fare']
pd.DataFrame(clf[0].transform(X_train), columns=clf[0].get_feature_names())
您也可以尝试NamedColumnTransformer
来获得ColumnTransformer
here的更有趣的示例。
答案 2 :(得分:-1)
您的问题不在编码中,它可以正常工作,请参见
print(X_train_t[0])
print(X_test_t[0])
# [ 0. 1. 0. 0. 1. 0. 3. 22. 1. 0. 7.25]
# [ 0. 1. 0. 1. 0. 3. 34.5 0. 0. 7.8292]
在性别/内隐变量中不再有男/女或南安普敦/瑟堡/皇后镇的地方,而不再是数字值。
注意::X_train_t
有11列,而X_test_t
有10列。
您的问题在这里
train_df = pd.read_csv('../../data/train.csv', usecols=cols)
test_df = pd.read_csv('../../data/test.csv', usecols=[e for e in cols if e != 'Survived'])
您应该同时删除Survived
和test_df
中的train_df
列,就像这样:
train_df = pd.read_csv('../../data/train.csv', usecols=[e for e in cols if e != 'Survived'])
test_df = pd.read_csv('../../data/test.csv', usecols=[e for e in cols if e != 'Survived'])
,因此两个数据框只有10列。 Survived
列是您的Y
,位于Y_train
中。
Y_pred
将正确地作为您可以与真实测试数据进行比较的预测:
Y_pred = logreg.predict(X_test_t)
Y_test = test_df["Survived"]
print(Y_pred)
print(Y_test)
print(Y_pred == Y_test)
[1 1 1 0 ... 0 1]
[0 0 1 0 ... 0 1]
[False False True True ... True True]