这次我们专门挑了一份烂大街的数据集Titanic,写了一点关于数据预处理部分,但是代码风格却是大(zhuang)佬(bi)级别。很明显,我不是大佬,不过是有幸被培训过。
说到预处理,一般就是需要:
- 数字型缺失值处理
- 类别型缺失值处理
- 数字型标准化
- 类别型特征变成dummy变量
- Pipeline 思想
在做数据处理以及机器学习的过程中,最后你会发现每个项目似乎都存在“套路”。所有的项目处理过程都会存在一个“套路”:
- 预处理
- 建模
- 训练
- 预测
对于预处理,其实也是一个套路,不过我们不用pipeline 函数,而是另一个FeatureUnion函数。
当然一个函数也不能解决所有问题,我们通过实战来看看哪些函数以及编码风格能让我们的代码看起来很有条理并且“大(zhuang)佬(bi)”风格十足。
导入数据开启实战
今天我们分析的titanic 数据,数据我已经下载,并且放在项目路径下的data 文件中。
- import pandas as pd
- file = 'data/titanic_train.csv'
- raw_df = pd.read_csv(file)
接下来就是标准套路:预览info以及预览head。
- print(raw_df.info())
- print(raw_df.head())
我们对数据集的名称进行简单的回顾:
- RangeIndex: 891 entries, 0 to 890:表示891 个样本
- columns :共12 列
按数据类型来划分:
int64 :
- PassengerId :乘客ID
- Survived:是否生存,1 为生存
- Pclass :乘客级别
- SibSp :sibling and spouse (兄弟姐妹以及配偶个数)Parch :parents and children(父母以及子女个数)
object:
- Name: 名字
- Sex:性别
- Ticket :船票编号
- Cabin:船舱号
- Embarked:登船地点
float64:
- Age:年龄
- Fare 票价
- RangeIndex: 891 entries, 0 to 890
- Data columns (total 12 columns):
- # Column Non-Null Count Dtype
- --- ------ -------------- -----
- 0 PassengerId 891 non-null int64
- 1 Survived 891 non-null int64
- 2 Pclass 891 non-null int64
- 3 Name 891 non-null object
- 4 Sex 891 non-null object
- 5 Age 714 non-null float64
- 6 SibSp 891 non-null int64
- 7 Parch 891 non-null int64
- 8 Ticket 891 non-null object
- 9 Fare 891 non-null float64
- 10 Cabin 204 non-null object
- 11 Embarked 889 non-null object
- dtypes: float64(2), int64(5), object(5)
- memory usage: 83.7+ KB
一般的机器学习都不会预处理缺失值以及类别型数据,因此我们至少要对这两种情形做预处理。
首先我们查看缺失值,其实上文中的info已经有这样的信息。这里我们更显式的展示缺失信息。
- # get null count for each columns
- nulls_per_column = raw_df.isnull().sum()
- print(nulls_per_column)
结果如下:
- PassengerId 0
- Survived 0
- Pclass 0
- Name 0
- Sex 0
- Age 177
- SibSp 0
- Parch 0
- Ticket 0
- Fare 0
- Cabin 687
- Embarked 2
- dtype: int64
可以看到Age 有缺失,Age是float64 类型数据,Cabin 有缺失,Cabin 为object 类型,Embarked 有缺失,Embarked 也是object 类型。
主角登场(策略与函数)
上述我们可以看到缺失的列有哪些,对于有些情况,比如快速清理数据,我们仅仅会制定如下策略:
对于float类型,我们一般就是用均值或者中位数来代替 对于object 类型,如果ordinal 类型,也就是严格类别之分,比如(男,女),比如(高,中,低)等,一般就用众数来替代 对于object 类型,如果nominal类型,也就是没有等级/严格类别关系,比如ID,我们就用常值来替代。本文中用到的是sklearn的preprocessing 模块,pipeline模块,以及一个第三方“新秀”sklearn_pandas 库。
这里我们简单的介绍这个函数的用途。
- StandardScaler: 用于对数字类型做标准化处理
- LabelBinarizer: 顾名思义,将类型类型,先label 化(变成数字),再Binarize (变成二进制)。相当于onehot 编码,不过LabelBinarizer只是针对一列进行处理
- FeatureUnion:用于将不同特征预处理过程(函数)重新合并,但是需要注意的是它的输入不是数据而是transformer,也就是预处理的方法。
- SimpleImputer:sklearn 自带了类似于fillna的预处理函数
- CategoricalImputer: 来自于sklearn_pandas 的补充,因为sklearn 中并没有针对类别类型数据的预处理。
- DataFrameMapper: 相当于构建针对dataframe的不同的列构建不同的transformer。
- from sklearn.preprocessing import StandardScaler
- from sklearn.preprocessing import LabelBinarizer
- from sklearn.pipeline import FeatureUnion
- from sklearn_pandas import CategoricalImputer
- from sklearn_pandas import DataFrameMapper
- from sklearn.impute import SimpleImputer
按照我们策略,我们需要将列分为数字型和类别型。思路就是看一列数据是否为object类型。
- # split categorical columns and numerical columns
- categorical_mask = (raw_df.dtypes == object)
- categorical_cols = raw_df.columns[categorical_mask].tolist()
- numeric_cols = raw_df.columns[~categorical_mask].tolist()
- numeric_cols.remove('Survived')
- print(f'categorical_cols are {categorical_cols}' )
- print(f'numeric_cols are {numeric_cols}' )
print:
- categorical_cols are ['Name', 'Sex', 'Ticket', 'Cabin', 'Embarked']
- numeric_cols are ['PassengerId', 'Pclass', 'Age', 'SibSp', 'Parch', 'Fare']
数值型数据预处理
对数值型数据进行预处理,这里我们采用DataFrameMapper来创建这个transformer 对象,对所有的numeric_cols 进行填写中值。
- numeric_fillna_mapper=DataFrameMapper([([col], SimpleImputer(strategy="median")) for col in numeric_cols],
- input_df=True,
- df_out=True
- )
我们可以测试代码,看一下变换后的数据是什么样。这里需要调用fit_transform 方法。
- transformed = numeric_fillna_mapper.fit_transform(raw_df)
- print(transformed.info())
结果如下,可以看到变换后的数据只包含我们处理的列,并且可以看到non-null 个数已经为891,表明没有缺失。
- # Column Non-Null Count Dtype
- -- ------ -------------- -----
- 0 PassengerId 891 non-null float64
- 1 Pclass 891 non-null float64
- 2 Age 891 non-null float64
- 3 SibSp 891 non-null float64
- 4 Parch 891 non-null float64
- 5 Fare 891 non-null float64
如果我们需要对数值型特征,先进行缺失值填充,然后再进行标准化。这样我们只需要将上面的函数重新修改,增加一个transformer list。这个transformer list包含SimpleImputer 和StandardScaler 两步。
- # fill nan with mean
- # and then standardize cols
- numeric_fillna_standardize_mapper=DataFrameMapper([([col], [SimpleImputer(strategy="median"),
- StandardScaler()]) for col in numeric_cols],
- input_df=True,
- df_out=True
- )
- fillna_standardized = numeric_fillna_standardize_mapper.fit_transform(raw_df)
- print(fillna_standardized.head())
预览变换后的结果:
- PassengerId Pclass Age SibSp Parch Fare
- 0 -1.730108 0.827377 -0.565736 0.432793 -0.473674 -0.502445
- 1 -1.726220 -1.566107 0.663861 0.432793 -0.473674 0.786845
- 2 -1.722332 0.827377 -0.258337 -0.474545 -0.473674 -0.488854
- 3 -1.718444 -1.566107 0.433312 0.432793 -0.473674 0.420730
- 4 -1.714556 0.827377 0.433312 -0.474545 -0.473674 -0.486337
这样我们就完成了数值型数据的预处理。类似的我们可以针对类别型数据进行预处理。
类别型数据预处理
本例中,Cabin 有缺失,Embarked 有缺失,因为这两者都是有有限类别个数的,我们可以用出现最高频次的数据进行填充,假如是Name 缺失呢?一般Name都没有重名的,而且即便有个别重名,用最高频次的数据进行填充也没有意义。所以我们会选择用常数值填充,比如“unknown”等。
作为一个模板,这里我们的处理方法要涵盖两种情况。
['Name','Cabin','Ticket'] 其实都类似于ID,几乎没有重复的,我们用常值替代,然后用LabelBinarizer变成dummy 变量 其他列,我们用最高频次的类别填充,然后用LabelBinarizer变成dummy 变量。
- # Apply categorical imputer
- constant_cols = ['Name','Cabin','Ticket']
- frequency_cols = [_ for _ in categorical_cols if _ not in constant_cols]
- categorical_fillna_freq_mapper = DataFrameMapper(
- [(col, [CategoricalImputer(),LabelBinarizer()]) for col in frequency_cols],
- input_df=True,
- df_out=True
- )
- categorical_fillna_constant_mapper = DataFrameMapper(
- [(col, [CategoricalImputer(strategy='constant',fill_value='unknown'),LabelBinarizer()]) for col in constant_cols],
- input_df=True,
- df_out=True
- )
我们同样进行测试代码:
- transformed = categorical_fillna_freq_mapper.fit_transform(raw_df)
- print(transformed.info())
- transformed = categorical_fillna_constant_mapper.fit_transform(raw_df)
- print(transformed.shape)
结果如下:
- Data columns (total 4 columns):
- # Column Non-Null Count Dtype
- --- ------ -------------- -----
- 0 Sex 891 non-null int32
- 1 Embarked_C 891 non-null int32
- 2 Embarked_Q 891 non-null int32
- 3 Embarked_S 891 non-null int32
- dtypes: int32(4)
以及:
- (891, 1720)
featureunion 所有的预处理过程
前面我们已经测试了每一种的预处理的方式(transfomer 或者称为mapper),可以看到结果中只包含处理的部分列对应的结果。
实际中,我们可以用FeatureUnion,直接将所有需要处理的方式(transfomer 或者称为mapper)变成一个pipeline,一目了然。
然后调用fit_transform 对原始数据进行变换,这样我们的预处理看起来更有条理。
- feature_union_1 = FeatureUnion([("numeric_fillna_standerdize", numeric_fillna_standardize_mapper),
- ("cat_freq", categorical_fillna_freq_mapper),
- ("cat_constant", categorical_fillna_constant_mapper)])
- df_1 = feature_union_1.fit_transform(raw_df)
- print(df_1.shape)
- print(raw_df.shape)
总结
本文介绍了“大佬”级别的数据预处理方式,并且是在实战中进行演示。
通过本文可以学到:
- 数值型预处理,通过DataFrameMapper 直接对数值类型的列进行多次变换
- 类别型预处理,通过DataFrameMapper 直接对类别型的列进行多次变换
- 类别型变换方法可以至少采用两种方式
- LabelBinarizer,SimpleImputer,CategoricalImputer,LabelBinarizer等函数对数据 进行变换
- FeatureUnion 来将预处理过程管道化(pipeline) 通过这样的方式处理数据,会一目了然。