从今天开始,我们再一起来学习数据分析,共同进步!
首先先来进行一个数据清洗的实战,使用比较经典的数据集,泰坦尼克号生存预测数据。
数据集下载地址
https://github.com/zhouwei713/DataAnalyse/tree/master/Titanic_dataset
导入数据
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns df = pd.read_csv('titanic_data.csv') df
数据集信息如下:
各字段含义
pclass:船票等级
sibsp:一同登船的兄弟姐妹或配偶数量
parch:一同登船的父母或子女数量
ticket:船票号码
fare:船票价格
cabin:船舱
embarked:登船地点
数据整体查看
拿到数据之后,我们先整体查看下数据信息
df.describe()
还有些列是非数值的,所以没有展示。
这里得到的各项指标,我们先保存不动,在后面处理缺失值时会有用到。
处理缺失值
首先查看缺失值
df.isnull().sum() >>> pclass 1 survived 1 name 1 sex 1 age 264 sibsp 1 parch 1 ticket 1 fare 2 cabin 1015 embarked 3 dtype: int64
可以看到,缺失值比较多的是 cabin 和 age
年龄处理
查看缺失占比
print('缺失占比 %.2f%%' %((df['age'].isnull().sum()/df.shape[0])*100)) >>> 缺失占比 20.15%
下面查看下年龄的分布情况
age = df['age'].hist(bins=15, color='teal', alpha=0.6) age.set(xlabel='age') plt.xlim(-10,85) plt.show()
从图中我们可以看出,整体数据是向左偏的,即大多数数据是小于平均值的,故而我们可以采用中位数来填补空值,而不是平均数。
从上面的 describe 函数的输出值也可以看出,平均值是 29.88,中位数是 28,显然中位数更加接近于大部分数据所在的区域。
使用中位数填充空缺的年龄值
data = df.copy() data['age'].fillna(df['age'].median(skipna=True), inplace=True)
仓位处理
查看缺失百分比
print('缺失百分比 %.2f%%' %((df['cabin'].isnull().sum()/df.shape[0])*100)) >>> 缺失百分比 77.48%
由于仓位信息已经缺失了大部分,所以这里选择直接删除处理
data.drop(columns=['cabin'], inplace=True)
登船地点处理
我们先来查看下登船地点这列数据的形式
print(df['embarked'].value_counts()) sns.countplot(x='embarked', data=df, palette='Set2') plt.show() >>> S 914 C 270 Q 123 Name: embarked, dtype: int64
可以看到,登船地点总共包含三类数据,S、C 和 Q,他们出现的次数分别为 914、270 和 123。
又因为该列数据总共缺失 3 个,缺失率很低,使用众数来填充这三个缺失值应该是没问题的。
使用众数填充
data['embarked'].fillna(df['embarked'].value_counts().idxmax(), inplace=True)
其他缺失值处理
对于其他列,只是缺失了一到两个,可以采用众数的方式来填充缺失值,也可以选择直接删除掉缺失的部分,不影响整体数据分布
data.dropna(axis=0, how='any', inplace=True)
最后,再查看确认下是否不存在缺失值了
data.isnull().sum() >>> pclass 0 survived 0 name 0 sex 0 age 0 sibsp 0 parch 0 ticket 0 fare 0 embarked 0 dtype: int64
其他特征列处理
对于 sibsp 和 parch 两列,我们可以抽象成是否是独自登船,这样就能够把两列合并为一列,并用 0,1 来表示是否独自登船。
我们新增一列 alone,把两列都是 0 的数据添加到新列中并设置为 0,把两列相加不为 0 的数据添加到新列中,并设置数值为 1。那么原来的两列就可以删除了。
data['alone']=np.where((data["sibsp"]+data["parch"])>0, 0, 1) data.drop('sibsp', axis=1, inplace=True) data.drop('parch', axis=1, inplace=True) data.head()
对于 embarked 和 sex 这两列,都是字符串类型的数据,需要转化为数字才能被算法模型分析处理。
这里可以采用独热编码的方式,来转换数据
data =pd.get_dummies(data, columns=["embarked","sex"]) data.head()
独热编码(one-hot encoding),是一种常用的数据转换方式,对于每一个特征,如果它有 m 个可能值,那么经过独热编码后,就变成了 m 个二元特征,这些特征互斥,每次只有一个激活。
对于 name 和 ticket 两列,由于他们的存在,对于我们的数据分析没有任何意义,往往会直接删除掉这样无意义的数据
data.drop('name', axis=1, inplace=True) data.drop('ticket', axis=1, inplace=True)
至此,我们就把一份原始的数据,处理成了比较标准的,易于数据分析的数据。
透视表分析
在处理数据之后,我们还可以使用透视表,整体分析下数据
这里主要查看下各个特征(船票等级,性别,仓位等)对于存活率的影响
注意数据集 df 与 data 的区别
性别透视表
首先来看下,不同性别,存活率的情况
sex_sur_table = pd.pivot_table(df, index=['sex'], values='survived') print(sex_sur_table) >>> survived sex female 0.727468 male 0.190985
女性存活率是远远高于男性的,ladies first。
船票等级与存活率
pclass_sur_table = pd.pivot_table(df, index=['sex'], columns=['pclass'], values='survived') print(pclass_sur_table) >>> pclass 1.0 2.0 3.0 sex female 0.965278 0.886792 0.490741 male 0.340782 0.146199 0.152130
可以看到,一等船票的女性存活率是非常高的,同时船票等级越高,无论男女,存活率都越高
不同年龄存活率
将年龄离散化处理
data['age_cut'] = pd.cut(data['age'], [0, 18, 90]) data['sex'] = df['sex'] print(data.head()) >>> pclass survived age fare alone embarked_C embarked_Q \ 0 1.0 1.0 29.0000 211.3375 1 0 0 1 1.0 1.0 0.9167 151.5500 0 0 0 2 1.0 0.0 2.0000 151.5500 0 0 0 3 1.0 0.0 30.0000 151.5500 0 0 0 4 1.0 0.0 25.0000 151.5500 0 0 0 embarked_S sex_female sex_male age_cut sex 0 1 1 0 (18, 90] female 1 1 0 1 (0, 18] male 2 1 1 0 (0, 18] female 3 1 0 1 (18, 90] male 4 1 1 0 (18, 90] female
年龄段与存活率
age_cut_sur_table = pd.pivot_table(data, index=['sex'], columns=['pclass', 'age_cut'], values='survived') print(age_cut_sur_table) >>> pclass 1.0 2.0 3.0 age_cut (0, 18] (18, 90] (0, 18] (18, 90] (0, 18] (18, 90] sex female 0.923077 0.969466 0.952381 0.870588 0.534483 0.474684 male 0.750000 0.321637 0.523810 0.093333 0.208333 0.142857
当然,透视表还有很多强大的功能,你可以试着探索更多。
数据清洗的重要性
要知道,一个好的数据分析师必定是一名数据清洗高手。在数据分析的过程中,数据清洗是最占用时间与精力的步骤。数据质量的高低,直接影响我们最后分析的结果,千万马虎不得。
数据质量的准则
那么既然数据清洗这么重要,我需要把原始数据处理到什么程度,才算是合格的待分析数据呢?如下我总结了一些业界的标准,可以供你参考。
- 完整性:数据集中是否存在空值,统计的字段是否完善。
- 全面性:某列数据,是否能够全面的反应真实的情况,是否只包含一部分情况。
- 合法性:数据的类型,内容,大小等是否合理。比如:是否有年龄超过 150 的,是否有成绩超过 1 万的,数据单位是否统一等等。
- 唯一性:数据是否存在重复记录。
在进行数据清洗的时候,一定要先耐心的观察数据,充分的理解每列数据的意义,从真实的情况出发分析数据是否有真实的含义,再根据生活工作中的经验,来逐一处理数据。
我们再用一个小例子来理解下
姓名 | 身高 | 体重 | 年龄 | 年龄 |
张飞 | 180 | 500 |
500 | |
关羽 | 181 | 100 | 28 |
28 |
刘备 | 1.78 | 160 | 30K |
30k |
赵云 | 175 | 140 | 23 |
23 |
曹操 | 180 | 150 | 37 |
37 |
赵云 | 175 |
140 |
23 | |
曹操 |
把数据转化成 Pandas 数据结构
mydata = pd.read_csv('mydata.csv', index_col='name') print(mydata) >>> height weight age age.1 name 张飞 180.00 NaN 500 500 关羽 181.00 100.0 28 28 刘备 1.78 160.0 30K 30K 赵云 175.00 140.0 23 23 曹操 180.00 150.0 37 37 赵云 175.00 140.0 23 23 典韦 NaN NaN NaN NaN
完整性
查看缺失值
mydata1 = mydata.copy() # copy mydata1.isnull().sum() # 查看总体缺失值 >>> height 1 weight 2 age 1 age.1 1 dtype: int64
一般是处理空值,空行等
mydata1['weight'].fillna(mydata1['weight'].mean(), inplace=True) # 使用平均值填充 #mydata1['height'].fillna(mydata['height'].value_counts().index[0], inplace=True) # 使用众数填充 mydata1.dropna(how='any', inplace=True) # 删除空行
一定要先执行空值填充,再执行删除空行的代码,否则含有空值的行都会被删除。
全面性
刘备的身高是“米”的单位,我们需要转换为“厘米”
mydata1.loc[mydata1['height']<100, 'height'] = mydata1[mydata1['height']<100]['height']*100
合理性
张飞的年龄是 500,显然不合理,需要处理。因为张飞是三弟,年龄需要比刘备和关羽小,就设置为 27 吧
mydata1.loc['张飞', 'age'] = 27
同时刘备的年龄还存在一个 K 字符,需要去掉
mydata1['age'].replace({r'[K]': ''}, regex=True, inplace=True)
唯一性
数据中还存在重复的行和列,也需要删除,保证数据的唯一性
mydata1.drop_duplicates(inplace=True) # 删除重复行 mydata1.drop('age.1', axis=1, inplace=True) # 删除不需要的列
最终我们的数据为
print(mydata1) >>> height weight age name 张飞 180.0 138.0 27 关羽 181.0 100.0 28 刘备 178.0 160.0 30 赵云 175.0 140.0 23 曹操 180.0 150.0 37
总结
本节我们共同完成了一个数据清洗的实战和一个练习小例子。对于缺失值,需要根据其缺失的百分比及数据分布情况,来决定如何填充缺失值。对于一些非数字类型的数据,可以选择独热编码等方式转换数据。还总结了数据清洗的准则,只要你遵循这些准则来处理数据,那么得到的数据基本就是“好”的数据了。
练习题
对于本节的例子,你还有哪些观点,对于缺失值的填充,是否还有其他的方式呢?对于 pclass,sex 等数据的缺失值,还可以怎么处理呢,欢迎留言讨论啊