数据分析可视化与特征工程

本文数据来源自Kaggle竞赛中的titanic预测是否幸存的例子。
Titnaic

数据预处理

整个数据处理过程均使用python中的Pandas库

大致了解数据集

加载数据使用read_csv加载csv格式的数据

1
df = pd.read_csv('./data/titanic/train.csv')

通过info(),sample(10),head(10)函数对数据集有一个大致的了解,比如每一列数据的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
df.info() # 返回当前数据dateframe中的类型
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 891 entries, 0 to 890
# Data columns (total 12 columns):
# PassengerId 891 non-null int64
# Survived 891 non-null int64
# Pclass 891 non-null int64
# Name 891 non-null object
# Sex 891 non-null object
# Age 714 non-null float64
# SibSp 891 non-null int64
# Parch 891 non-null int64
# Ticket 891 non-null object
# Fare 891 non-null float64
# Cabin 204 non-null object
# Embarked 889 non-null object
# dtypes: float64(2), int64(5), object(5)
# memory usage: 83.6+ KB
df.sample(10) #
df.head() # 返回前几行


数据处理

在大致了解数据集的基本概况之后,需要对数据进行清洗,方便后续特征工程已经模型的训练和调参。数据清洗主要分为如下四个步骤:

  • 修改异常值
  • 完善缺失值
  • 生成新特征
  • 转换数据格式

缺失值的补充,首先需要对数据进行校验,查看是否存在NAN的值。此处可以使用如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# isnull 还针对每一行和每一列进行非空判断
# sum 将判断后的结果按累加 axis=0(在index纬度上) axis=1(在column纬度上)
df.isnull().sum()
# 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

# describe 会计算每一列的 总数、中位数、均值,等等属性
df.describe(include = 'all')

缺失值的补充使用Pandas提供的fillna()函数,如dataset['Age'].fillna(dataset['Age'].median(), inplace = True) 其中fillna()函数可以作用于DataFrameSeries,第一个参数表示要填充的值,此处选择的的是中位数,对于连续的值可以采用此方法。但是,有些特征的值并不是连续的,比如类型等特征,这个时候可以选择出现最多的类别的作为填充值,使用df['Embarked'].mode()[0]

1
2
3
4
5
df = pd.DataFrame({'A': [1, 2, 1, 2, 1, 2, 3]})
df.mode()
# A
# 0 1
# 1 2

删除无用的列,比如数据集中提供的某些列与最终的结果的预测并没有什么关系或者可以替换为一些其他特征的(名字,出生年月日,等等),此时在数据清洗的过程中可以将其从数据中删除,使用drop()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
#    A  B   C   D
# 0 0 1 2 3
# 1 4 5 6 7
# 2 8 9 10 11
df.drop(['B','C'], axis = 1)
dr.drop(columns = ['B','C'])
# A D
# 0 0 3
# 1 4 7
# 2 8 11
dr.drop([0,2])
# A B C D
# 1 4 5 6 7

增加组合信息列,通常数据中原有的一些特征无法满足我们对结果进行有效的区分,因此需要对原有的一些特征进行组合从而形成更加高洁有效的特征。

针对字符串类型的数据可以使用df['name'].str的方法将字符串中可以使用的strip() split() upper() lower()等等一系列的函数应用到该列特征上。例如,对Titanic数据集中的Name列进行操作,得到名字中的关键标志。

1
df_train['Title'] = df_train['Name'].str.split(", ", expand=True)[1].str.split(".", expand=True)[0]

其中split()函数会将字符串进行切分,每一行对应了一个字符串数组(如图1)
图1
如果加上expand参数的话,意味着会将切分后的字符串list展开到列中,会将切分后的结果放在不同的列中(如图2)。
图2

针对连续值的数据特征,可以使用pd.cat(df['Age].astype(int),5)或者pd.qcut(df['Fare'],4)将其进行切分。其中cut()是按照该列数据的实际值进行切分,比如该列特征有值1,1,1,1,1,2,2,3,3,4,4,4,5,6,7,8,8,那么使用cut(data, 3)切分后如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 0     (0.993, 3.333]
# 1 (0.993, 3.333]
# 2 (0.993, 3.333]
# 3 (0.993, 3.333]
# 4 (0.993, 3.333]
# 5 (0.993, 3.333]
# 6 (0.993, 3.333]
# 7 (0.993, 3.333]
# 8 (0.993, 3.333]
# 9 (3.333, 5.667]
# 10 (3.333, 5.667]
# 11 (3.333, 5.667]
# 12 (3.333, 5.667]
# 13 (5.667, 8.0]
# 14 (5.667, 8.0]
# 15 (5.667, 8.0]
# 16 (5.667, 8.0]

然后qcut(data, 3)是按照该列数值出现的频率进行切分,使其在每个区间上的样本数大致相同,切分效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 0     (0.999, 2.0]
# 1 (0.999, 2.0]
# 2 (0.999, 2.0]
# 3 (0.999, 2.0]
# 4 (0.999, 2.0]
# 5 (0.999, 2.0]
# 6 (0.999, 2.0]
# 7 (2.0, 4.0]
# 8 (2.0, 4.0]
# 9 (2.0, 4.0]
# 10 (2.0, 4.0]
# 11 (2.0, 4.0]
# 12 (4.0, 8.0]
# 13 (4.0, 8.0]
# 14 (4.0, 8.0]
# 15 (4.0, 8.0]
# 16 (4.0, 8.0]

针对字符串或者是类别类型的特征值,在将数据送入模型训练之前,需要对其进行转换,转换基本有两种方式,一种是直接转换成Int类型的的值,使用不同的值标识不同的类别,另一种方式是将该特征转换为One-Hot的形式。

  • 第一种使用的是Sklearn中的LabelEncoder().fit_transform()来进行特征的转换
  • 第二种使用的是Pandas中的get_dummies()来进行特征的转换

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ss = pd.DataFrame(['A','B','C'],columns=['Type'])
# Type
# 0 A
# 1 B
# 2 C
ss['Type'] = lable.fit_transform(ss['Type'])
# Type
# 0 0
# 1 1
# 2 2
sss = pd.get_dummies(ss)
# Type_A Type_B Type_C
# 0 1 0 0
# 1 0 1 0
# 2 0 0 1

特征工程

在完成数据清洗、补全、添加、转换等多个步骤后,原始数据已经被处理成即将用来作为模型训练所需的特征。在特征选择的过程中,每个特征与最终预测值之间的关系是衡量这个特征是否有效的标准。为了能够大致选择出特征,将特征之间关系进行可视化,从而得到一个直观的感受。

可视化工具

主要使用Seaborn中的几个画图函数,Seaborn是基于matpltlib封装的高级画图库,可以方便的对离散和连续的数据进行可视化。下边主要使用如下几个画图函数:

  • barplot() 主要利用矩阵条的高度反映数值变量的集中趋势
  • pointplot() 点图代表散点图位置的数值变量的中心趋势估计
  • boxplot() 箱形图,显示一组数据分散情况资料的统计图。它能显示出一组数据的最大值、最小值、中位数及上下四分位数。
  • violinplot() 琴形图,不像箱形图中所有绘图组件都对应于实际数据点,小提琴绘图以基础分布的核密度估计为特征
  • kdeplot()
  • FacetGrid()
箱形图 boxplot()

琴形图 violinplot()

使用以及清洗过的数据,执行如下代码,

1
2
3
4
5
6
7
fig, ax = plt.subplots(1,3,figsize=(30,15))
sns.boxplot(x='Pclass', y='Fare', hue='Survived', data=df_train , ax=ax[0])
sns.violinplot(x='Pclass', y='Age', hue='Survived' ,data=df_train, split=False ,ax= ax[1])
sns.violinplot(x='Sex', y='Age',hue='Survived', data=df_train, ax=ax[2])
sns.swarmplot(x='Sex', y='Age',hue='Survived',data=df_train,dodge=True, ax=ax[2])
sns.swarmplot(x='Pclass', y='Age',hue='Survived',data=df_train,dodge=True, ax=ax[1])
sns.swarmplot(x='Pclass', y='Fare',hue='Survived',data=df_train,dodge=True, ax=ax[0])

图中可以看到不同,在不同类别下(是否存活、Pclass)特征FareAge会呈现出不同的分布情况,Fare的区分更为明显,特别是在Pclass = 1的情况下。另外,通过散点图swarmplot()我们也可以清晰的看到,整个特征的分布情况。

如果仅仅是想查看某一特征与最终预测分类之间的关系的话,可以将hue参数去掉,将参数y修改为预测label,如下:

1
sns.boxplot(x='Survived', y='Fare', data=df_train , ax=ax[0])

概率密度函数Kde

如果单纯考虑某一特征,还有另外一种方法,就是使用概率密度图(针对连续值),此时可以根据预测变量的不同取值,画出不同取值情况下某一特征的概率密度函数,例如票价和年龄。曲线下边的面积表示概率值,从下边的图中可以明显看到,特征Fare取值较小的情况下,也就是票价较低的情况下,曲线下边的面积存活的要比不存活的小;同样可以看到年龄在[20,30]之间存活的概率还会变高,超过不存活的。

1
2
3
4
a = sns.FacetGrid( df_train, hue = 'Survived', aspect=4)
a.map(sns.kdeplot, 'Age', shade= True )
a.set(xlim=(0 , df_train['Age'].max()))
a.add_legend()


相关性与热力图heatmap

通过Pandas提供的计算相关系数的函数corr()得到不同特征之间的相关性。

1
2
3
f = plt.figure(figsize = (10, 8))
sns.heatmap(df_train.corr(), annot = True, fmt='.2f')
plt.show()