你們好,這一期我將爲你們帶來個人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上,你們若是想要下載能夠點擊下面的連接:
這一期先講到這裏,但願你們可以繼續支持我,完結,撒花