Python数据分析实战:大(zhuang)佬(bi)级别数据预处理方式

做~自己de王妃 提交于 2020-08-06 21:08:26

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群

作者丨琥珀里有波罗的海  

https://zhuanlan.zhihu.com/p/146906814

前言

之前写的文字都比较干,每篇文章都是篇幅巨长,恨不得一篇文章把一个数据集从入手到预测完成全部覆盖。这里面还要加上自己的“思路”和“弯路”。

这次我们专门挑了一份烂大街的数据集Titanic(后台回复:Titanic即可获取),写了一点关于数据预处理部分,但是代码风格却是大(zhuang)佬(bi)级别。很明显,我不是大佬,不过是有幸被培训过。

说到预处理,一般就是需要:

数字型缺失值处理 

类别型缺失值处理 

数字型标准化 

类别型特征变成dummy变量

Pipeline 思想

在做数据处理以及机器学习的过程中,最后你会发现每个项目似乎都存在“套路”。所有的项目处理过程都会存在一个“套路”:

  • 预处理

  • 建模

  • 训练

  • 预测

对于预处理,其实也是一个套路,不过我们不用pipeline 函数,而是另一个FeatureUnion函数。

当然一个函数也不能解决所有问题,我们通过实战来看看哪些函数以及编码风格能让我们的代码看起来很有条理并且“大(zhuang)佬(bi)”风格十足。

导入数据开启实战

今天我们分析的titanic 数据,数据集来自Kaggle,不过我相信很多网站都提供了该数据集。

数据我已经下载,并且放在项目路径下的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)通过这样的方式处理数据,会一目了然。

程序员专栏 扫码关注填加客服 长按识别下方二维码进群
近期精彩内容推荐:   写外挂赚12万获刑,被抓才知道帮团队赚300万 程序员高薪盛宴背后:程序员正在消失? 教你构建 Python 编程的核心知识体系! 分享史上Java最牛逼,最简短的代码


在看点这里好文分享给更多人↓↓
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!