之前已经讲过,数据分析的过程是这样的。
1.明确需求 2.数据采集 3.数据清洗 4.数据分析 5.数据报告+数据可视化
之前我们学习的一系列python模块,比如BeautifulSoup、Xpath、selenium等模块,都是属于数据清洗的范畴;matplotlib模块属于数据可视化模块。numpy和pandas模块我们侧重于学习了理论部分,接下来我们就要学习如何在实际工作中使用它们了。
干巴巴的理论不太好理解,我们还是打个比方来帮助记忆,如果把数据分析比作做菜:
数据分析过程 → 做菜过程 明确需求 明确做什么菜品 数据采集 去市场买菜 数据清洗 洗菜切菜配菜 数据分析 下锅炒菜 数据报告+数据可视化 拍照发朋友圈+吃菜
数据清洗的定义:从记录表、表格、数据库中检测、纠正或删除损坏或不准确记录的过程。
换个俏皮的说法,数据清洗就是把脏数据变为干净的数据的过程。
脏数据:没有经过处理的自身含有一定问题的数据(残缺数据、错误数据、重复数据、不符合规则的数据)。
干净数据:经过处理的完全符合规范要求的数据(可以直接带入模型)。
pd.read_csv pd.read_excel pd.read_sql pd.read_html
index columns head tail shape describe info dtype
除了info比较眼生,这些内容都是之前在pandas模块的DataFrame数据类型中就学习过的参数。
移除首尾空格 大小写转换
duplicated() # 查看是否含有重复数据 drop_duplicats() # 删除重复数据
我们来回忆一下,MySQL中如何快速判断某列是否含有重复的数据?
create database db1; create table t1(id int,name varchar(32),pwd int); insert into t1 values(1,'joe',123),(2,'simon',321),(3,'frank',222),(4,'jerry',111),(5,'eddie',222),(6,'eddie',333);
思路:
select count(name) from t1; # 统计表t1中name字段的数据量
如果只单纯给name字段计数的话,它只会给出name字段所有数据的数量。
所以我们首先要进行去重操作,然后统计字段去重后的数据。将得到的计数和原先未去重的计数相一比较,立马就能知道有没有重复的数据了。
select count(distinct(name)) from t1; # 将name字段的数据去重之后再计数
若两者数字相同,表示name列没有重复的数据,不同则表示含有重复的数据。
删除缺失值 填充缺失值 均值填充法 向前填充/向后填充(原理,视频03) 模型填补法,如随机森林 ……
向前填充即是指缺失值选用前面一个值进行填充,向后填充即是指缺失值选用后面一个值进行填充。
模型填补法这里暂且不展开讲,混个眼熟即可。
删除异常值 作为缺失值处理 修正异常值(同样是当做缺失值处理) 平均值修正、盖帽法修正
切割 筛选 ……
Y # 年 m # 月 d # 日 H # 时 M # 分 S # 秒
其中许多内容都是曾经学习过的知识点,具体用法可以参考过去的博客。
在整套数据清洗流程中,第3~第8步没有固定的顺序,可以根据需要随意调整操作顺序。前期不熟练时可以按序号来。
接下来我们就通过一个实际案例来看看数据清洗应该怎么做,同时这也是对之前学的个方面内容的一个全面回顾。
首先启动anaconda,等到载入完后运行jupyter notebook。新建文件夹并且重命名便于辨识。放入数据资料,新建python3文件。准备工作都完成后我们可以进行编程了。
首先还是雷打不动地载入需要用到的模块。
# 导入三剑客 import numpy as np import pandas as pd import matplotlib.pyplot as plt
data = pd.read_csv(r'qunar_freetrip.csv')
data.index # 查看行数 data.columns # 查看列字段 data.shape # 查看行数和列数 data.head() # 查看头几行数据 data.tail() # 查看尾几行数据 data.dtypes # 查看字段类型 data.describe() # 简单的数据统计 data.describe(include='all') # include参数可以指定计算的类型,all表示不论数据类型所有字段都进行数据统计(一般不加) data.info() # 既可以查看每个列的数据类型,还可以查看缺失数据量
data.info是个挺有意思的语句,不光列出了字段的名称,还列出了每个字段的总数、缺失数据数量、字段数据类型。
1.将Unnamed: 0列删除
data.drop(columns=['Unnamed: 0'],inplace=True) # 既可以删列数据也可以删行数据
2.列字段处理
data['列字段'] # 获取指定列字段的数据 eg: data['目的地'] # 报错
报错?但是语句没问题啊。
其实这里是有个小陷阱,如果观察不仔细很容易忽略。
data.columns # 查看列字段
有些列字段中带有空格,所以单写中文没法获取到数据。
所以应该先移除列字段空格。
new_columns = [] # 定义一个空列表 for col in data.columns: # 循环获取列字段名 new_columns.append(col.strip()) # 将去除空格后的列字段名加入到空列表中 data.columns = new_columns # 修改原列字段
这里还可以做优化,用列表生成的方式就能用一行代码解决。
data.columns = [col.strip() for col in data.columns] data.columns = new_columns
无论用哪种方式,改完后都可以正常索引获取数据了。
data['目的地'] # 可以正常获取
首先,查看是否含有重复数据。
data.duplicated().sum() # 查看重复数据的数量 data[data.duplicated()] # 查看重复数据
其中有100条重复数据。
# 直接删除重复数据 data.drop_duplicates(inplace=True) # 验证是否成功 data.duplicated().sum() # 0 data.shape # (5000, 13) # 删除重复数据后将引发行标签问题(行标签并不会重新排序) data.tail()
针对行标签是否需要重新排序,取决于业务需要,一般情况下可以不重排。
data.reset_index(inplace=True) # 重置行标签,重置后会默认保留原来的行标签 data.drop(columns=['index'],inplace=True) # 删除原来的行标签
重置行标签另有一种更简便的方法(支持自定义起始数字,推荐)
data.index = range(0,df.shape[0])
1.利用快速统计大致筛选出可能有异常值的列字段
data.describe()
节省这一列看起来很不正常,统计下来的总和居然比价格还要多出几块钱来。
可以利用公式求证我们的猜想。
公式计算:(列数据-列数据均值)/列数据标准差,如果值大于3就是有异常的。 sd = (((data['价格'] - data['价格'].mean()) / data['价格'].std())>3).sum() # 有可能是负数
得到的结果可能是负数,所以这里需要用到绝对值。
sd_abs = abs(((data['价格'] - data['价格'].mean()) / data['价格'].std())>3).sum() data[sd_abs]
2.同理验证节省是否有异常
sd2_abs = abs(((data['节省'] - data['节省'].mean()) / data['节省'].std())>3).sum() # 按照公式计算确实存在异常,但可能是由于促销、引流等因素导致的,针对这类数据可以保留
3.验证节省大于价格的
data[data['节省'] > data['价格']]
4.删除价格和节省都有异常的数据
pd.concat(data[]) # 合并表 data.drop(index=del_index,inplace=True) # 操作完后行标签又变了,需要的话还是要调整
# 查看缺失数据的方式 data.isnull().sum() # 求每个列字段中缺失数据的数量 data.info() # 也可以验证
1.修改出发地缺失数据
res = data[data['出发地'].isnull()] # 筛选出出发地为缺失值的数据
缺失数据项有时可能存在于其他列数据中。
接下来就是取值了。
res['路线名'].values # 从路线名字段中取值
针对本案例有多种获取城市名的方式。
# 1.字符串切片(适用性太窄,局限性太大,不推荐) d1[0:2] # 2.字符串切割(可以) d1.split('-')[0] # 3.正则表达式 import re re.findall('(.*?)-',d2) # 可用列表表达式来完成 data.loc[data.出发地.isnull(),'出发地'] = [i.split('-')[0] for i in data.loc[data.出发地.isnull(),'路线名'].values] # 验证是否还有缺失数据 data[data['出发地'].isnull()]
2.修改目的地缺失数据
# 筛选出目的地为缺失值的数据 res = data[data['目的地'].isnull()] res # 从路线名字段中取值 data.loc[data.目的地.isnull(),'路线名'].values d3 = '深圳-大连3天2晚 | 入住大连黄金山大酒店 + 南方航空/东海往返机票' # 正则筛选出需要的数据 re.findall('-(.*?)\d',d3) # 三元表达式整合 data.loc[data.目的地.isnull(),'目的地'] = [re.findalll('-(.*?)\d',i) for i in data.loc[data.目的地.isnull(),'路线名'].values] # 验证是否还有缺失数据 data[data['目的地'].isnull()]
3.修改价格缺失数据
round(data['价格'].mean(),1) #求价格均值,保留一位小数 data['价格'].fillna(round(data['价格'].mean(),1),inplace=True) # 用价格平均值填充入缺失数据 data[data['价格'].isnull()] # 验证是否还有缺失数据
4.修改节省缺失数据
round(data['节省'].mean(),1) #求节省均值,保留一位小数 data['节省'].fillna(round(data['节省'].mean(),1),inplace=True) # 用节省平均值填充入缺失数据 data[data['节省'].isnull()] # 验证是否还有缺失数据
如此一来,例题的数据清洗步骤就完成了。