你们好,这一期我将为你们带来个人pandas学习心得第二期:数据清理。这一步很是重要,通常在获取数据源以后,咱们紧接着就要开始这一步,以便为了以后的各类操做,简单来讲,咱们的目标就是让数据看起来赏心悦目,规规矩矩的,因此咱们会对原始的dataframe作一些必要的美容,包括规范命名,去除异常值,从新选择合适的index啊,处理缺失值,统一列的命名等等。python
这一期我会和你们分享一些比较好用常见的清洗方法。首先仍是让咱们来简单看一下本文将会用到的数据源:git
这篇文章我会从如下几个方面来和你们分享个人心得体会:github
这里咱们会用到 property_data.csv这个数据集,在开始处理缺失值以前,咱们能够先话一分钟仔细想一想,为何实际生活中的数据历来是不完整的,缘由基本有几个方面:数据库
由于这些缘由,我每次在处理missing value的时候都会问本身两个基础问题:segmentfault
带着这些疑问,咱们能够开始了,首先让咱们简单读取一下数据,利用head函数看看前5行,若是你还对pandas的基础知识有疑问,能够看看我上一篇文章:Pandas之旅(一): 让咱们把基础知识一次撸完,申精干货函数
import pandas as pd import numpy as np import os os.chdir("F:\\Python教程\\segmentfault\\pandas_share\\Pandas之旅_02 数据清洗")
# Read csv file into a pandas dataframe df = pd.read_csv("property_data.csv") # Take a look at the first few rows df.head()
PID | ST_NUM | ST_NAME | OWN_OCCUPIED | NUM_BEDROOMS | NUM_BATH | SQ_FT | |
---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3 | 1 | 1000 |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3 | 1.5 | -- |
2 | 100003000.0 | NaN | LEXINGTON | N | NaN | 1 | 850 |
3 | 100004000.0 | 201.0 | BERKELEY | 12 | 1 | NaN | 700 |
4 | NaN | 203.0 | BERKELEY | Y | 3 | 2 | 1600 |
如今让咱们看看数据的一些关键列是什么:学习
这里能够给你们普及点房地产知识,有的时候房屋用途被明确规定,好比有的房产写的是"owner occupied only ")意思是说若是你买了,那这个房子会成为你的主要住所,不能用于出租之类的,简单理解就是自住测试
因此如今我能够自问自答第一个问题:数据集每一列有什么特色?spa
如今让咱们关注ST_NUM这一列:code
# Looking at the ST_NUM column df['ST_NUM']
0 104.0 1 197.0 2 NaN 3 201.0 4 203.0 5 207.0 6 NaN 7 213.0 8 215.0 Name: ST_NUM, dtype: float64
若是想查看该列的缺失值状况,咱们能够利用isnull()方法,若是出现缺失值,会返回True,反之返回false
df['ST_NUM'].isnull()
0 False 1 False 2 True 3 False 4 False 5 False 6 True 7 False 8 False Name: ST_NUM, dtype: bool
可是其实若是咱们打开csv文件,你会发现第3行是空白,还有一行在该列显示的是NA,因此结论已经有了:在pandas里表示缺省值的符号及时NA,换句话说,若是咱们要表示缺省值,标准写法是NA
一样的,这回让咱们关注一下NUM_BEDROOMS这一列,咱们发现出现了4种类型的表达缺省值的标记:
经过刚才的实践,咱们已经肯定NA是pandas能够识别的,那么其余的符号呢,如今让咱们来测试一下
df['NUM_BEDROOMS']
0 3 1 3 2 NaN 3 1 4 3 5 NaN 6 2 7 1 8 na Name: NUM_BEDROOMS, dtype: object
df['NUM_BEDROOMS'].isnull()
0 False 1 False 2 True 3 False 4 False 5 True 6 False 7 False 8 False Name: NUM_BEDROOMS, dtype: bool
能够看到pandas识别了n/a 和NA两种符号,可是接下来咱们要考虑一个问题,假设你是房地产公司的地区总经理,你每周会收到不一样地区的负责人提交的表格,
这些人中有的喜欢用--表示空白值,有的人喜欢用na,那应该怎么办?
最简单的方式就是将全部表示空白值的符号统一放在list中,让后让pandas一次性识别:
# Making a list of missing value types missing_values = ["na", "--"] df = pd.read_csv("property_data.csv", na_values = missing_values)
如今咱们来看看到底发生了什么?
df
PID | ST_NUM | ST_NAME | OWN_OCCUPIED | NUM_BEDROOMS | NUM_BATH | SQ_FT | |
---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN |
2 | 100003000.0 | NaN | LEXINGTON | N | NaN | 1 | 850.0 |
3 | 100004000.0 | 201.0 | BERKELEY | 12 | 1.0 | NaN | 700.0 |
4 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 |
5 | 100006000.0 | 207.0 | BERKELEY | Y | NaN | 1 | 800.0 |
6 | 100007000.0 | NaN | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 |
7 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN |
8 | 100009000.0 | 215.0 | TREMONT | Y | NaN | 2 | 1800.0 |
咱们能够发现只要missing_value中记录的表达空白值的符号,所有变成了规整的NaN
刚刚咱们已经简单了解了在pandas中如何处理缺失值的,还有一种状况,让咱们来看OWN_OCCUPIED这一列,这一列的答案只能是Y,N 可是咱们发现数据集意外地出现了12,属于类型不对称
df['OWN_OCCUPIED'].isnull()
0 False 1 False 2 False 3 False 4 False 5 False 6 True 7 False 8 False Name: OWN_OCCUPIED, dtype: bool
如今咱们发现12是异常值,由于它是类型错误,因此咱们能够简单经过下面这个方法来检测,
# Detecting numbers cnt=0 for row in df['OWN_OCCUPIED']: try: int(row) df.loc[cnt, 'OWN_OCCUPIED']=np.nan except ValueError: pass cnt+=1
咱们这里的策略是:
这样咱们会把OWN_OCCUPIED这一列中全部类型不对的值转化为NaN,如今来看结果:
df['OWN_OCCUPIED']
0 Y 1 N 2 N 3 NaN 4 Y 5 Y 6 NaN 7 Y 8 Y Name: OWN_OCCUPIED, dtype: object
pandas提供了更为简洁的方式,可让咱们总体了解全部column的空值:
df.isnull().sum()
PID 1 ST_NUM 2 ST_NAME 0 OWN_OCCUPIED 2 NUM_BEDROOMS 3 NUM_BATH 1 SQ_FT 2 dtype: int64
或者若是咱们只想知道数据是否存在空值,那么可使用如下的命令:
# Any missing values? df.isnull().values.any()
True
若是咱们想要替换掉缺失值,能够用fillna方法
# Replace missing values with a number df['ST_NUM'].fillna(125, inplace=True)
或者咱们能够经过准肯定位来替换缺失值:
# Location based replacement df.loc[2,'ST_NUM'] = 125
替换缺失值的一种很是常见的方法是使用中位数:
# Replace using median median = df['NUM_BEDROOMS'].median() df['NUM_BEDROOMS'].fillna(median, inplace=True) df
PID | ST_NUM | ST_NAME | OWN_OCCUPIED | NUM_BEDROOMS | NUM_BATH | SQ_FT | |
---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN |
2 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 |
3 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 |
4 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 |
5 | 100006000.0 | 207.0 | BERKELEY | Y | 2.5 | 1 | 800.0 |
6 | 100007000.0 | 125.0 | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 |
7 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN |
8 | 100009000.0 | 215.0 | TREMONT | Y | 2.5 | 2 | 1800.0 |
如今假设由于一些需求,须要咱们统一修改列名,把列名改成小写,咱们能够结合列表推导式轻易实现
df.rename(str.lower, axis='columns',inplace =True) df.columns
Index(['pid', 'st_num', 'st_name', 'own_occupied', 'num_bedrooms', 'num_bath', 'sq_ft'], dtype='object')
或者须要把列名中的_改成-:
new_cols = [c.replace("_","-") for c in df.columns] change_dict =dict(zip(df.columns,new_cols)) df.rename(columns=change_dict,inplace=True) df
pid | st-num | st-name | own-occupied | num-bedrooms | num-bath | sq-ft | |
---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN |
2 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 |
3 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 |
4 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 |
5 | 100006000.0 | 207.0 | BERKELEY | Y | 2.5 | 1 | 800.0 |
6 | 100007000.0 | 125.0 | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 |
7 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN |
8 | 100009000.0 | 215.0 | TREMONT | Y | 2.5 | 2 | 1800.0 |
这里我没有写的精简一些,反而是复杂了,主要是想让你们回忆起以前我分享的dict使用技巧中的内容,注意这里inplace=True,致使的结果是咱们的的确确修改了df全部的列名
假如目前咱们须要新增一列,根据房屋面积大小来赋值,咱们先随意把缺失值补上:
df['sq-ft'].fillna('0.0')
0 1000 1 0.0 2 850 3 700 4 1600 5 800 6 950 7 0.0 8 1800 Name: sq-ft, dtype: object
而后新建一列rank来根据房屋面积大小赋值S=small,M=medium,B=big:
df["rank"]= pd.cut(df['sq-ft'], [0, 800, 1600, np.inf], labels=("S","M","B")) df
pid | st-num | st-name | own-occupied | num-bedrooms | num-bath | sq-ft | rank | |
---|---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 | M |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN | NaN |
2 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 | M |
3 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 | S |
4 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 | M |
5 | 100006000.0 | 207.0 | BERKELEY | Y | 2.5 | 1 | 800.0 | S |
6 | 100007000.0 | 125.0 | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 | M |
7 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN | NaN |
8 | 100009000.0 | 215.0 | TREMONT | Y | 2.5 | 2 | 1800.0 | B |
具体实现方法咱们以后会说,这里主要是用到了pandas的cut方法,很是便捷
在许多状况下,使用数据的惟一值标识字段做为其索引是有帮助的。这里可能咱们的数据不太合适,所以咱们先伪造一列Fake_Index来模拟真实场景中的真正索引
df["Fake_Index"]=["A00"+str(i) for i in range(len(df))] df
pid | st-num | st-name | own-occupied | num-bedrooms | num-bath | sq-ft | rank | Fake_Index | |
---|---|---|---|---|---|---|---|---|---|
0 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 | M | A000 |
1 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN | NaN | A001 |
2 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 | M | A002 |
3 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 | S | A003 |
4 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 | M | A004 |
5 | 100006000.0 | 207.0 | BERKELEY | Y | 2.5 | 1 | 800.0 | S | A005 |
6 | 100007000.0 | 125.0 | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 | M | A006 |
7 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN | NaN | A007 |
8 | 100009000.0 | 215.0 | TREMONT | Y | 2.5 | 2 | 1800.0 | B | A008 |
如今咱们添加的最后一列很是像真正的房屋Id了,让咱们来看看这个伪造的索引是否是惟一值,能够利用is_unique来检验:
df.Fake_Index.is_unique
True
没有问题,如今咱们能够放心地把这列设置为咱们真正的索引:
df = df.set_index('Fake_Index') df
pid | st-num | st-name | own-occupied | num-bedrooms | num-bath | sq-ft | rank | |
---|---|---|---|---|---|---|---|---|
Fake_Index | ||||||||
A000 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 | M |
A001 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN | NaN |
A002 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 | M |
A003 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 | S |
A004 | NaN | 203.0 | BERKELEY | Y | 3.0 | 2 | 1600.0 | M |
A005 | 100006000.0 | 207.0 | BERKELEY | Y | 2.5 | 1 | 800.0 | S |
A006 | 100007000.0 | 125.0 | WASHINGTON | NaN | 2.0 | HURLEY | 950.0 | M |
A007 | 100008000.0 | 213.0 | TREMONT | Y | 1.0 | 1 | NaN | NaN |
A008 | 100009000.0 | 215.0 | TREMONT | Y | 2.5 | 2 | 1800.0 | B |
如今对数据的操做容易多了,咱们不少事情能够经过索引完成:
# 根据索引名称切片 df['A000':'A003']
pid | st-num | st-name | own-occupied | num-bedrooms | num-bath | sq-ft | rank | |
---|---|---|---|---|---|---|---|---|
Fake_Index | ||||||||
A000 | 100001000.0 | 104.0 | PUTNAM | Y | 3.0 | 1 | 1000.0 | M |
A001 | 100002000.0 | 197.0 | LEXINGTON | N | 3.0 | 1.5 | NaN | NaN |
A002 | 100003000.0 | 125.0 | LEXINGTON | N | 2.5 | 1 | 850.0 | M |
A003 | 100004000.0 | 201.0 | BERKELEY | NaN | 1.0 | NaN | 700.0 | S |
# 根据索引位置切片 df.iloc[1:3, 0:3]
pid | st-num | st-name | |
---|---|---|---|
Fake_Index | |||
A001 | 100002000.0 | 197.0 | LEXINGTON |
A002 | 100003000.0 | 125.0 | LEXINGTON |
# 定位到具体元素 df.iloc[1,2]
'LEXINGTON'
我把这一期的ipynb文件和py文件放到了GIthub上,你们若是想要下载能够点击下面的连接:
这一期先讲到这里,但愿你们可以继续支持我,完结,撒花