决策树特征#

KDD 2009 竞赛的获胜者观察到,许多特征与目标具有高互信息但低相关性,这使他们得出结论,这些关系是非线性的。虽然非线性关系可以通过非线性模型捕捉,但要利用这些特征的信息进行线性模型建模,我们需要以某种方式将这些信息转换为与目标的线性或单调关系。

决策树的输出,即它们的预测,如果树拟合良好,应该与目标单调相关。

此外,基于2个或更多特征训练的决策树可以捕捉到更简单模型会遗漏的特征交互。

通过用决策树预测结果产生的特征来丰富数据集,我们可以创建性能更好的模型。但缺点是,决策树产生的特征不容易解释或说明。

DecisionTreeFeatures() 创建并添加由基于一个或多个特征训练的决策树的预测结果产生的特征。

基于树的特征值#

如果我们为回归创建特征,DecisionTreeFeatures() 将在幕后训练 scikit-learn 的 DecisionTreeRegressor,并且这些特征是从这些回归器的 predict 方法中派生出来的。因此,特征将在目标的尺度上。但请记住,决策树回归器的输出不是连续的,它是一个分段函数。

如果我们为分类创建特征,DecisionTreeFeatures() 将在幕后训练 scikit-learn 的 DecisionTreeClassifier。如果目标是二元的,生成的特征是模型 predict_proba 方法的输出,对应于类别 1 的预测。另一方面,如果输出是多类别的,特征则来自 predict 方法,因此返回预测的类别。

示例#

在文档的其余部分,我们将展示 DecisionTreeFeatures() 通过使用决策树来创建多个特征的多功能性。

首先,我们从sklearn加载并显示加利福尼亚住房数据集

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from feature_engine.creation import DecisionTreeFeatures

X, y = fetch_california_housing(return_X_y=True, as_frame=True)
X.drop(labels=["Latitude", "Longitude"], axis=1, inplace=True)
print(X.head())

在以下输出中,我们看到了数据框:

   MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup
0  8.3252      41.0  6.984127   1.023810       322.0  2.555556
1  8.3014      21.0  6.238137   0.971880      2401.0  2.109842
2  7.2574      52.0  8.288136   1.073446       496.0  2.802260
3  5.6431      52.0  5.817352   1.073059       558.0  2.547945
4  3.8462      52.0  6.281853   1.081081       565.0  2.181467

让我们将数据集分为训练集和测试集:

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=0)

结合特性 - 整数#

我们将设置 DecisionTreeFeatures() 以创建 所有可能 的2个特征组合。为了创建所有可能的组合,我们使用带有 features_to_combine 参数的整数:

dtf = DecisionTreeFeatures(features_to_combine=2)
dtf.fit(X_train, y_train)

如果我们把参数 variables 设为 NoneDecisionTreeFeatures() 将会按照我们在 features_to_combine 中指示的方式,组合训练集中的所有数值变量。由于我们设置了 features_to_combine=2,转换器将会创建所有可能的 1 或 2 个变量的组合。

我们可以找到将用于训练树的特征组合,如下所示:

dtf.input_features_

在以下输出中,我们看到了将用于训练决策树的1和2个特征的组合,这些组合基于训练集中的所有数值变量:

['MedInc',
 'HouseAge',
 'AveRooms',
 'AveBedrms',
 'Population',
 'AveOccup',
 ['MedInc', 'HouseAge'],
 ['MedInc', 'AveRooms'],
 ['MedInc', 'AveBedrms'],
 ['MedInc', 'Population'],
 ['MedInc', 'AveOccup'],
 ['HouseAge', 'AveRooms'],
 ['HouseAge', 'AveBedrms'],
 ['HouseAge', 'Population'],
 ['HouseAge', 'AveOccup'],
 ['AveRooms', 'AveBedrms'],
 ['AveRooms', 'Population'],
 ['AveRooms', 'AveOccup'],
 ['AveBedrms', 'Population'],
 ['AveBedrms', 'AveOccup'],
 ['Population', 'AveOccup']]

现在让我们将新功能添加到数据中:

train_t = dtf.transform(X_train)
test_t = dtf.transform(X_test)

print(test_t.head())

在以下输出中,我们看到了从决策树派生的特征的结果数据:

       MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup  \
14740  4.1518      22.0  5.663073   1.075472      1551.0  4.180593
10101  5.7796      32.0  6.107226   0.927739      1296.0  3.020979
20566  4.3487      29.0  5.930712   1.026217      1554.0  2.910112
2670   2.4511      37.0  4.992958   1.316901       390.0  2.746479
15709  5.0049      25.0  4.319261   1.039578       649.0  1.712401

       tree(MedInc)  tree(HouseAge)  tree(AveRooms)  tree(AveBedrms)  ...  \
14740      2.204822        2.130618        2.001950         2.080254  ...
10101      2.975513        2.051980        2.001950         2.165554  ...
20566      2.204822        2.051980        2.001950         2.165554  ...
2670       1.416771        2.051980        1.802158         1.882763  ...
15709      2.420124        2.130618        1.802158         2.165554  ...

       tree(['HouseAge', 'AveRooms'])  tree(['HouseAge', 'AveBedrms'])  \
14740                        1.885406                         2.124812
10101                        1.885406                         2.124812
20566                        1.885406                         2.124812
2670                         1.797902                         1.836498
15709                        1.797902                         2.124812

       tree(['HouseAge', 'Population'])  tree(['HouseAge', 'AveOccup'])  \
14740                          2.004703                        1.437440
10101                          2.004703                        2.257968
20566                          2.004703                        2.257968
2670                           2.123579                        2.257968
15709                          2.123579                        2.603372

       tree(['AveRooms', 'AveBedrms'])  tree(['AveRooms', 'Population'])  \
14740                         2.099977                          1.878989
10101                         2.438937                          2.077321
20566                         2.099977                          1.878989
2670                          1.728401                          1.843904
15709                         1.821467                          1.843904

       tree(['AveRooms', 'AveOccup'])  tree(['AveBedrms', 'Population'])  \
14740                        1.719582                           2.056003
10101                        2.156884                           2.056003
20566                        2.156884                           2.056003
2670                         1.747990                           1.882763
15709                        2.783690                           2.221092

       tree(['AveBedrms', 'AveOccup'])  tree(['Population', 'AveOccup'])
14740                         1.400491                          1.484939
10101                         2.153210                          2.059187
20566                         2.153210                          2.059187
2670                          1.861020                          2.235743
15709                         2.727460                          2.747390

[5 rows x 27 columns]

结合功能 - 列表#

假设我们想要基于训练了2个或更多变量的树来创建特征。我们不需要在 features_to_combine 中使用整数,而是需要传递一个整数列表,告诉 DecisionTreeFeatures() 生成列表中提到的整数的所有可能组合。

这次我们将设置转换器,基于仅有的3个数值变量的所有可能的2和3特征组合来创建特征:

dtf = DecisionTreeFeatures(
    variables=["AveRooms", "AveBedrms", "Population"],
    features_to_combine=[2,3])

dtf.fit(X_train, y_train)

如果我们现在检查功能组合:

dtf.input_features_

我们看到它们是基于我们在 variables 参数中设置的2到3个变量的组合:

[['AveRooms', 'AveBedrms'],
 ['AveRooms', 'Population'],
 ['AveBedrms', 'Population'],
 ['AveRooms', 'AveBedrms', 'Population']]

我们现在可以将特征添加到数据中并检查结果:

train_t = dtf.transform(X_train)
test_t = dtf.transform(X_test)

print(test_t.head())

在以下输出中,我们看到了带有新特征的数据框:

       MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup  \
14740  4.1518      22.0  5.663073   1.075472      1551.0  4.180593
10101  5.7796      32.0  6.107226   0.927739      1296.0  3.020979
20566  4.3487      29.0  5.930712   1.026217      1554.0  2.910112
2670   2.4511      37.0  4.992958   1.316901       390.0  2.746479
15709  5.0049      25.0  4.319261   1.039578       649.0  1.712401

       tree(['AveRooms', 'AveBedrms'])  tree(['AveRooms', 'Population'])  \
14740                         2.099977                          1.878989
10101                         2.438937                          2.077321
20566                         2.099977                          1.878989
2670                          1.728401                          1.843904
15709                         1.821467                          1.843904

       tree(['AveBedrms', 'Population'])  \
14740                           2.056003
10101                           2.056003
20566                           2.056003
2670                            1.882763
15709                           2.221092

       tree(['AveRooms', 'AveBedrms', 'Population'])
14740                                       2.099977
10101                                       2.438937
20566                                       2.099977
2670                                        1.843904
15709                                       1.843904

指定功能组合 - 元组#

我们可以精确地指出我们想要用作决策树输入的特征。让我们创建一个包含特征组合的元组。我们想要一个用人口训练的树,一个用人口和平均占用率训练的树,以及一个用这两个变量加上房屋年龄训练的树:

features = (('Population'), ('Population', 'AveOccup'),
            ('Population', 'AveOccup', 'HouseAge'))

现在,我们将这个元组传递给 DecisionTreeFeatures() ,并注意到我们可以将参数 variables 保留为默认值,因为在使用元组时,该参数会被忽略:

dtf = DecisionTreeFeatures(
    variables=None,
    features_to_combine=features,
    cv=5,
    scoring="neg_mean_squared_error"
)

dtf.fit(X_train, y_train)

如果我们检查输入特征,它将与传递给 features_to_combine 的元组一致:

dtf.input_features_

我们看到输入特征是来自元组的那些:

['Population',
 ['Population', 'AveOccup'],
 ['Population', 'AveOccup', 'HouseAge']]

现在我们可以继续为数据添加功能:

train_t = dtf.transform(X_train)
test_t = dtf.transform(X_test)

检查新功能#

DecisionTreeFeatures() 将单词 tree 附加到新特征上,因此如果我们只想显示新特征,我们可以这样做

tree_features = [var for var in test_t.columns if "tree" in var]
print(test_t[tree_features].head())
       tree(Population)  tree(['Population', 'AveOccup'])  \
14740          2.008283                          1.484939
10101          2.008283                          2.059187
20566          2.008283                          2.059187
2670           2.128961                          2.235743
15709          2.128961                          2.747390

       tree(['Population', 'AveOccup', 'HouseAge'])
14740                                      1.443097
10101                                      2.257968
20566                                      2.257968
2670                                       2.257968
15709                                      3.111251

评估单个树#

如果我们愿意,可以评估用于创建特征的每棵树的性能。让我们设置 DecisionTreeFeatures()

dtf = DecisionTreeFeatures(features_to_combine=2)
dtf.fit(X_train, y_train)

DecisionTreeFeatures() 使用交叉验证训练每棵树。如果我们没有传递带有超参数的网格,它将默认优化深度。我们可以这样找到训练好的估计器:

dtf.estimators_

因为估计器是用 sklearn 的 GridSearchCV 训练的,所以存储的是搜索的结果:

[GridSearchCV(cv=3, estimator=DecisionTreeRegressor(random_state=0),
              param_grid={'max_depth': [1, 2, 3, 4]},
              scoring='neg_mean_squared_error'),
 GridSearchCV(cv=3, estimator=DecisionTreeRegressor(random_state=0),
              param_grid={'max_depth': [1, 2, 3, 4]},
              scoring='neg_mean_squared_error'),
 ...

 GridSearchCV(cv=3, estimator=DecisionTreeRegressor(random_state=0),
              param_grid={'max_depth': [1, 2, 3, 4]},
              scoring='neg_mean_squared_error'),
 GridSearchCV(cv=3, estimator=DecisionTreeRegressor(random_state=0),
              param_grid={'max_depth': [1, 2, 3, 4]},
              scoring='neg_mean_squared_error')]

如果你想检查单个树及其性能,你可以这样做:

tree = dtf.estimators_[4]
tree.best_params_

在以下输出中,我们看到了基于特征 人口 训练的树,用于预测房价的最佳参数:

{'max_depth': 2}

如果我们想查看在网格搜索中找到的最佳树的性能,我们可以这样做:

tree.score(X_test[['Population']], y_test)

以下性能值对应于均方误差的负值,这是搜索过程中优化的指标(您可以通过 DecisionTreeFeatures()scoring 参数选择要优化的指标)。

-1.3308515769033213

请注意,您也可以隔离树,然后获取性能指标:

tree.best_estimator_.score(X_test[['Population']], y_test)

在这种情况下,以下性能指标对应于 R2,这是 scikit-learn 的 DecisionTreeRegressor 返回的默认指标。

0.0017890442253447603

丢弃原始变量#

通过 DecisionTreeFeatures(),我们可以自动从结果数据框中移除作为决策树输入的特征。我们需要将 drop_original 设置为 True

dtf = DecisionTreeFeatures(
    variables=["AveRooms", "AveBedrms", "Population"],
    features_to_combine=[2,3],
    drop_original=True
)

dtf.fit(X_train, y_train)

train_t = dtf.transform(X_train)
test_t = dtf.transform(X_test)

print(test_t.head())

我们在结果的数据框中看到,变量 [“AveRooms”, “AveBedrms”, “Population”] 不存在:

       MedInc  HouseAge  AveOccup  tree(['AveRooms', 'AveBedrms'])  \
14740  4.1518      22.0  4.180593                         2.099977
10101  5.7796      32.0  3.020979                         2.438937
20566  4.3487      29.0  2.910112                         2.099977
2670   2.4511      37.0  2.746479                         1.728401
15709  5.0049      25.0  1.712401                         1.821467

       tree(['AveRooms', 'Population'])  tree(['AveBedrms', 'Population'])  \
14740                          1.878989                           2.056003
10101                          2.077321                           2.056003
20566                          1.878989                           2.056003
2670                           1.843904                           1.882763
15709                          1.843904                           2.221092

       tree(['AveRooms', 'AveBedrms', 'Population'])
14740                                       2.099977
10101                                       2.438937
20566                                       2.099977
2670                                        1.843904
15709                                       1.843904

创建分类特征#

如果我们是为分类器而不是回归器创建特征,过程是相同的。我们只需要将参数 regression 设置为 False。

请注意,如果你正在为二分类创建特征,添加的特征将包含类别1的概率。另一方面,如果你正在为多类分类创建特征,特征将包含类别的预测。

其他资源#

有关此方法和其他特征工程方法的更多详细信息,请查看以下资源:

../../_images/feml.png

机器学习的特征工程#











或者阅读我们的书:

../../_images/cookbook.png

Python 特征工程手册#














我们的书籍和课程都适合初学者和更高级的数据科学家。通过购买它们,您正在支持 Feature-engine 的主要开发者 Sole。