脏数据就是在物理上临时存在过,但在逻辑上不存在的数据。
数据清洗是整个数据分析过程的第一步,就像做一道菜之前需要先择菜洗菜一样。数据分析师经常需要花费大量的时间来清洗数据或者转换格式,这个工作甚至会占整个数据分析流程的80%左右的时间。
在这篇文章中,我尝试简单地归纳一下用Python来做数据清洗的7步过程,供大家参考。
一、数据预处理
一、数据预处理
部署环境,导入分析包和数据
#导入数据分析包
import pandas as pdimport numpy as np
#导入csv数据
#dtype = str,最好读取的时候都以字符串的形式读入,不然可能会使数据失真#比如一个0010008的编号可能会读取成10008fileNameStr = './Actual transactions from UK retailer.csv'DataDF = pd.read_csv(fileNameStr,encoding = "ISO-8859-1",dtype = str)# encoding = "ISO-8859-1" -- 用什么解码,一般会默认系统的编码,如果是中文就用 "utf-8"DataDF = pd.read_csv(fileNameStr,encoding = "utf-8",dtype = str)
2. 尝试去理解这份数据集
我们可以通过对数据集提问来判断这份数据能不能满足解答我们的问题,数据是否干净需不需要进一步处理,问题包括但不限于:数据集多少数据?包含了什么字段?字段格式是什么?字段分别代表什么意义字段之间的关系是什么?可以用做什么分析?或者说能否满足了对分析的要求?有没有缺失值;如果有的话,缺失值多不多?现有数据里面有没有脏数据?尤其需要注意人工输入的数据,经常会出现名称写错,多输入空格等等的情况
3. 下面我们就结合代码来看一下数据
#1 从宏观一点的角度去看数据:查看dataframe的信息
DataDF.info()
也可以用这两条来看:
#1.1查看每一列的数据类型
DataDF.dtypes#1.2有多少行,多少列DataDF.shape
# 2.检查缺失数据
# 如果你要检查每列缺失数据的数量,使用下列代码是最快的方法。# 可以让你更好地了解哪些列缺失的数据更多,从而确定怎么进行下一步的数据清洗和分析操作。DataDF.isnull().sum().sort_values(ascending=False)
# 3.是抽出一部分数据来,人工直观地理解数据的意义,尽可能地发现一些问题
DataDF.head()
可以看到:
1)Country和UnitPrice都出现了NaN值,需要去掉
2)InvoiceDate的时间出现具体时分,可以删去
3)Description大概率是人工填写的数据,一般都会有比较多格式问题。
猜测会存在有标点符号掺杂/大小写不一致等问题,所以进一步这些人工填写数据的去重项拎出来研究一下
# 查看这个商品名称的去重项
DataDF['Description'].unique()
# 设置输出全部的内容
# threshold就是设置超过了多少条,就会呈现省略#(比如threshold=10的意思是超过10条就会省略)np.set_printoptions(threshold=np.inf)
发现有很多空格的问题
根据第一步数据预处理后,整理一下该数据集有下列问题需要处理:
1)调整数据类型:由于一开始用到了str来导入,打算后期再更换格式,需要调整数据类型。
2)修改列名:该数据的名称不易于理解,需要改列名
3)选择部分子集:因为有部分列在数据分析中不需要用到
4)可能存在逻辑问题需要筛选:比如Unit Price为负
5)格式一致化:Description可能会存在有标点符号掺杂/大小写不一致/空格重复出现等问题
6)消灭空值:CustomerID、Description、Country和UnitPrice都出现了NaN值,需要去掉
于是下面就开始后续的数据清洗6步
二、调整数据类型
数据类型调整前
#字符串转换为数值(整型)
DataDF['Quantity'] = DataDF['Quantity'].astype('int')#字符串转换为数值(浮点型)DataDF['UnitPrice'] = DataDF['UnitPrice'].astype('float')
日期调整前(为求简便这里用已经剔除分秒,剔除的办法后面在格式一致化的空格分割再详细说)
#数据类型转换:字符串转换为日期
#errors='coerce' 如果原始数据不符合日期的格式,转换后的值为空值NaTDataDF.loc[:,'InvoiceDate']=pd.to_datetime(DataDF.loc[:,'InvoiceDate'],format='%d/%m/%Y',errors='coerce')#!!⚠️ format 是你[原始数据]中日期的格式%y 两位数的年份表示(00-99)%Y 四位数的年份表示(000-9999)%m 月份(01-12)%d 月内中的一天(0-31)%H 24小时制小时数(0-23)%I 12小时制小时数(01-12)%M 分钟数(00-59)%S 秒(00-59)
日期类型调整后
数据类型调整完毕
三、修改列名
修改前
#建立字典字典:旧列名和新列名对应关系
colNameDict = {'InvolceDate':'SaleDate','StockCode':'StockNo'}#!! ⚠️一定要旧列名放在冒号前#每组对应关系以[逗号]隔开salesDf.rename(columns = colNameDict,inplace=True)
修改后
四、选择部分子集
这是一个8列*541909行的数据集。
#选择子集,选择其中一列
subDataDF1=DataDF["InvoiceDate"]
#选择子集,选择其中两列
subDataDF1=DataDF[["InvoiceDate","UnitPrice"]]
利用切片筛选 数 据功能 df.loc
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html#pandas.DataFrame.loc
loc这个代码有点像Excel里面的鼠标左键,可以随意拉动你需要的数据进行切片。
以逗号作为隔开的界限,左边为index,右边为column
subDataDF1=DataDF.loc[:,"InvoiceDate"]
subDataDF1#单一个冒号意味着不作限制的全选
subDataDF2=DataDF.loc[0:9,:]
subDataDF2
subDataDF3=DataDF.loc[1:9,"StockCode":"CustomerID"]
subDataDF3
五、逻辑问题需要筛选
还是Dataframe.loc这个函数的知识点。
由于loc还可以判断条件是否为True
DataDF.loc[:,'UnitPrice']>0
一般来说价格不能为负,所以从逻辑上来说如果价格是小于0的数据应该予以筛出
#删除异常值:通过条件判断筛选出数据
#查询条件querySer=DataDF.loc[:,'Quantity']>0#应用查询条件print('删除异常值前:',DataDF.shape)DataDF=DataDF.loc[querySer,:]print('删除异常值后:',DataDF.shape)
六、格式一致化
大小写/去除空格
将我们数据中所有的Descrption改成大写:
DataDF['Description']= DataDF['Description'].str.upper()
类似的代码还有 字符串修改方法:
str().
upper()lower()title()lstrip()strip()DataDF['Description']= DataDF['Description'].str.strip()
2. 去除字符串符号 去乱码
3. 空格分割
#定义函数:分割InvoiceDate,获取InvoiceDate
#输入:timeColSer InvoiceDate这一列,是个Series数据类型#输出:分割后的时间,返回也是个Series数据类型def splitSaletime(timeColSer):timeList=[]for value in timeColSer:#例如2018/01/01 12:50,分割后为:2018-01-01dateStr=value.split(' ')[0]timeList.append(dateStr)#将列表转行为一维数据Series类型timeSer=pd.Series(timeList)return timeSer
最后再赋值回去
DataDF.loc[:,'InvoiceDate']=splitSaletime(DataDF.loc[:,'InvoiceDate'])
七、处理缺失值
python缺失值有3种:
1)Python内置的None值
2)在pandas中,将缺失值表示为NA,表示不可用not available。
3)对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。后面出来数据,如果遇到错误:说什么float错误,那就是有缺失值,需要处理掉
所以,缺失值有3种:None,NA,NaN
那None和NaN有什么区别呢:
None是Python的一种数据类型,
NaN是浮点类型
两个都用作空值
1、去除缺失值
# 再一次提醒检查缺失数据
DataDF.isnull().sum().sort_values(ascending=False)
去除缺失值的知识点:
DataFrame.dropna
DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
# 默认(axis=0)是逢空值剔除整行,设置关键字参数axis=1表示逢空值去掉整列
# 'any'如果一行(或一列)里任何一个数据有任何出现Nan就去掉整行,‘all’一行(或列)每一个数据都是Nan才去掉这整行DataDF.dropna(how='any')DataDF.dropna(how='all')# 更精细的thresh参数,它表示留下此行(或列)时,要求有多少[非缺失值]DataDF.dropna(thresh = 6 )
2、填充缺失内容:某些缺失值可以进行填充,方法有以下四种:
1) 以业务知识或经验推测(默认值)填充缺失值
2) 以同一指标的计算结果(均值、中位数、众数等)填充缺失值
3) 用相邻值填充缺失值
4) 以不同指标的计算结果填充缺失值
去除缺失值的知识点:
DataFrame.fillna
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html#pandas.DataFrame.fillna
1) 用默认值填充- df.fillna(' ')
我们应该去掉那些不友好的 NaN 值。但是,我们应该用什么值替换呢?这个时候可能要结合你对这个数据集的理解,看填充什么数据才是比较合适,以下是一下常用的方法。
在这个数据集中,我们大致判断CustomerID如果是不太重要的,就我们可以用使用""空字符串或其他默认值。
DataDF.Country= DataDF.Country.fillna('Not Given')
上面,我们就将“country”整个列使用“”空字符串替换了,或者,我们也可以轻易地使用“Not Given”这样的默认值进行替换。
如果想了解更多 fillna() 的详细信息参考 pandas.DataFrame.fillna
pandas.pydata.org
2) 以同一指标的计算结果(均值、中位数、众数等)填充缺失值
平均值- df.fillna(df.mean())
使用数字类型的数据有可能可以通过这样的方法来去减少错误。
比如,这个案例里面的价格。如果用0或者"Not Given"等来去填充都不太合适,但这个大概的价格是可以根据其他数据估算出来的。
DataDF.UnitPrice = DataDF.UnitPrice.fillna(DataDF.UnitPrice.mean())
3)除此,还有一种常见的方法,就是用相邻的值进行填充,
这在时间序列分析中相当常见,用前面相邻的值向后填充,也可以用后面相邻的值向前填充。
print(DataDF)
print(DataDF.UnitPrice.fillna(method='ffill')) # 前向后填充print(DataDF.UnitPrice.fillna(method='bfill')) # 后向前填充
填充后
4) 以不同指标的计算结果填充缺失值
关于这种方法年龄字段缺失,但是有屏蔽后六位的身份证号可以推算具体的年龄是多少。