本文介绍scikit-learn 0.20版本中新增的sklearn.compose.ColumnTransformer和有所改动的sklearn.preprocessing.OneHotEncoder

ColumnTransformer

假设现在有这样一个场景:有一个数据集,每个样本包含n个数值型(numeric)特征,m个标称型(categorical)特征,我们在使用这个数据集训练模型之前,需要对n个数值型特征做归一化,对m个标称型特征做one-hot编码?这个要如何实现?

其实这个不难,但挺麻烦的。一般的方式是把数值型的特征数据列和标称型数据分别拿出来,然后分别做预处理,处理完之后再拼在一起训练模型。这样一方面是麻烦,另一方面比较难保证原来特征的顺序(虽然顺序一般没什么影响)。scikit-learn在0.20.0版本中新增了一个sklearn.compose.ColumnTransformer类,通过这个类我们可以对输入的特征分别做不同的预处理,并且最终的结果还在一个特征空间里面。描述太抽象,直接看官方的一个例子

# Author: Pedro Morales <part.morales@gmail.com>
#
# License: BSD 3 clause

from __future__ import print_function

import pandas as pd
import numpy as np

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, GridSearchCV

np.random.seed(0)

# Read data from Titanic dataset.
titanic_url = ('https://raw.githubusercontent.com/amueller/'
               'scipy-2017-sklearn/091d371/notebooks/datasets/titanic3.csv')
data = pd.read_csv(titanic_url)

# We will train our classifier with the following features:
# Numeric Features:
# - age: float.
# - fare: float.
# Categorical Features:
# - embarked: categories encoded as strings {'C', 'S', 'Q'}.
# - sex: categories encoded as strings {'female', 'male'}.
# - pclass: ordinal integers {1, 2, 3}.

# We create the preprocessing pipelines for both numeric and categorical data.
numeric_features = ['age', 'fare']
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())])

categorical_features = ['embarked', 'sex', 'pclass']
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))])

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)])

# Append classifier to preprocessing pipeline.
# Now we have a full prediction pipeline.
clf = Pipeline(steps=[('preprocessor', preprocessor),
                      ('classifier', LogisticRegression(solver='lbfgs'))])

X = data.drop('survived', axis=1)
y = data['survived']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

clf.fit(X_train, y_train)
print("model score: %.3f" % clf.score(X_test, y_test))

# output:
# model score: 0.790

例子很简单,里面的注释已经表述的比较清楚了:读入了titanic3.csv数据集,里面包含了2个数值型特征(age, fare)和3个标称型特征(embarked, sex, pclass),然后对数值型特征做缺失值处理和归一化,对标称型特征做缺失值处理和One-Hot编码。例子里面使用Pipeline将这些操作串了起来。

我们看下sklearn.compose.ColumnTransformer的原型:

class sklearn.compose.ColumnTransformer(transformers, remainder=’drop’, sparse_threshold=0.3, n_jobs=None, transformer_weights=None)

简单介绍一下transformersremainder两个参数:

  • transformers:该参数是一个由元组组成的列表(list of tuples),每个元组的结构为:(name, transformer, column):

    • name: transformer的名字,随便起一个字符串即可;
    • transformer: 支持fittransform的estimator或者passthrough或者drop. passthrough表示透传,不对column指定的列做任何转换;drop表示丢弃指定column指定的列。
    • column: 指定对哪些列做转换操作,所以可以是下标、列名等。
  • remainder:这个参数的值可以是支持fittransform的estimator或者passthrough或者drop,默认值是drop,其功能和transformers参数非常像:

    • drop:表示将column指定的列之外的其他列都丢弃;
    • passthrough:表示将column指定的列之外的其他列透传;
    • estimator:表示对column指定的列之外的其他列执行该estimator代表的转换。

新功能的使用还是非常容易的。

OneHotEncoder

scikit-learn 0.20版本里面另外一个比较重要的改动就是sklearn.preprocessing.OneHotEncoder除了支持整数外,还支持字符串。这样如果特征是字符串,就省去了原来需要做sklearn.preprocessing.LabelEncoder的步骤。

老的sklearn.preprocessing.OneHotEncoder原型:

class sklearn.preprocessing.OneHotEncoder(n_values=’auto’, categorical_features=’all’, dtype=<class ‘numpy.float64’>, sparse=True, handle_unknown=’error’)

新的sklearn.preprocessing.OneHotEncoder原型:

class sklearn.preprocessing.OneHotEncoder(n_values=None, categorical_features=None, categories=None, sparse=True, dtype=<class ‘numpy.float64’>, handle_unknown=’error’)

可以看到新老API的主要差别是新API增加了一个categories参数,这个参数是为了替换里面的n_values参数;后者在0.22版本中就去掉了。而且如果要OneHotEncoder支持字符串的话,就必须使用categories,不能使用n_values了。我们简单介绍一下categories这个参数,该参数的可取值为:'auto'( 默认值,表示根据训练数据自己决定categories;)或一个list of list/array。前者很容易理解,后者稍微难理解一些,我们通过例子来看。

>>> enc = preprocessing.OneHotEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)  
OneHotEncoder(categorical_features=None, categories=None,
       dtype=<... 'numpy.float64'>, handle_unknown='error',
       n_values=None, sparse=True)
>>> enc.transform([['female', 'from US', 'uses Safari'],
...                ['male', 'from Europe', 'uses Safari']]).toarray()
array([[1., 0., 0., 1., 0., 1.],
       [0., 1., 1., 0., 0., 1.]])

# 可以通过categories_属性查看所有类别
>>> enc.categories_
[array(['female', 'male'], dtype=object),
 array(['from Europe', 'from US'], dtype=object),
 array(['uses Firefox', 'uses Safari'], dtype=object)]

上例中,categories采用默认值,如果我们需要使用list实现等效的效果的话,可以按照如下方式改写上面代码:

>>> enc_new = preprocessing.OneHotEncoder(categories=[['male', 'female'],['from Europe', 'from US'],['uses Firefox', 'uses Safari']])
>>> enc_new.fit(X)
OneHotEncoder(categorical_features=None,
       categories=[['male', 'female'], ['from Europe', 'from US'], ['uses Firefox', 'uses Safari']],
       dtype=<class 'numpy.float64'>, handle_unknown='error',
       n_values=None, sparse=True)
>>> enc_new.transform([['female', 'from US', 'uses Safari'],
...                ['male', 'from Europe', 'uses Safari']]).toarray()
array([[1., 0., 0., 1., 0., 1.],
       [0., 1., 1., 0., 0., 1.]])
>>> enc_new.categories_
[array(['male', 'female'], dtype=object),
 array(['from US', 'from Europe'], dtype=object),
 array(['uses Safari', 'uses Firefox'], dtype=object)]

例子已经展示的很清楚了,categories的值取list of list/array时候,里面的categories[i]表示第i列特征的categories。同时需要注意:在单个特征的list/array里面,其值要么是numeric要么是string,不能混用;如果是numeric,还需要是排序的。

Reference