特征增强:清洗数据
《特征工程入门与实践》–特征增强
识别数据中的缺失值
# 导入探索性数据分析所需的包
import pandas as pd # 存储表格数据
import numpy as np # 数学计算包
import matplotlib.pyplot as plt # 流行的数据可视化工具
import seaborn as sns # 另一个流行的数据可视化工具
%matplotlib inline
plt.style.use('fivethirtyeight') # 流行的数据可视化主题
可以这样从CSV中导入数据:
# 使用Pandas导入数据
pima = pd.read_csv('../data/pima.data')
pima.head()
手动添加标题,代码如下:
pima_column_names = ['times_pregnant',
'plasma_glucose_concentration', 'diastolic_blood_pressure',
'triceps_thickness', 'serum_insulin', 'bmi',
'pedigree_function', 'age', 'onset_diabetes']
pima = pd.read_csv('../data/pima.data', names=pima_column_names)
pima.head()
空准确率是指当模型总是预测频率较高的类别时达到的正确率。
pima['onset_diabetes'].value_counts(normalize=True)
# 空准确率,65%的人没有糖尿病
0 0.651042
1 0.348958
Name: onset_diabetes, dtype: float64
既然终极目标是研究数据的规律以预测是否会患糖尿病,那么可以对糖尿病患者和健康人的区别进行可视化。希望直方图可以显示一些规律,或者这两类之间的显著差异:
# 对plasma_glucose_concentration列绘制两类的直方图
col = 'plasma_glucose_concentration'
plt.hist(pima[pima['onset_diabetes']==0][col], 10, alpha=0.5, label='non-diabetes')
plt.hist(pima[pima['onset_diabetes']==1][col], 10, alpha=0.5, label='diabetes')
plt.legend(loc='upper right')
plt.xlabel(col)
plt.ylabel('Frequency')
plt.title('Histogram of {}'.format(col))
plt.show()
继续按此绘制其他列的直方图:
for col in ['bmi', 'diastolic_blood_pressure', 'plasma_glucose_concentration']:
plt.hist(pima[pima['onset_diabetes']==0][col], 10, alpha=0.5, label='non-diabetes')
plt.hist(pima[pima['onset_diabetes']==1][col], 10, alpha=0.5, label='diabetes')
plt.legend(loc='upper right')
plt.xlabel(col)
plt.ylabel('Frequency')
plt.title('Histogram of {}'.format(col))
plt.show()
上面的代码会输出3张直方图。
第一张直方图是两类(正常人和患者)的身体质量指数(BMI,body mass index)分布.
第二张直方图也表明两类人有显著区别,是在舒张压方面.
第三张直方图是血浆葡萄糖浓度方面的区别.
Seaborn可视化工具:
# 数据集相关矩阵的热力图
sns.heatmap(pima.corr())
# plasma_glucose_concentration很明显是重要的变量
下图是数据集的相关矩阵,显示了皮马人数据集中不同列的相关性。输出如下图所示。
相关矩阵显示,plasma_glucose_concentration(血浆葡萄糖浓度)和onset_diabetes(糖尿病)有很强的相关性。我们进一步研究onset_diabetes列的相关性数值:
pima.corr()['onset_diabetes'] # 相关矩阵
# plasma_glucose_concentration很明显是重要的变量
times_pregnant 0.221898
plasma_glucose_concentration 0.466581
diastolic_blood_pressure 0.065068
triceps_thickness 0.074752
serum_insulin 0.130548
bmi 0.292695
pedigree_function 0.173844
age 0.238356
onset_diabetes 1.000000
Name: onset_diabetes, dtype: float64
数据集中是否有数据点是空的(缺失值)。用Pandas DataFrame内置的isnull()方法:
pima.isnull().sum()
times_pregnant 0
plasma_glucose_concentration 0
diastolic_blood_pressure 0
triceps_thickness 0
serum_insulin 0
bmi 0
pedigree_function 0
age 0
onset_diabetes 0
dtype: int64
数据中没有缺失值。继续探索数据,首先用shape方法看看数据的行数和列数:
pima.shape # (行数, 列数)
(768, 9)
用下面的代码看看糖尿病的发病率:
pima['onset_diabetes'].value_counts(normalize=True)
# 空准确率,65%的人没有糖尿病
0 0.651042
1 0.348958
Name: onset_diabetes, dtype: float64
DataFrame内置的describe方法可以提供数据基本的描述性统计:
pima.describe() # 基本的描述性统计
分析每列属性的实际意义可以得出结论,下面这些列中的缺失值用0填充了:
- plasma_glucose_concentration
- diastolic_blood_pressure
- triceps_thickness
- serum_insulin
- bmi
必须时刻保持警惕,尽可能地了解数据集,以便找到使用其他符号填充的缺失数据。务必阅读公开数据集的所有文档,里面有可能提到了缺失数据的问题。
如果数据集没有文档,缺失值的常见填充方法有:
- 0(数值型)
- unknown或Unknown(类别型)
- ?(类别型)
我们知道数据集中有5列存在缺失值,现在就开始深入研究如何解决这个问题。
处理数据集中的缺失值
两个最主要的处理方法是:
- 删除缺少值的行;
- 填充缺失值。
在进一步处理前,先用Python中的None填充所有的数字0,这样Pandas的fillna和dropna方法就可以正常工作了。我们可以手动将每列的0替换成None,方法如下:
# 被错误填充的缺失值是0
pima['serum_insulin'].isnull().sum()
0
pima['serum_insulin'] = pima['serum_insulin'].map(lambda x:x if x != 0 else None)
# 用None手动替换0
pima['serum_insulin'].isnull().sum()
# 检查缺失值数量
374
既可以手动一列列地操作,也可以用for循环和内置的replace方法加速,代码如下:
# 直接对所有列操作,快一些
columns = ['serum_insulin', 'bmi', 'plasma_glucose_concentration', 'diastolic_blood_pressure', 'triceps_thickness']
for col in columns:
pima[col].replace([0], [None], inplace=True)
如果现在用isnull方法计算缺失值的数量,应该可以看见正确的结果:
pima.isnull().sum() # 现在有意义多了
times_pregnant 0
plasma_glucose_concentration 5
diastolic_blood_pressure 35
triceps_thickness 227
serum_insulin 374
bmi 11
pedigree_function 0
age 0
onset_diabetes 0
dtype: int64
删除有害的行
在处理缺失数据的两种办法中,最常见也最容易的方法大概是直接删除存在缺失值的行。
可以在Pandas中利用dropna方法获取新的DataFrame,如下所示:
# 删除存在缺失值的行
pima_dropped = pima.dropna()
当然,现在的问题是我们丢失了一些行。用下面的代码检查删除了多少行:
num_rows_lost = round(100*(pima.shape[0] - pima_dropped.shape[0])/float(pima.shape[0]))
print("lost {}% of rows".format(num_rows_lost))
# 大部分行都丢了
lost 49% of rows
下面对数据集做进一步的探索性数据分析,比较一下丢弃缺失值前后的统计数据:
# 丢弃缺失值前后的探索性数据分析
# 分成True和False两组
pima['onset_diabetes'].value_counts(normalize=True)
0 0.651042
1 0.348958
Name: onset_diabetes, d