前言
1. 刪除重復
2. 異常值監測
3. 替換
4. 數據映射
5. 數值變量類型化
6. 創建啞變量
統計師的Python日記【第7天:數據清洗(1)】
前言
根據我的Python學習計划:
Numpy → Pandas → 掌握一些數據清洗、規整、合並等功能 → 掌握類似與SQL的聚合等數據管理功能 → 能夠用Python進行統計建模、假設檢驗等分析技能 → 能用Python打印出100元錢 → 能用Python幫我洗衣服、做飯 → 能用Python給我生小猴子......
上一篇的數據合並,以及本篇的數據清洗,都是非常非常實用的技能。我們用Python做數據分析,其實會有80%的功夫花在這些操作上面。我曾經去德國專門學過如何用做SAS數據清洗,數據清洗有一個專門的流程,涉及到數據缺失處理、變量值覆蓋、日期時間數據、異常值、多選題數據處理、文本處理等等。日常項目中,可能隨時用到這里面的某個技能,今天,就來學習一下Python的數據清洗吧!
現在有一份心臟病患者的數據,經過問卷調查之后,最終錄入數據如下:
-
Age:年齡
-
Areas:來自哪里,有A/B/C/D四個地區
-
ID:患者的唯一識別編號
-
Package:每天抽幾包煙,缺失的為-9,代表不抽煙
-
SHabit:睡眠習慣,1-早睡早起;2-晚睡早起;3-早睡晚起;4-晚睡晚起
為了學習方便,假設這里就這些變量吧。
看完這個變量說明我不淡定了,這個數據存在很多問題啊!Age是年齡?158是什么鬼??還有6歲小孩,每天抽1包煙?ID是唯一編號嗎?為什么有3個1號、2個5號、2個9號、2個10號?
這個數據問題太多了,因此我要逐一來清洗一下,順便學一下數據清洗方面的知識。
1. 刪除重復
3個1號、2個5號、2個9號、2個10號。這是數據錄入中經常出現的問題——重復錄入了,所以首先我要把那么多占空間又沒用的重復數據剔除。
介紹兩個方法:data.duplicated() 和 data.drop_duplicates(),前者標記出哪些是重復的(true),后者直接將重復刪除。
所以drop.duplicates直接就將重復值刪除了,默認保留第一條。
以上是按照“有兩行數據,這兩行數據的所有變量值都一樣,這么這兩行就算重復數據”,但有時候我們會只根據一個變量來剔除重復,比如值根據Areas這個變量,那么A/B/C/D四個地區只會保留第一條,傳入take_last=True則保留最后一個:
A/B/C/D每個地區值保留一條數據了。
2. 異常值檢測
在第一步剔除重復值之后。得到了無重復數據的data_noDup:
第二步,我想檢測一下數據中有沒有異常值。首先可以用 describe() 進行一個描述分析,在第五天的學習中(第5天:Pandas,露兩手)已經學過如何對數據進行描述:
有兩個變量值得我們注意,一個是age,最大值158、最小值6,肯定有問題,另一個是package,最小值是-9,存在缺失。
用 data[條件] 的方式可以看一下有多少age大於100、age小於10,、package為-9的:
好了,檢測完畢,現在來處理這些異常值。
3. 替換
我要把異常的年齡替換成缺失,把package等於-9的替換成0(換成0是因為,不抽煙其實也就是抽煙數量為0,這樣還能少一些缺失值)。
替換的方式有2種,字典,或者替換關系組成的數組:
(1)data.replace([A, B], [A_R, B_R]),如果這里替換之后的值A_R和B_R是一樣的,那么[A_R,B_R]直接是A_R就可以了
(2)data.replace({A:A_R, B:B_R}),這是字典的方式。
所以,這里想要將age的6、158替換成缺失,就應該為:
data_noDup['Age'].replace([158, 6], np.nan)
將package的-9替換成0:
data_noDup['Package'].replace(-9, 0)
替換之后的數據命名為data_noDup_rep:
4. 數據映射
接下來的一些處理,是為了變量能夠更加便於分析,首先是要進行數據映射。什么是映射呢?以Areas為例,Areas取四個地區:A/B/C/D,這四個地區在分析的時候並沒有什么意義,但A/B/C為城市,D為農村,這個很有意義,所以我要根據areas創建新變量CType:U-城市、R-農村,映射關系如下:
方法就是寫一個映射字典,把A/B/C變成U,把D變成R:
areas_to_ctype={'A':'U','B':'U','C':'U','D':'R'}
然后使用 map(映射字典) 去創建新變量CType:
data_noDup_rep['CType']=data['Areas'].map(areas_to_ctype)
其實用替換也可以,但是替換是在原列上替換,而映射自己可以新建一個變量。
5. 數值變量類型化
接下來還要處理的變量是年齡Age,需要分成四組,
-
0:30歲以下,也就是0到30歲
-
1:30-40歲
-
2:40-50歲
-
3:50歲以上,不妨設為50-100歲
這個問題如果用映射MAP的話就麻煩了,每一個年齡都要寫一個映射。使用 cut 函數來分割,就可以自己分割成幾個組。
1)首先要設置幾個分割點:0、30、40、50、100:cutPoint=[0,30, 40, 50,80]
2)接着,用 cut(data, cutPoint) 的格式對age按照cutPoint進行划分:pd.cut(data_noDup_rep['Age'],cutPoint)
3)最后,將這個賦給新變量ageGroup:data_noDup_rep['ageGroup'] =pd.cut(data_noDup_rep['Age'],cutPoint)
這樣很不好看有木有?怎么把四個組分別用0、1、2、3來表示呢?
設定一個組標簽groupLabel=[0,1,2,3],指定 labels=groupLable 即可。
data_noDup_rep['ageGroup'] =pd.cut(data_noDup_rep['Age'],cutPoint, labels=groupLabel)
一個問題來了,依稀記得之前做過一個項目,樣本量有7000,年齡分組是按照分位數來分的,那再python中能否實現?
可以的,用 qcut(data, n) 就可以,按照分位數分n組,比如分2組,那么就按照中位數來分,分4組,就按照四分位數來分。對這個例子我分兩組:
data_noDup_rep['ageGroup'] =pd.qcut(data_noDup_rep['Age'],2)
6. 創建啞變量
啞變量一般用於兩種情況:一是變量值是無序並列的,比如例子中的SHabit,四個選項1、2、3、4是並列的;另一種就是多選題,也需要生成啞變量。
以本例中的SHabit(睡眠情況)為例,四個取值是並列的,沒有順序,因此我們要把這1個問題變成4個:
SHabit(睡眠習慣,1-早睡早起;2-晚睡早起;3-早睡晚起;4-晚睡晚起)
變成:
SHabit_1:是否早睡早起?(0-否,1-是)
SHabit_2:是否晚睡早起?(0-否,1-是)
SHabit_3:是否早睡晚起?(0-否,1-是)
SHabit_4:是否晚睡晚起?(0-否,1-是)
使用 get.dummies( data[‘SHabit’] ) 就可以直接搞定:
生成了四個變量。要把它合並入原數據data_noDup_rep中去,只要用 merge 就可以了(上一文剛剛介紹過數據的合並,戳復習→第6天:數據合並)
data_noDup_rep_dum =pd.merge(data_noDup_rep, pd.get_dummies(data_noDup_rep['SHabit']),right_index=True, left_index=True)
(注:因為合並鍵值是索引,因此要用right_index=True和left_index=True)
一個問題:變量名1、2、3、4太丑了!
可以在get_dummies函數中加 prefix=’’ 選項為名字加一個前綴:
data_noDup_rep_dum =pd.merge(data_noDup_rep, pd.get_dummies(data_noDup_rep['SHabit'], prefix='SHabit' ), right_index=True, left_index=True)
變量比較多,所以換行顯示了。還有一種情況,如果SHabit是多選呢?每個人的睡眠習慣不止一種,像這樣:
這樣的多選題數據,在分析中肯定一點用沒有,處理的方法也是生成啞變量,如何生成?將在【第8天:數據清洗(2)文本分析】中學習,除此之外,還要學習如何進行分列處理、如何處理文本數據中的空白,如何使用正則表達式。
前言
1. 去除空白
2. 分列
3. 把多選題的文本處理成啞變量
4. 下集預告
統計師的Python日記【第8天:數據清洗(2)文本處理】
前言
根據我的Python學習計划:
Numpy → Pandas → 掌握一些數據清洗、規整、合並等功能 → 掌握類似與SQL的聚合等數據管理功能 → 能夠用Python進行統計建模、假設檢驗等分析技能 → 能用Python打印出100元錢 → 能用Python幫我洗衣服、做飯 → 能用Python給我生小猴子......
從第6天開始入手學習了一些非常實用的Python數據處理技能,昨天(第7天)學習了數據的清洗,用的例子是一份心臟病病人的問卷數據:
為了方便,假設只有這些變量:
-
Age:年齡
-
Areas:來自哪里,有A/B/C/D四個地區
-
ID:患者的唯一識別編號
-
Package:每天抽幾包煙,缺失的為-9,代表不抽煙
-
SHabit:睡眠習慣,1-早睡早起;2-晚睡早起;3-早睡晚起;4-晚睡晚起
我把這份數據中存在的問題一一處理了,包括:
-
重復值刪除
ID為1的出現了三次、ID為5的出現了兩次、ID為9和10的都分別出現了兩次。
-
異常值檢測
年齡還有158歲的、還有6歲的並且已經開始吸煙。
-
替換
將年齡異常的替換成缺失,將抽煙為-9的替換成0(不抽煙就是抽煙數量為0,替換之后既合理又可以減少缺失值)
-
數據映射
將Areas四個地區分別映射成農村(R)和城市(U)。
-
數值變量類型化
將年齡再進行一個分組,從連續變量變成類型變量。
-
創建啞變量
SHabit睡眠習慣的4個取值是無序並列的,這種情況在分析的時候要變成啞變量
也留了一個問題:如果SHabit是多選呢?像這樣:
這個就更要創建啞變量了,就是把一個問題分成四個。但是用第7天的函數get_dummies()是搞不定的,這屬於文本格式。
好吧,那么今天就專門學習一下如何清洗亂七八糟的文本數據吧!
1. 去除空白
先等一會解決那個多選問題,從最基本的開始,如果我們的文本數據中,混入了很多空格該怎么辦?比如Areas這個變量:
這個問題不是鑽牛角尖,因為之前經常遇到這樣的情況,它會帶來很多麻煩,比如,我想篩選出Areas為A的數據,用 if Areas == ’A‘,那些帶空白的你就找不出來了。
之前知道strip()這個函數可以解決單個字符串的問題:
那么在Pandas中,是否可以直接用strip()?
Series是不可以直接用strip()的,.map(str.strip) 可以幫我解決:
data_noDup_rep_dum ['Areas'].map(str.strip)
2. 分列
很久之前,使用excel的歲月里,分列功能沒少用過,有的數據是通過A:B的形式儲存在一列中,分析的時候要把兩列劈開。這里假設數據的ID與性別“粘”在一起了,格式為 ID:Gender
分號前面的是ID,分號后面的代表性別,0為男性,1為女性。split() 可以幫我把它們劈開,如果是單個字符串,直接使用即可:
但對於Pandas的數據結構,則要寫循環:
IDGender = pd.DataFrame((x.split(':') for x in data_noDup_rep_dum.ID))
此句的意思就是將data_noDup_rep_dum中的ID列,逐行給劈開,結果為:
注意看變量的列名為0、1,非常的丑,所以我決定再加上columns=指定一下列名
IDGender = pd.DataFrame((x.split(':') for x in data_noDup_rep_dum.ID), columns=['ID_new','Gender'])
然后再將這兩列粘貼到原數據中去:
pd.merge(IDGender, data_noDup_rep_dum, right_index=True,left_index=True)
錯了!!
為什么?注意看劈開之后的IDGender數據,索引是0、1、2、3...9,從0到9,而原數據data_noDup_rep_dum的索引則是0、3、4、5......是因為前期我在做清洗的時候很多數據被砍掉了,所以沒有1、2等。想索引一致,只需要在spilt(':')這個語句中加上index=data_noDup_rep_dum.index,換成原數據的索引即可。
IDGender = pd.DataFrame((x.split(':') for xin data_noDup_rep_dum.ID) , index=data_noDup_rep_dum.index, columns=['ID_new','Gender'] )
那么現在與原數據合並起來就行了:
print pd.merge(IDGender, data_noDup_rep_dum, right_index=True, left_index=True)
3. 把多選題的文本創建成啞變量
正如開頭所說的,如果SHabit是多選:
這種變量在分析的時候完全沒有用,必須要處理成啞變量,那么第7天中學的
get_dummies是否可以呢?試一下吧:
啞變量是生成了,但不是我想要的,我想要的就是四個選項變成的四個問題:1 2 3 4,當一個人多選了1和2,那么就在問題1下面和問題2下面賦值為1,其他賦值為0。
str.contains() 可以幫我解決,它的作用是,在SHabit列中查找某個元素,當含有這個元素時,賦值為True,否則為False:
data_noDup_rep_mul['SHabit_1'] = data_noDup_rep_mul['SHabit'].str.contains('1')
這個語句會生成一個新變量,SHabit_1,當原變量SHabit中包含1時,它為True,否則為False。
用同樣的方法生成SHabit_2、SHabit_3、SHabit_4:
后期再處理成0/1就可以了。
關於文本數據的處理,還有很多很多其他情況,比如下面這份文本數據:
(01)1872-8756
Body shop P1
Book B13
(05)9212-0098
PD(05)9206-4571
Shushuophone
(12) 6753-5513
None here
PD(12)6434-4532
P&DWashing
......(未顯示完)
假設這是一份產品名單,我現在只想把數字編碼的數據,也即紅色部分篩選出來,應該如何做?