转载请注明,来自:https://www.jianshu.com/p/8959917c6253
使用sklearn+pandas做特征选择时,需要对特征名和索引进行维护,以免丢失或错乱,非常麻烦。于是发现了 sklearn-pandas 这个包,对它的使用文档进行了简单翻译。
github: https://github.com/scikit-learn-contrib/sklearn-pandas
这个模块在Scikit-Learn的机器学习方法和pandas风格的数据框架之间提供了一个桥梁。
具体地说,它提供了一种将DataFrame列映射到变换(transformation)的方法,这些转换将被重新组合到特征中。
安装(Installation)
pip install sklearn-pandas
使用方法(Usage)
Import
从sklearn_pandas
中导入需要的部分,你可以选择:
-
DataFrameMapper
,一个类,用于将panda.DataFrame
的列映射到不同的 sklearn 变换。 -
cross_val_score
,类似sklearn.cross_validation.cross_val_scor
,但在 DataFrame 上工作。
对于以下示例,我们需要导入这两个:
from sklearn_pandas import DataFrameMapper, cross_val_score
我们同时还要用到 pandas, numpy, 和 sklearn:
import pandas as pd
import numpy as np
import sklearn.preprocessing, sklearn.decomposition, \
\sklearn.linear_model, sklearn.pipeline, sklearn.metrics
from sklearn.feature_extraction.text import CountVectorizer
载入数据
通常会从文件中读取数据,但为了演示目的,我们将利用 dict 创建一个 DataFrame:
data = pd.DataFrame({'pet': ['cat', 'dog', 'dog', 'fish', 'cat', 'dog', 'cat', 'fish'],
'children': [4., 6, 3, 3, 2, 3, 5, 4],
'salary': [90., 24, 44, 27, 32, 59, 36, 27]})
变换映射(Transformation Mapping)
将列(column)映射到变换(Transformation )
映射器(mapper)需要传入一个元组列表(a list of tuples)。每个tuple
的第一个元素是DataFrame
中的一个列名,或者一个list
,这个list
包含一个或多个列名。第二个元素是一个object
,它将执行应用于该列的变换。第三个元素是可选的(如果可用的话),它是一个包含变换设置选项的dict
(请参见下面的“为变换后的特征自定义列名”)。
mapper = DataFrameMapper([
('pet', sklearn.preprocessing.LabelBinarizer()),
(['children'], sklearn.preprocessing.StandardScaler())
])
指定列选择器为'column'
(作为简单字符串)和['column']
(作为带有一个元素的列表)之间的区别是传递给变换器的数组的形状。在第一种情况下,将传递一个一维数组,而在第二种情况下,将会传递一个只有一列的二维数组,即一个列向量。
这种特性模仿了 pandas.DataFrame 的__getitem__
索引的模式:
>>> data['children'].shape
(8,)
>>> data[['children']].shape
(8, 1)
注意,一些变换器需要一维输入(以标签为导向的),而另一些变换器(如OneHotEncoder
或Imputer
),则需要得到具有形状[n_samples, n_features]
的二维输入。
测试变换
我们可以使用fit_transform
来喂入模型并查看转换后的数据。在本例和其他示例中,使用np.round
将输出四舍五入为两位数,以避免不同硬件上的舍入误差:
>>> np.round(mapper.fit_transform(data.copy()), 2)
array([[ 1. , 0. , 0. , 0.21],
[ 0. , 1. , 0. , 1.88],
[ 0. , 1. , 0. , -0.63],
[ 0. , 0. , 1. , -0.63],
[ 1. , 0. , 0. , -1.46],
[ 0. , 1. , 0. , -0.63],
[ 1. , 0. , 0. , 1.04],
[ 0. , 0. , 1. , 0.21]])
注意,前三列是LabelBinarizer
的输出(分别对应于cat
、dog
和fish
),第四列是children
数量标准化后的值。通常,输出后的列顺序和构造DataFrameMapper
时的列序一致。
现在变换器已经经过了训练,我们来确认它对新数据有效:
>>> sample = pd.DataFrame({'pet': ['cat'], 'children': [5.]})
>>> np.round(mapper.transform(sample), 2)
array([[1. , 0. , 0. , 1.04]])
输出特征名
在某些情况下,比如在研究特征重要性时,我们希望能够将原始特征与DataFrameMapper
生成的特征相关联。我们可以通过调用执行变换后 mapper 自动生成的transformed_names_
属性来实现:
>>> mapper.transformed_names_
['pet_cat', 'pet_dog', 'pet_fish', 'children']
为变换后的特征自定义列名
我们可以为变换后的特征自定义名称,以代替自动生成的特征名:
>>> mapper_alias = DataFrameMapper([
... (['children'], sklearn.preprocessing.StandardScaler(),
... {'alias': 'children_scaled'})
... ])
>>> _ = mapper_alias.fit_transform(data.copy())
>>> mapper_alias.transformed_names_
['children_scaled']
向变换器传递 Series/DataFrames
默认情况下,Series
/DataFrames
在传递给DataFrameMapper
后,选定列将会作为numpy.Array
传递给指定的变换器(这是因为sklearn转换器在是基于numpy数组而设计的)。
然而,有些变化器(一般是定制的变换器)需要Series
/DataFrames
作为输入。这种情况下,我们需要在初始化DataFrameMapper
时,使input_df=True
:
>>> from sklearn.base import TransformerMixin
>>> class DateEncoder(TransformerMixin):
... def fit(self, X, y=None):
... return self
...
... def transform(self, X):
... dt = X.dt
... return pd.concat([dt.year, dt.month, dt.day], axis=1)
>>> dates_df = pd.DataFrame(
... {'dates': pd.date_range('2015-10-30', '2015-11-02')})
>>> mapper_dates = DataFrameMapper([
... ('dates', DateEncoder())
... ], input_df=True)
>>> mapper_dates.fit_transform(dates_df)
array([[2015, 10, 30],
[2015, 10, 31],
[2015, 11, 1],
[2015, 11, 2]])
我们也可以为单独一列指定这个选项,而不是为整个映射器:
>>> mapper_dates = DataFrameMapper([
... ('dates', DateEncoder(), {'input_df': True})
... ])
>>> mapper_dates.fit_transform(dates_df)
array([[2015, 10, 30],
[2015, 10, 31],
[2015, 11, 1],
[2015, 11, 2]])
输出 DataFrame
默认情况下,DataFrameMapper 的输出是一个 numpy 数组。这是因为大多数 sklearn 估计器都需要输入一个 numpy 数组。如果我们希望映射器的输出是一个 dataframe,我们可以在创建映射器时使参数df_out=True
:
>>> mapper_df = DataFrameMapper([
... ('pet', sklearn.preprocessing.LabelBinarizer()),
... (['children'], sklearn.preprocessing.StandardScaler())
... ], df_out=True)
>>> np.round(mapper_df.fit_transform(data.copy()), 2)
pet_cat pet_dog pet_fish children
0 1 0 0 0.21
1 0 1 0 1.88
2 0 1 0 -0.63
3 0 0 1 -0.63
4 1 0 0 -1.46
5 0 1 0 -0.63
6 1 0 0 1.04
7 0 0 1 0.21
列的名称与transformed_names_
属性中的名称相同。
注意,df_out=True
参数与Mapper的default=True
及sparse=True
参数不兼容。
需要多个输入列的变换(如PCA)
变换可能需要多个输入列(如PCA)。在这些情况下,列名可以在一个list
中指定:
>>> mapper2 = DataFrameMapper([
... (['children', 'salary'], sklearn.decomposition.PCA(1))
... ])
接下来执行fit_transform
,将会在children
和salary
两列使用 PCA,并返回第一个主成分:
>>> np.round(mapper2.fit_transform(data.copy()), 1)
array([[ 47.6],
[-18.4],
[ 1.6],
[-15.4],
[-10.4],
[ 16.6],
[ -6.4],
[-15.4]])
对同一列执行多种转换
通过在一个list
中指定多种变换器,可以对同一列执行多种变换:
>>> mapper3 = DataFrameMapper([
... (['age'], [sklearn.preprocessing.Imputer(),
... sklearn.preprocessing.StandardScaler()])])
>>> data_3 = pd.DataFrame({'age': [1, np.nan, 3]})
>>> mapper3.fit_transform(data_3)
array([[-1.22474487],
[ 0. ],
[ 1.22474487]])
不需要任何变换的列
输出只保留DataFrameMapper中指定的列。若要保留列但不对其应用任何转换,请使用None作为转换器:
>>> mapper3 = DataFrameMapper([
... ('pet', sklearn.preprocessing.LabelBinarizer()),
... ('children', None)
... ])
>>> np.round(mapper3.fit_transform(data.copy()))
array([[1., 0., 0., 4.],
[0., 1., 0., 6.],
[0., 1., 0., 3.],
[0., 0., 1., 3.],
[1., 0., 0., 2.],
[0., 1., 0., 3.],
[1., 0., 0., 5.],
[0., 0., 1., 4.]])
指定默认变换器
默认变换器可以应用于未显式选择的列,将其作为默认参数传递给 Mapper:
>>> mapper4 = DataFrameMapper([
... ('pet', sklearn.preprocessing.LabelBinarizer()),
... ('children', None)
... ], default=sklearn.preprocessing.StandardScaler())
>>> np.round(mapper4.fit_transform(data.copy()), 1)
array([[ 1. , 0. , 0. , 4. , 2.3],
[ 0. , 1. , 0. , 6. , -0.9],
[ 0. , 1. , 0. , 3. , 0.1],
[ 0. , 0. , 1. , 3. , -0.7],
[ 1. , 0. , 0. , 2. , -0.5],
[ 0. , 1. , 0. , 3. , 0.8],
[ 1. , 0. , 0. , 5. , -0.3],
[ 0. , 0. , 1. , 4. , -0.7]])
使default=False
(默认为False
)将会丢掉未选择的列。使default=None
将不对未选中的列做任何变换地保留他们。
多个列使用相同的变换器
有时需要对几个列应用相同的变换。为了简化这个过程(不用对每个列指定变换器),包提供了gen_features
函数,该函数接受一个包含列名的list
,和特征变换器类(特征变换器类的列表),返回一个新特征的定义,可以被DataFrameMapper
接受。
例如,考虑一个有3个分类列的数据集,分别是'col1'、'col2'和'col3',要对它们进行数值化编码,可以将列名和LabelEncoder
变换器类传递到gen_features
中,然后使用返回的定义作为DataFrameMapper
的feature
参数:
>>> from sklearn_pandas import gen_features
>>> feature_def = gen_features(
... columns=['col1', 'col2', 'col3'],
... classes=[sklearn.preprocessing.LabelEncoder]
... )
>>> feature_def
[('col1', [LabelEncoder()]), ('col2', [LabelEncoder()]), ('col3', [LabelEncoder()])]
>>> mapper5 = DataFrameMapper(feature_def)
>>> data5 = pd.DataFrame({
... 'col1': ['yes', 'no', 'yes'],
... 'col2': [True, False, False],
... 'col3': ['one', 'two', 'three']
... })
>>> mapper5.fit_transform(data5)
array([[1, 1, 0],
[0, 0, 2],
[1, 0, 1]])
如果需要指定变换器的参数,那么应该提供一个带有class
键和变换器参数的dict
。例如,考虑一个有缺失值的数据集,下面的代码可以用来覆盖默认的输入策略:
>>> feature_def = gen_features(
... columns=[['col1'], ['col2'], ['col3']],
... classes=[{'class': sklearn.preprocessing.Imputer, 'strategy': 'most_frequent'}]
... )
>>> mapper6 = DataFrameMapper(feature_def)
>>> data6 = pd.DataFrame({
... 'col1': [None, 1, 1, 2, 3],
... 'col2': [True, False, None, None, True],
... 'col3': [0, 0, 0, None, None]
... })
>>> mapper6.fit_transform(data6)
array([[1., 1., 0.],
[1., 0., 0.],
[1., 1., 0.],
[2., 1., 0.],
[3., 1., 0.]])
特征选择和其他有监督的变换
DataFrameMapper
支持同时需要X和y参数的变换器,比如特征选择。将 'pet' 列作为标签,我们将选择最能预测它的列:
>>> from sklearn.feature_selection import SelectKBest, chi2
>>> mapper_fs = DataFrameMapper([(['children','salary'], SelectKBest(chi2, k=1))])
>>> mapper_fs.fit_transform(data[['children','salary']], data['pet'])
array([[90.],
[24.],
[44.],
[27.],
[32.],
[59.],
[36.],
[27.]])
处理稀疏特征
默认情况下,DataFrameMapper
将返回一个密集的特征数组。在 Mapper 中设置sparse=True
将在提取的任何特征为稀疏时,返回一个稀疏数组:
>>> mapper5 = DataFrameMapper([
... ('pet', CountVectorizer()),
... ], sparse=True)
>>> type(mapper5.fit_transform(data))
<class 'scipy.sparse.csr.csr_matrix'>
在不致密的情况下,完成对稀疏特征的堆叠。
交叉验证
现在我们已经结合了pandas.DataFrame
的特性,我们还可能想要使用交叉验证来验证我们的模型是否有效。scikit-learn<0.16.0
提供了支持交叉验证的特性,但是期望使用numpy数据结构作为输入,并且不能直接使用DataFrameMapper
。
为了解决这个问题,sklearn-pandas 为 sklearn 的cross_val_score
函数提供了一个包装器(wrapper),它将一个panda.DataFrame
传递给估计器,而不是一个numpy数组:
>>> pipe = sklearn.pipeline.Pipeline([
... ('featurize', mapper),
... ('lm', sklearn.linear_model.LinearRegression())])
>>> np.round(cross_val_score(pipe, X=data.copy(), y=data.salary, scoring='r2'), 2)
array([ -1.09, -5.3 , -15.38])
Sklearn-panda的cross_val_score
函数提供了与 sklearn 同名函数完全相同的接口。
CategoricalImputer
由于scikit-learn
Imputer
变换器目前只能处理数字,因此 sklearn-panda 提供了一个等效的辅助变换器,它可以处理字符串,用该列中最常见的值替换空值。或者,您可以指定要使用的固定值。
Example: imputing with the mode:
>>> from sklearn_pandas import CategoricalImputer
>>> data = np.array(['a', 'b', 'b', np.nan], dtype=object)
>>> imputer = CategoricalImputer()
>>> imputer.fit_transform(data)
array(['a', 'b', 'b', 'b'], dtype=object)
Example: imputing with a fixed value:
>>> from sklearn_pandas import CategoricalImputer
>>> data = np.array(['a', 'b', 'b', np.nan], dtype=object)
>>> imputer = CategoricalImputer(strategy='constant', fill_value='a')
>>> imputer.fit_transform(data)
array(['a', 'b', 'b', 'a'], dtype=object)
FunctionTransformer
通常,我们希望对诸如np.log
这样的数据应用简单的转换。FunctionTransformer
是一个简单的包装器(wrapper),它接受任何函数并进行向量化,以便可以将其用作变换器。这样,就可以与DataFrameMappe
r结合使用
Example:
>>> from sklearn_pandas import FunctionTransformer
>>> array = np.array([10, 100])
>>> transformer = FunctionTransformer(np.log10)
>>> transformer.fit_transform(array)
array([1., 2.])