动手学数据分析(2)——数据清洗及特征处理

本文为Datawhale8月组队学习——动手学数据分析课程的系列学习笔记。

Datawhale-动手学数据分析

数据来源

Kaggle小白入门首选练手项目——Kaggle-泰坦尼克号存活率

Ch2-1 数据清洗及特征处理

缺失值

缺失值表现在数据集中,有以下几种形式

  • NaN(Not A Number):普通数据的NA值
  • NaT(Not A Time):时间戳数据的NA值
  • None:Python中空值,没有数值
  • 误输入无意义值

对于使用IO方法读入的数据中的空值,pandas默认会将其转化为NaN,NaN属于float的子类,与None不同,None属于Object类型。同时,pandas和numpy提供的处理NaN的方法更多,因此,一般不能使用None来判断空值。

None和NaN的详细比较可以参考:

[1] Python 中 NaN 和 None 的详细比较

[2] 数据分析之Pandas缺失数据处理

缺失值检测

查看存在空值的列:

  • isnull():可以检测所有NA类数据(包含Null和NaN)
  • info():输出所有列的空值信息
1
2
3
4
5
6
7
## 查看存在空值的列,并统计空值数

# 01 info()方法
train.info()

# 02 isnull()方法
train.isnull().sum()

查看存在空值的行会用到以下两个处理逻辑值的方法:

  • any():判断某一行/列是否含有True
  • all():判断某一行/列是否都为True

踩的坑:NaN != NaN

因此,对于NaN的检测,不能使用 == np.nan,只能使用isnan(),NaT同理

1
2
3
4
5
# 显示某列为空值的行
train[train['Age'].isnull()] # 正确
train[np.isnan(train['Age'])] # 正确
train[train['Age']== None] # 错误
train[train['Age'] == np.nan] # 错误

缺失值处理

处理缺失值的方法可以分为两大类:

  • 删除:以dropna为代表,一般适用于数据较多的情况,删除数据不会使得数据过少。
  • 填补:以fillna为代表,当数据量不够大时,一般使用这种方法,填充的方法有很多,如:均值填充、众数填充、使用模型预测缺失值填充等。由于填补的数据并不是真实的数据,可能会使数据失真,一般只能用于客观数据。

缺失值处理

主要使用的函数/方法:

  • DataFrame.dropna():删除含有缺失值的行/列
  • DataFrame.fillna():填充行/列的缺失值
  • DataFrame.interpolate():对缺失数据进行插值

前两种方法都包含以下参数:

  • subset:用于选择判断依据行/列
  • how:’any’/‘all’,存在or任意
  • axis:维度

后两种方法都包含method参数,用于选择填补/插值的具体方法,可选方法参考fillna_methodsinterpolate_methods

1
2
3
# fillna()的几种使用方式
train['Age'] = train['Age'].fillna(int(train['Age'].mean())) # 填充均值,因为Cabin列是字符串,不能使用这种方法,只以Age列为例
train.fillna(method='ffill')

对于该任务中缺失值的处理:
由于Cabin列缺失的数据很多,使用填补方式很容易失真,删除则会让数据集很小。因此,考虑后续建模不使用该特征(因此不去处理该列的缺失),而是使用该特征构建新的可用于预测的特征。
对于Age列缺失值,由于不存在顺序关系,且年龄不属于定距变量,选择使用众数进行填补。

1
train['Age'] = train['Age'].fillna(train['Age'].mode())

数据去重

重复值的处理主要用到两个方法:duplicated()drop_duplicates()

一般需要处理的只有所有特征完全相同的样本,具体情况需要观察后得知。

1
2
3
4
5
6
# duplicated函数
train.duplicated() # 判断整行重复
train.duplicated(['Name','Sex','Age'])

# drop_duplicates()函数,也可以选择哪几列重复为依据,以及去重保留哪一行(keep=)
train.drop_duplicates(keep='first',inplace =True)

特征处理

数据分箱

数据预处理技术,用于减少次要观察误差的影响,是一种将多个连续值分组为较少数量的「分箱」方法。一般在建立分类模型时,需要对连续变量离散化,特征离散化后,模型会更稳定,降低了模型过拟合的风险。

分箱可以基于自定义划分的区间,也可以基于分位点。

数据分箱一般使用等距或者n等分,按分位数不等分极少。

在Python中,主要用到两种静态方法:pd.cut()pd.qcut()

  • cut():按值切割,即根据数据值的大小范围等分成n组,落入对应范围的进入到对应的组。
  • qcut():等频切割,即基本保证每个组里的元素个数是相等的。(也用于分位点分箱)
1
2
3
4
5
6
7
8
9
10
## cut
# pandas.cut(x, bins, right=True, labels=None, retbins=False)
# bins为int时,自动分箱;bins为list时,根据list划定各区间
Age_category = pd.cut(train['Age'],5,labels = ['1','2','3','4','5'])
Age_category = pd.cut(train['Age'],[0,5,15,30,50,80],labels=['1','2','3','4','5'])

## qcut
# pandas.qcut(x, q, labels=None, retbins=False)
# q除了可以为分位数,支持传入分位点list
Age_category = pd.qcut(train['Age'],[0.0,0.1,0.3,0.5,0.7,0.9],labels=['1','2','3','4','5'])

二者都含有label参数,用于产生分类后的类别标签。

两个方法均返回Categorical对象,其中包含了一个Series,即每个元素的分组标记。retbin参数控制是否返回bins,即整体的分组情况(分组区间)。

输入的binsq为list时,区间分位点要按顺序排列,不能出现交叉,否则会报错。

特征编码

数据分析前,除了将连续数据离散化,往往还需将类别特征转换为数值特征以方便后续的建模分析,因此需要对其进行编码。

常见的特征编码形式包括:标签编码(Label Encoding)独热编码(One Hot Encoding)

在编码之前需要提取类别特征的所有可能值,因此会用到以下方法:

  • unique():返回所有可能取值
  • nunique():返回可能取值的数量
  • value_counts():返回所有可能取值及其对应的数量

unique包含NaN值,但nunique和value_counts忽略了NaN值

此外,也可以使用其他方法来获取类别特征的可能取值,如groupby等:

1
2
# groupby输出所有可能取值
list(train.groupby('Sex').groups.keys())
标签编码(Label Encoding)

数字化编码即给特征的不同值赋予不同的数字标签(对类别变量中每一类别赋一数值),一般从0或1开始编码。

如:

原文本特征值 标签编码
S 0
C 1
Q 2

这种编码方式往往适用于类别间具有排序逻辑关系的数据(如:高、中、低),这种编码方式就保留了其中的大小关系。

而对于没有大小关系的特征,这样的编码无形之中给该特征添加了大小关系(如:S<C<Q)。例如(网上查到的例子):将[dog,cat,dog,mouse,cat]转换为[1,2,1,3,2]。对于不同机器学习模型来说,这里无形之间附加了新的信息——dog和mouse的平均值是cat。这会干扰模型的学习,影响模型的预测。

具体代码实现可以借助:

  • replace():
  • map()
  • sklearn库中的LabelEncoder类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## Label Encoding

# 01 replace
train['Sex_num'] = train['Sex'].replace({'male':1,'female':2})
# 也可使用list
# train['Sex'].replace(['male','female'],[1,2])

# 02 map
cabins = dict(zip(train['Cabin'].unique(),range(1,train['Cabin'].nunique()))) # 建立一个映射字典
train['Cabin_num'] = train['Cabin'].map(cabins)

# 03 LabelEncoder
from sklearn import preprocessing
lbe = preprocessing.LabelEncoder()
#lbe.fit_transform(train['Embarked']) # 错误,embarked列中含有非string数据
# 默认0开始编码,fit_transform可以拆成fit和transform,sklearn中都要先fit
train['Embarked_num']=lbe.fit_transform(train['Embarked'].astype(str))

sklearn库中的preprocessing提供了数据预处理需要用到的很多工具。该库往往需要先建立一个算法的对象,拟合fit()后,再进行预测predict()或转换transform()

独热编码(One Hot Encoding)

独热编码即 One-Hot 编码,又称一位有效编码,其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候,其中只有一位有效。

这种编码方式为每个整数值都创建了一个二值数组,即0/1数组。对于每个特征,如果它有m个可能值,那么经过独热编码后,就变成了m个二元特征(如登船甲板这个特征有S、C、Q变成one-hot就是100, 010, 001)。并且,这些特征互斥,每次只有一个激活值(只含有一个1)。因此,数据会变成稀疏的。

原文本特征值 标签编码
S 100
C 010
Q 001

优点:

  • 在回归、分类、聚类等机器学习算法中,往往需要计算特征之间的距离或相似度,这种计算往往基于欧式空间。One-hot编码将离散特征的取值扩展到了欧式空间,离散特征的某个取值对应欧式空间的某个点,这使得特征之间的距离计算更加合理。
  • 扩充了特征空间
  • 解决了标签编码附加大小关系的问题

缺点:

  • 当类别很多时,特征空间会变得非常大,增加了计算量,在这种情况下,一般可以用PCA来减少维度。

独热编码可能会产生完全共线性问题。共线性问题可以在后续的相关性分析中解决(对相关系数过大特征予以处理)。

具体代码实现可以借助:

  • pandas自带的get_dummies静态方法:可以设置prefix参数,即生成编码DataFrame中列名(特征名)前缀
  • sklearn库中的OneHotEncoder类:
1
2
3
4
5
6
7
## One Hot Encoding
# 01 OneHotEncoder
ohe = preprocessing.OneHotEncoder()
embark_oh = ohe.fit_transform(train['Embarked'].astype(str).values.reshape(-1,1)).toarray() # .values.reshape(-1,1)是为了fit_transform函数输入为ndarray

# 02 get_dummies()
cabin_oh = pd.get_dummies(train['Cabin'],prefix='Cabin')

fit_transform()方法相当于同时进行拟合fit()和转化/预测transform(),其要求输入的变量为String组成的ndarray,否则会报错。

reshape(-1,1)的作用是将Series转化为ndarray。

编码完毕后可能会使用到concat()方法,合并多个DataFrame。

文本提取

在数据集中,文本类数据往往并非所有都是有效信息,需要通过类似爬虫用到的文本处理方法来提取其中的有效信息。

在pandas中,可以使用Series.str.extract()方法结合正则表达式提取字符串类型数据中的有效信息。

正则表达式的书写可以参考Learn Regex The Easy Way,可以配合正则表达式测试工具使用。

评论