數據規整化:清理、轉換、合併、重塑
合併數據集
pandas對象中的數據可以通過一些內置的方式進行合併:
- pandas.merge可根據一個或多個鍵將不同DataFrame中的行連接起來。SQL或其他關係型資料庫的用戶對此應該會比較熟悉,因為它實現的就是資料庫的連接操作。
- pandas.concat可以沿著一條軸將多個對象堆疊到一起。
- 實例方法combine_first可以將重複數據編接在一起,用一個對象中的值填充另一個對象中的缺失值。
資料庫風格的DataFrame合併
數據集的合併(merge)或連接(join)運算是通過一個或多個鍵將行連接起來的。這些運算是關係型資料庫的核心。pandas的merge函數是對數據應用這些演算法的主要切入點。
以一個簡單的例子開始:
In
[176]: df1 = pd.DataFrame({key:[b,b,a,c,a,a,b],
...:
data1:range(7)})
In
[177]: df2 = pd.DataFrame({key:[a,b,d],
...:
data2:range(3)})
In
[178]: df1
Out[178]:
data1 key
0
0 b
1
1 b
2
2 a
3
3 c
4
4 a
5
5 a
6
6 b
In
[179]: df2
Out[179]:
data2 key
0
0 a
1
1 b
2
2 d
In
[180]: pd.merge(df1,df2)
Out[180]:
data1 key data2
0
0 b 1
1
1 b 1
2
6 b 1
3
2 a 0
4
4 a 0
5
5 a 0
注意,我們並沒有指明要用哪個列進行連接。如果沒有指明,merge就會將重疊列的列名當作鍵。不過,最好顯式指定一下:
In
[181]: pd.merge(df1,df2,on=key)
Out[181]:
data1 key data2
0
0 b 1
1
1 b 1
2
6 b 1
3
2 a 0
4
4 a 0
5
5 a 0
如果兩個對象的列名不同,也可以分別進行指定:
In
[182]: df3 = pd.DataFrame({lkey:[b,b,a,c,a,a,b],
...:
data1:range(7)})
In
[183]: df4 = pd.DataFrame({rkey:[a,b,d],
...:
data2:range(3)})
In
[184]: pd.merge(df3,df4,left_on=lkey,right_on=rkey)
Out[184]:
data1 lkey data2 rkey
0
0 b 1 b
1
1 b 1 b
2
6 b 1 b
3
2 a 0 a
4
4 a 0 a
5
5 a 0 a
可能你已經注意到,結果裡面c和d以及與之相關的數據消失了。默認情況下,merge做的是「inner」連接;結果中的鍵是交集。其他方式還有「merge」、「right」以及「outer」。外連接求取的是鍵的並集,組合了左連接和右連接的效果:
In
[185]: pd.merge(df1,df2,how=outer)
Out[185]:
data1 key data2
0
0.0 b 1.0
1
1.0 b 1.0
2
6.0 b 1.0
3
2.0 a 0.0
4
4.0 a 0.0
5
5.0 a 0.0
6
3.0 c NaN
7
NaN d 2.0
要根據多個鍵進行合併,傳入一個由列名組成的列表即可:
In
[186]: left = pd.DataFrame({key1:[foo,foo,bar],
...:
key2:[one,two,one],
...:
lval:[1,2,3]})
In
[187]: right = pd.DataFrame({key1:[foo,foo,bar,bar],
...:
key2:[one,one,one,two],
...:
rval:[4,5,6,7]})
In
[188]: pd.merge(left,right,on=[key1,key2],how=outer)
Out[188]:
key1 key2 lval rval
0 foo one 1.0
4.0
1 foo one 1.0
5.0
2 foo two 2.0
NaN
3 bar one 3.0
6.0
4 bar two NaN
7.0
結果中會出現哪些鍵組合取決於所選的合併方式,你可以這樣理解:多個鍵形成一系列元組,並將其當作單個連接鍵(當然,實際上不是這麼回事)。
對於合併運算需要考慮的最後一個問題是對重複列名的處理,merge有一個實用的suffixes選項,用於指定附加到左右兩個DataFrame對象的重疊列名上的字元串:
In
[191]: pd.merge(left,right,on=key1)
Out[191]:
key1 key2_x lval key2_y rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
In
[192]: pd.merge(left,right,on=key1,suffixes=(_left,_right))
Out[192]:
key1 key2_left lval key2_right rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
merge函數的參數
參數說明left參與合併的左側DataFrameright參與合併的右側DataFramehowinner、outer、left、right其中之一。默認為inneron用於連接的列名。必須存在於左右兩個DataFrame中。如果未指定,且其他連接鍵也未指定,則以left和right列名的交集作為連接鍵left_on左側DataFrame中用作連接鍵的列right_on右側DataFrame中用作連接鍵的列left_index將左側的行索引用作器連接鍵right_index類似於 left_inex
sort根據連接鍵對合併後的數據進行排序,默認為Truesuffixes字元串值元組,用於追加到重疊列名的末尾,默認為(x,y)copy設置為False,可以在某些特殊情況下避免將數據複製到結果數據結構中。默認總是複製。
索引上的合併
DataFrame的連接鍵也可能位於其索引中。在這種情況下,你可以傳入left_index=True或 right_index=True
(或兩個都傳)以說明索引應該被用作連接鍵:
In
[7]: left1 = pd.DataFrame({key:[a,b,a,a,b,c],
...:
value:range(6)})
In
[8]: right1 = pd.DataFrame({group_val:[3.5,7]},index=[a,b])
In
[9]: left1
Out[9]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In
[10]: right1
Out[10]:
group_val
a 3.5
b 7.0
In
[11]: pd.merge(left1,right1,left_on=key,right_index=True)
Out[11]:
key value group_val
0 a 0
3.5
2 a 2
3.5
3 a 3
3.5
1 b 1
7.0
4 b 4
7.0
由於merge方法默認的是求連接鍵的交集,也可以通過外連接的方式得到它們的並集:
In
[12]: pd.merge(left1,right1,left_on=key,right_index=True,how="outer")
Out[12]:
key value group_val
0 a 0
3.5
2 a 2
3.5
3 a 3
3.5
1 b 1
7.0
4 b 4
7.0
5 c 5
NaN
對於層次化索引的數據,就更複雜點:
In
[13]: lefth = pd.DataFrame({key1:[Ohio,Ohio,Ohio,Nevada,Nevada],
...:
key2:[2000,2001,2002,2001,2002],
...:
data:np.arange(5)})
In
[14]: righth = pd.DataFrame(np.arange(12).reshape(6,2),index=[[Neveda,Nevada,Ohio,Ohio,Ohio,Ohio],[2001,2000,2000,2000,2001,2002]],columns=[event1,event2])
In
[15]: lefth
Out[15]:
data key1 key2
0
0
Ohio
2000
1
1
Ohio
2001
2
2
Ohio
2002
3
3
Nevada
2001
4
4
Nevada
2002
In
[16]: righth
Out[16]:
event1 event2
Neveda
2001
0
1
Nevada
2000
2
3
Ohio
2000
4
5
2000
6
7
2001
8
9
2002
10
11
這種情況下,必須以列表的形式指明用作合併鍵的多個列(注意對重複索引值的處理):
In
[17]: pd.merge(lefth,righth,left_on=[key1,key2],right_index=True)
Out[17]:
data key1 key2 event1 event2
0
0
Ohio
2000
4
5
0
0
Ohio
2000
6
7
1
1
Ohio
2001
8
9
2
2
Ohio
2002
10
11
In
[18]: pd.merge(lefth,righth,left_on=[key1,key2],right_index=True,how="outer")
Out[18]:
data key1 key2 event1 event2
0
0.0
Ohio
2000
4.0
5.0
0
0.0
Ohio
2000
6.0
7.0
1
1.0
Ohio
2001
8.0
9.0
2
2.0
Ohio
2002
10.0
11.0
3
3.0
Nevada
2001
NaN
NaN
4
4.0
Nevada
2002
NaN
NaN
4
NaN
Neveda
2001
0.0
1.0
4
NaN
Nevada
2000
2.0
3.0
同時使用合併雙方的索引也沒問題:
In
[19]: left2 = pd.DataFrame([[1,2],[3,4],[5,6]],index=[a,c,e],
...: columns=[Ohio,Nevada])
In
[20]: right2 = pd.DataFrame([[7,8],[9,10],[11,12],[13,14]],index=[b,c,d,e],columns=[Missouri,Alabama])
In
[21]: left2
Out[21]:
Ohio
Nevada
a 1
2
c 3
4
e 5
6
In
[22]: right2
Out[22]:
Missouri
Alabama
b 7
8
c 9
10
d 11
12
e 13
14
In
[23]: pd.merge(left2,right2,left_index=True,right_index=True,how="outer")
Out[23]:
Ohio
Nevada
Missouri
Alabama
a 1.0
2.0
NaN
NaN
b NaN
NaN
7.0
8.0
c 3.0
4.0
9.0
10.0
d NaN
NaN
11.0
12.0
e 5.0
6.0
13.0
14.0
DataFrame還有一個join實例方法,它能更為方便地實現按索引合併。它還可用於合併多個帶有相同或相似索引地DataFrame對象,而不管它們之間地沒有重疊的列。在上面的例子,我們可以編寫:
In
[24]: left2.join(right2,how="outer")
Out[24]:
Ohio
Nevada
Missouri
Alabama
a 1.0
2.0
NaN
NaN
b NaN
NaN
7.0
8.0
c 3.0
4.0
9.0
10.0
d NaN
NaN
11.0
12.0
e 5.0
6.0
13.0
14.0
DataFrame的join方法是在連接鍵上做左連接。它還支持參數DataFrame的索引跟調用者DataFrame的某個列之間的連接:
In
[26]: left1.join(right1,on="key")
Out[26]:
key value group_val
0 a 0
3.5
1 b 1
7.0
2 a 2
3.5
3 a 3
3.5
4 b 4
7.0
5 c 5
NaN
最後,對於簡單的索引合併,你還可以向join傳入一組DataFrame(後面我們會介紹更為通用的concat函數,它也能實現此功能):
In
[27]: another = pd.DataFrame([[7,8],[9,10],[11,12],[16,17]],
...: index=[a,c,e,f],columns=[New York,Oregon])
In
[28]: left2.join([right2,another])
Out[28]:
Ohio
Nevada
Missouri
Alabama
New
York
Oregon
a 1
2
NaN
NaN
7
8
c 3
4
9.0
10.0
9
10
e 5
6
13.0
14.0
11
12
In
[29]: left2.join([right2,another],how=outer)
Out[29]:
Ohio
Nevada
Missouri
Alabama
New
York
Oregon
a 1.0
2.0
NaN
NaN
7.0
8.0
b NaN
NaN
7.0
8.0
NaN
NaN
c 3.0
4.0
9.0
10.0
9.0
10.0
d NaN
NaN
11.0
12.0
NaN
NaN
e 5.0
6.0
13.0
14.0
11.0
12.0
f NaN
NaN
NaN
NaN
16.0
17.0
軸向連接
另一種數據合併運算也被稱作連接(concatenation)、綁定(binding)或堆疊(stacking)。NumPy有一個用於合併原始NumPy數組的concatenation函數:
In
[33]: arr = np.arange(12).reshape(3,4)
In
[34]: arr
Out[34]:
array([[
0,
1,
2,
3],
[
4,
5,
6,
7],
[
8,
9,
10,
11]])
In
[35]: np.concatenate([arr,arr],axis=1)
Out[35]:
array([[
0,
1,
2,
3,
0,
1,
2,
3],
[
4,
5,
6,
7,
4,
5,
6,
7],
[
8,
9,
10,
11,
8,
9,
10,
11]])
對於pandas對象(如Series和DataFrame),帶有標籤的軸使你能夠進一步推廣數組的連接運算。具體點說,你還需要考慮以下這些東西:
- 如果各對象其他軸上的索引不同,那些軸應該是做並集還是交集?
- 結果對象中的分組需要各不相同嗎?
- 用於連接的軸重要嗎?
pandas的concat函數提供了一種能夠解決這些問題的可靠方式。假設有三個沒有重疊索引的Series:
In
[36]: s1 = pd.Series([0,1],index=[a,b])
In
[37]: s2 = pd.Series([2,3,4],index=[c,d,e])
In
[38]: s3 = pd.Series([5,6],index=[f,g])
In
[39]: pd.concat([s1,s2,s3])
Out[39]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
默認情況下,concat是在axis=0上工作的,最終產生一個新的Series。如果傳入axis=1,則結果就會變成DataFrame(axis=1是列):
In
[40]: pd.concat([s1,s2,s3],axis=1)
Out[40]:
0
1
2
a 0.0
NaN
NaN
b 1.0
NaN
NaN
c NaN
2.0
NaN
d NaN
3.0
NaN
e NaN
4.0
NaN
f NaN
NaN
5.0
g NaN
NaN
6.0
這種情況下,另外一條軸上沒有重疊,從索引的有序並集(外連接)上就可以看出來。傳入join=inner即可得到它們的交集:
In
[47]: s4 = pd.concat([s1*5,s3])
In
[48]: pd.concat([s1,s4],axis=1)
Out[48]:
0
1
a 0.0
0
b 1.0
5
f NaN
5
g NaN
6
In
[50]: pd.concat([s1,s4],axis=1,join=inner)
Out[50]:
0
1
a 0
0
b 1
5
可以通過join_axes指定要在其他軸上使用的索引:
In
[51]: pd.concat([s1,s4],axis=1,join_axes=[[a,c,b,e]])
Out[51]:
0
1
a 0.0
0.0
c NaN
NaN
b 1.0
5.0
e NaN
NaN
不過有個問題,參與連接的片段在結果中區分不開。假設你想要在連接軸上創建一個層次化索引。使用keys參數即可達到這個目的:
In
[52]: result = pd.concat([s1,s1,s3],keys=[one,two,three])
In
[53]: result
Out[53]:
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
result.unstack()
Out[54]:
a b f g
one 0.0
1.0
NaN
NaN
two 0.0
1.0
NaN
NaN
three NaN
NaN
5.0
6.0
如果沿著axis=1對Series進行合併,則keys就會成為DataFrame的列頭:
In
[60]: pd.concat([s1,s2,s3],axis=1,keys=[one,two,three])
Out[60]:
one two three
a 0.0
NaN
NaN
b 1.0
NaN
NaN
c NaN
2.0
NaN
d NaN
3.0
NaN
e NaN
4.0
NaN
f NaN
NaN
5.0
g NaN
NaN
6.0
同樣的邏輯對DataFrame對象也是一樣:
In
[61]: df1 = pd.DataFrame(np.arange(6).reshape(3,2),index=[a,b,c],
...: columns=[one,two])
In
[62]: df2 = pd.DataFrame(5+np.arange(4).reshape(2,2),index=[a,c],columns=[three,four])
In
[63]: pd.concat([df1,df2],axis=1,keys=[level1,level2])
Out[63]:
level1 level2
one two three four
a 0
1
5.0
6.0
b 2
3
NaN
NaN
c 4
5
7.0
8.0
最後一個需要考慮的問題是,和當前分析工作無關的DataFrame行索引:
In
[69]: df1 = pd.DataFrame(np.random.randn(3,4),columns=[a,b,c,d])
In
[70]: df2 = pd.DataFrame(np.random.randn(2,3),columns=[b,d,a])
In
[71]: df1
Out[71]:
a b c d
0
2.240595
-0.978214
-2.582510
1.166495
1
-1.069589
1.448100
-0.511939
-0.047077
2
0.744933
1.829291
0.782055
-1.012039
In
[72]: df2
Out[72]:
b d a
0
-1.259407
-1.117981
1.036470
1
2.338679
-0.140818
-0.167361
In
[73]: pd.concat([df1,df2],ignore_index=True)
Out[73]:
a b c d
0
2.240595
-0.978214
-2.582510
1.166495
1
-1.069589
1.448100
-0.511939
-0.047077
2
0.744933
1.829291
0.782055
-1.012039
3
1.036470
-1.259407
NaN
-1.117981
4
-0.167361
2.338679
NaN
-0.140818
In
[74]: pd.concat([df1,df2])
Out[74]:
a b c d
0
2.240595
-0.978214
-2.582510
1.166495
1
-1.069589
1.448100
-0.511939
-0.047077
2
0.744933
1.829291
0.782055
-1.012039
0
1.036470
-1.259407
NaN
-1.117981
1
-0.167361
2.338679
NaN
-0.140818
concat函數的參數
參數說明objs參與連接的pandas對象的列表或字典。唯一必需的參數axis指明連接的軸向,默認為0joininner、outer其中之一,默認為outerjoin_axes指明用於其他n-1條軸的索引,不執行並集/交集運算keys與連接對象有關的值,用於形成連接軸向上的層次化索引。可以是任意值的列表或數組、元組數組、數組列表levels指定用作層次化索引各級別上的索引,如果設置了keys的話names用於創建分層級別的名稱,如果設置了keys和(或)levels的話verify_integrity檢查結果對象新軸上的重複情況,如果發現則引發異常。默認False允許重複ignore_index不保留連接軸上的索引,產生一組新索引range(total_length)
合併重疊數據
還有一種數據組合問題不能用簡單的合併(merge)或連接(concatenation)運算來處理。比如說,你可能有索引全部或部分重疊的兩個數據集。給這個例子增加一點啟發性,我們使用NumPy的where函數,它用於表達一種矢量化的if-else:
In
[75]: a = pd.Series([np.nan,2.5,np.nan,3.5,4.5,np.nan],
...: index=[f,e,d,c,b,a])
In
[76]: b = pd.Series(np.arange(len(a),dtype=np.float64),index=[f,e,d,c,b,a])
In
[77]: b[-1]
= np.nan
In
[78]: a
Out[78]:
f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
In
[79]: b
Out[79]:
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
In
[80]: np.where(pd.isnull(a),b,a)
Out[80]: array([
0.
,
2.5,
2.
,
3.5,
4.5, nan])
Series有一個combine_first方法,實現的也是一樣的功能,而且會進行數據對齊:
In
[83]: b[:-2].combine_first(a[:2])
Out[83]:
c 3.0
d 2.0
e 1.0
f 0.0
dtype: float64
對於DataFrame,combine_first自然也會在列上做同樣的事情,因此可以將其看作:用參數對象中的數據為調用者對象的缺失數據「打補丁」:
In
[86]: df1 = pd.DataFrame({a:[1,np.nan,5,np.nan],b:[np.nan,2,np.nan,6],
...:
c:range(2,18,4)})
In
[87]: df2 = pd.DataFrame({a:[5,4,np.nan,3,7],
...:
b:[np.nan,3,4,6,8]})
In
[88]: df1.combine_first(df2)
Out[88]:
a b c
0
1.0
NaN
2.0
1
4.0
2.0
6.0
2
5.0
4.0
10.0
3
3.0
6.0
14.0
4
7.0
8.0
NaN
重塑和軸向轉換
有許多用於重新排列表格型數據的基礎運算。這些函數也稱作重塑(reshape)或軸向旋轉(pivot)運算。
重塑層次化索引
層次化索引為DataFrame數據的重排任務提供了一種具有良好一致性的方式。主要功能有二:
- stack:將數據的列「旋轉」為行
- unstack:將數據的行「旋轉」為列
將通過一系列的範例來講解這些操作。
In
[92]: data = pd.DataFrame(np.arange(6).reshape(2,3),index=pd.Index([Ohio,Colorado],name=state),columns=pd.Index([one,two,three],name=number))
In
[93]: data
Out[93]:
number one two three
state
Ohio
0
1
2
Colorado
3
4
5
使用該數據的stack方法即可將列轉換為行,得到一個Series:
In
[94]: result = data.stack()
In
[95]: result
Out[95]:
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int32
對於一個層次化索引的Series,你可以用unstack將其重排為一個DataFrame:
In
[96]: result.unstack()
Out[96]:
number one two three
state
Ohio
0
1
2
Colorado
3
4
5
默認情況下,unstack操作的是最內層(stack也是如此)。傳入分層級別的編號或名稱即可對其他級別進行unstack操作:
In
[97]: result.unstack(0)
Out[97]:
state Ohio
Colorado
number
one 0
3
two 1
4
three 2
5
In
[98]: result.unstack(state)
Out[98]:
state Ohio
Colorado
number
one 0
3
two 1
4
three 2
5
如果不是所有的級別值都能在各分組中找到的話,則unstack操作可能會引入缺失數據:
In
[99]: s1 = pd.Series([0,1,2,3],index=[a,b,c,d])
In
[100]: s2 = pd.Series([4,5,6],index=[c,d,e])
In
[101]: data2 = pd.concat([s1,s2],keys=[one,two])
In
[102]: data2
Out[102]:
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
In
[103]: data2.unstack()
Out[103]:
a b c d e
one 0.0
1.0
2.0
3.0
NaN
two NaN
NaN
4.0
5.0
6.0
stack默認會濾除缺失數據,因此該運算是可逆的:
In
[104]: data2.unstack().stack()
Out[104]:
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
In
[105]: data2.unstack().stack(dropna=False)
Out[105]:
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
在對DataFrame進行unstack操作時,作為旋轉軸的級別將會成為結果中的最低級別:
In
[110]: df.unstack(state)
Out[110]:
side left right
state Ohio
Colorado
Ohio
Colorado
number
one 0
3
5
8
two 1
4
6
9
three 2
5
7
10
In
[111]: df.unstack(state).stack(side)
Out[111]:
state Colorado
Ohio
number side
one left 3
0
right 8
5
two left 4
1
right 9
6
three left 5
2
right 10
7
pivot函數是一個快捷方式:用set_index創建層次化索引,再用unstack重塑。
數據轉換
移除重複數據
DataFrame中常常會出現重複行。
In
[122]: data = pd.DataFrame({k1:[one]*3
+
[two]*4,
...:
k2:[1,1,2,3,3,4,4]})
In
[123]: data
Out[123]:
k1 k2
0 one 1
1 one 1
2 one 2
3 two 3
4 two 3
5 two 4
6 two 4
In
[124]: data.duplicated()
Out[124]:
0
False
1
True
2
False
3
False
4
True
5
False
6
True
dtype:
bool
DataFrame的duplicated方法返回一個布爾型Series,表示各行是否是重複行。
drop_duplicates方法可以返回一個移除了重複行的DataFrame:
In
[125]: data.drop_duplicates()
Out[125]:
k1 k2
0 one 1
2 one 2
3 two 3
5 two 4
以上的兩個方法會默認判斷全部列,你也可以指定部分列進行重複項判斷。假設你還有一列,且只希望根據k1過濾重複項:
In
[128]: data[v1]
= range(7)
In
[129]: data
Out[129]:
k1 k2 v1
0 one 1
0
1 one 1
1
2 one 2
2
3 two 3
3
4 two 3
4
5 two 4
5
6 two 4
6
In
[130]: data.drop_duplicates([k1])
Out[130]:
k1 k2 v1
0 one 1
0
3 two 3
3
duplicated和drop_duplicates默認保留第一個出現的值組合。
利用函數或映射進行數據轉換
在對數據集進行轉換時,你可能希望根據數組、Series或DataFrame列中的值來實現該轉換工作。來看一個例子:
In
[133]: data = pd.DataFrame({food:[bacon,pulled pork,bacon,Pastrami,conrned beef,Bacon,pastrami,honey ham,nova lox],
...:
ounces:[4,3,12,6,7.5,8,3,5,6]})
In
[134]: data
Out[134]:
food ounces
0 bacon 4.0
1 pulled pork 3.0
2 bacon 12.0
3
Pastrami
6.0
4 conrned beef 7.5
5
Bacon
8.0
6 pastrami 3.0
7 honey ham 5.0
8 nova lox 6.0
假設你想要添加一列表示該肉類食物來源的動物類型。先編寫一個肉類到動物的映射:
In
[135]: meat_to_animal =
{
...:
bacon:pig,
...:
pulled pork:pig,
...:
pastrami:cow,
...:
corned beef:cow,
...:
honey ham:pig,
...:
nova lox:salmon
...:
}
Series的map方法可以接受一個函數或含有映射關係的字典型對象。
In
[136]: data[animal]
= data[food].map(str.lower).map(meat_to_animal)
In
[137]: data
Out[137]:
food ounces animal
0 bacon 4.0 pig
1 pulled pork 3.0 pig
2 bacon 12.0 pig
3
Pastrami
6.0 cow
4 corned beef 7.5 cow
5
Bacon
8.0 pig
6 pastrami 3.0 cow
7 honey ham 5.0 pig
8 nova lox 6.0 salmon
也可以傳入一個能夠完成全部這些工作的函數:
In
[145]: data[food].map(lambda x:meat_to_animal[x.lower()])
Out[145]:
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype:
object
使用 map
是一種實現元素級轉換以及其他數據清理工作的便捷方式。
替換值
利用fillna方法填充缺失數據可以看作值替換的一種特殊情況。雖然前面提到的map可用於修改對象的數據子集,而replace則提供了一種實現該功能的更簡單、更靈活的方式。
In
[146]: data = pd.Series([1,-999,2,-999,-1000,3])
In
[147]: data
Out[147]:
0
1
1
-999
2
2
3
-999
4
-1000
5
3
dtype: int64
In
[148]: data.replace(-999,np.nan)
Out[148]:
0
1.0
1
NaN
2
2.0
3
NaN
4
-1000.0
5
3.0
dtype: float64
如果你希望一次性替換多個值,可以傳入一個由待替換值組成的列表以及一個替換值:
In
[149]: data.replace([-999,-1000],np.nan)
Out[149]:
0
1.0
1
NaN
2
2.0
3
NaN
4
NaN
5
3.0
dtype: float64
如果你希望對不同的值進行不同的替換,則傳入一個由替換關係組成的列表或字典即可:
In
[150]: data.replace([-999,-1000],[np.nan,0])
Out[150]:
0
1.0
1
NaN
2
2.0
3
NaN
4
0.0
5
3.0
dtype: float64
In
[151]: data.replace({-999:np.nan,-1000:0})
Out[151]:
0
1.0
1
NaN
2
2.0
3
NaN
4
0.0
5
3.0
dtype: float64
重命名軸索引
和Series中的值一樣,軸標籤也可以通過函數或映射進行轉換,從而得到一個新對象。軸還可以被就地修改,而無需新建一個數據結構。
In
[152]: data = pd.DataFrame(np.arange(12).reshape(3,4),index=[Ohio,Colorado,New York],
...: columns=[one,two,three,four])
In
[153]: data
Out[153]:
one two three four
Ohio
0
1
2
3
Colorado
4
5
6
7
New
York
8
9
10
11
In
[154]: data.index.map(str.upper)
Out[154]:
Index([OHIO,
COLORADO,
NEW YORK], dtype=object)
In
[155]: data.index = data.index.map(str.upper)
In
[156]: data
Out[156]:
one two three four
OHIO 0
1
2
3
COLORADO 4
5
6
7
NEW YORK 8
9
10
11
如果想要創建數據集的轉換版(而不是修改原始數據),比較實用的方法是rename:
In
[157]: data.rename(index=str.title,columns=str.upper)
Out[157]:
ONE TWO THREE FOUR
Ohio
0
1
2
3
Colorado
4
5
6
7
New
York
8
9
10
11
rename可以結合字典型對象實現對部分軸標籤的更新:
In
[159]: data.rename(index={OHIO:INDIANA},
...: columns={three:peekaboo})
Out[159]:
one two peekaboo four
INDIANA 0
1
2
3
COLORADO 4
5
6
7
NEW YORK 8
9
10
11
rename幫我們實現了:複製DataFrame並對其索引和列標籤進行賦值。如果希望就地修改某個數據集,傳入inplace=True即可。
離散化和面元劃分
為了便於分析,連續數據常常被離散化或拆分為「面元」(bin)。假設有一組人員數據,而你希望將它們劃分為不同的年齡組:
In
[3]: ages =
[20,
22,
25,
27,
21,
23,
37,
31,
61,
45,
41,
32]
接下來將這些數據劃分為「18到25」、「26到35」、「35到60」以及「60以上」幾個面元。要實現該功能,你需要使用pandas的cut函數:
In
[4]: bins =
[18,
25,
35,
60,
100]
In
[5]: cats = pd.cut(ages,bins)
In
[6]: cats
Out[6]:
[(18,
25],
(18,
25],
(18,
25],
(25,
35],
(18,
25],
...,
(25,
35],
(60,
100],
(35,
60],
(35,
60],
(25,
35]]
Length:
12
Categories
(4, interval[int64]):
[(18,
25]
<
(25,
35]
<
(35,
60]
<
(60,
100]]
pandas返回的是一個特殊的Categorical對象。結果展示了pandas.cut劃分的面元。你可以將其看做一組表示面元名稱的字元串。它的底層含有一個表示不同分類名稱的類型數組,以及一個codes屬性中的年齡數據的標籤:
In
[9]: cats.codes
Out[9]: array([0,
0,
0,
1,
0,
0,
2,
1,
3,
2,
2,
1], dtype=int8)
In
[10]: cats.categories
Out[10]:
IntervalIndex([(18,
25],
(25,
35],
(35,
60],
(60,
100]]
closed=right,
dtype=interval[int64])
In
[11]: pd.value_counts(cats)
Out[11]:
(18,
25]
5
(35,
60]
3
(25,
35]
3
(60,
100]
1
dtype: int64
跟「區間」的數學符號一樣,圓括弧表示開端,而方括弧則表示閉端(包括)。哪邊是閉端可以通過right=False進行修改:
In
[12]: pd.cut(ages,
[18,
26,
36,
61,
100], right=False)
Out[12]:
[[18,
26),
[18,
26),
[18,
26),
[26,
36),
[18,
26),
...,
[26,
36),
[61,
100),
[36,
61),
[36,
61),
[26,
36)]
Length:
12
Categories
(4, interval[int64]):
[[18,
26)
<
[26,
36)
<
[36,
61)
<
[61,
100)]
可以通過傳遞一個列表或數組到labels,設置自己的面元名稱:
In
[13]: group_names =
[Youth,
YoungAdult,
MiddleAged,
Senior]
In
[14]: pd.cut(ages, bins, labels=group_names)
Out[14]:
[Youth,
Youth,
Youth,
YoungAdult,
Youth,
...,
YoungAdult,
Senior,
MiddleAged,
MiddleAged,
YoungAdult]
Length:
12
Categories
(4,
object):
[Youth
<
YoungAdult
<
MiddleAged
<
Senior]
如果向cut傳入的是面元的數量而不是確切的面元邊界,則它會根據數據的最小值和最大值計算等長面元。下面這個例子中,我們將一些均勻分布的數據分成四組:
In
[19]: data = np.random.rand(20)
# precision=2,限定小數只有兩位
In
[20]: pd.cut(data,
4, precision=2)
Out[20]:
[(0.15,
0.36],
(0.15,
0.36],
(0.36,
0.57],
(0.79,
1.0],
(0.36,
0.57],
...,
(0.57,
0.79],
(0.79,
1.0],
(0.79,
1.0],
(0.57,
0.79],
(0.57,
0.79]]
Length:
20
Categories
(4, interval[float64]):
[(0.15,
0.36]
<
(0.36,
0.57]
<
(0.57,
0.79]
<
(0.79,
1.0]]
qcut是一個非常類似於cut的函數,它可以根據樣本分位數對數據進行面元劃分。根據數據的分布情況,cut可能無法使各個面元中含有相同數量的數據點。而qcut由於使用的是樣本分位數,因此可以得到大小基本相等的面元:
In
[21]: data = np.random.randn(1000)
In
[22]: cats = pd.qcut(data,
4)
In
[23]: cats
Out[23]:
[(0.608,
2.892],
(0.608,
2.892],
(-2.985,
-0.695],
(-0.0327,
0.608],
(0.608,
2.892],
...,
(0.608,
2.892],
(-0.0327,
0.608],
(-0.0327,
0.608],
(-0.695,
-0.0327],
(0.608,
2.892]]
Length:
1000
Categories
(4, interval[float64]):
[(-2.985,
-0.695]
<
(-0.695,
-0.0327]
<
(-0.0327,
0.608]
<
(0.608,
2.892]]
In
[24]: pd.value_counts(cats)
Out[24]:
(0.608,
2.892]
250
(-0.0327,
0.608]
250
(-0.695,
-0.0327]
250
(-2.985,
-0.695]
250
dtype: int64
與cut類似,你也可以傳遞自定義的分位數(0到1之間的數值,包含端點):
In
[25]: pd.qcut(data,
[0,
0.1,
0.5,
0.9,
1.])
Out[25]:
[(0.147,
0.288],
(0.288,
0.694],
(0.288,
0.694],
(0.694,
0.98],
(0.288,
0.694],
...,
(0.288,
0.694],
(0.694,
0.98],
(0.98,
0.998],
(0.694,
0.98],
(0.288,
0.694]]
Length:
20
Categories
(4, interval[float64]):
[(0.147,
0.288]
<
(0.288,
0.694]
<
(0.694,
0.98]
<
(0.98,
0.998]]
檢測和過濾異常值
異常值的過濾或變換運算在很大程度上其實就是數組運算。看一個例子:
In
[177]: np.random.seed(12345)
In
[178]: data = pd.DataFrame(np.random.randn(1000,4))
In
[179]: data.describe()
Out[179]:
0
1
2
3
count 1000.000000
1000.000000
1000.000000
1000.000000
mean -0.067684
0.067924
0.025598
-0.002298
std 0.998035
0.992106
1.006835
0.996794
min -3.428254
-3.548824
-3.184377
-3.745356
25%
-0.774890
-0.591841
-0.641675
-0.644144
50%
-0.116401
0.101143
0.002073
-0.013611
75%
0.616366
0.780282
0.680391
0.654328
max 3.366626
2.653656
3.260383
3.927528
假設你想找出某列中絕對值大於3的值:
In
[181]: col[np.abs(col)
>
3]
Out[181]:
97
3.927528
305
-3.399312
400
-3.745356
Name:
3, dtype: float64
要選出全部含有「超過3或-3的值」的行,可以利用布爾型DataFrame以及any方法:
In
[184]: data[(np.abs(data)>3).any(1)]
Out[184]:
0
1
2
3
5
-0.539741
0.476985
3.248944
-1.021228
97
-0.774363
0.552936
0.106061
3.927528
102
-0.655054
-0.565230
3.176873
0.959533
305
-2.315555
0.457246
-0.025907
-3.399312
324
0.050188
1.951312
3.260383
0.963301
400
0.146326
0.508391
-0.196713
-3.745356
499
-0.293333
-0.242459
-3.056990
1.918403
523
-3.428254
-0.296336
-0.439938
-0.867165
586
0.275144
1.179227
-3.184377
1.369891
808
-0.362528
-3.548824
1.553205
-2.186301
900
3.366626
-2.372214
0.851010
1.332846
根據這些條件,可以輕鬆地對值進行設置。下面地代碼可以將值限制在區間-3到3以內:
In
[190]: data[np.abs(data)>3]
= np.sign(data)
*
3
In
[191]: data.describe()
Out[191]:
0
1
2
3
count 1000.000000
1000.000000
1000.000000
1000.000000
mean -0.067623
0.068473
0.025153
-0.002081
std 0.995485
0.990253
1.003977
0.989736
min -3.000000
-3.000000
-3.000000
-3.000000
25%
-0.774890
-0.591841
-0.641675
-0.644144
50%
-0.116401
0.101143
0.002073
-0.013611
75%
0.616366
0.780282
0.680391
0.654328
max 3.000000
2.653656
3.000000
3.000000
排列和隨機採樣
利用numpy.random.permutation函數可以輕鬆實現對Series或DataFrame的列的排列工作。通過需要排列的軸的長度調用permutation,可產生一個表示新順序的整數數組:
In
[196]: df = pd.DataFrame(np.arange(5*4).reshape(5,4))
In
[197]: sampler = np.random.permutation(5)
In
[198]: sampler
Out[198]: array([1,
0,
2,
3,
4])
In
[199]: df
Out[199]:
0
1
2
3
0
0
1
2
3
1
4
5
6
7
2
8
9
10
11
3
12
13
14
15
4
16
17
18
19
In
[200]: df.take(sampler)
Out[200]:
0
1
2
3
1
4
5
6
7
0
0
1
2
3
2
8
9
10
11
3
12
13
14
15
4
16
17
18
19
要通過替換的方式產生樣本,最快的方式是通過np.random.randint得到一組隨機整數:
In
[201]: bag = np.array([5,7,-1,6,4])
In
[202]: sampler = np.random.randint(0,len(bag),size=10)
In
[203]: sampler
Out[203]: array([2,
4,
4,
4,
4,
2,
2,
2,
0,
3])
In
[204]: draws = bag.take(sampler)
In
[205]: draws
Out[205]: array([-1,
4,
4,
4,
4,
-1,
-1,
-1,
5,
6])
計算指標/啞變數
另一種常用於統計建模或機器學習的轉換方式是:將分類變數(categorical variable)轉換為「啞變數」或「指標矩陣」。
如果DataFrame的某一列中含有k個不同的值,則可以派生出一個k列矩陣或DataFrame(其值全為1和0)。pandas有一個get_dummies函數可以實現該功能(其實自己動手做一個也不難)。使用之前的一個DataFrame例子:
In
[27]: df = pd.DataFrame({key:
[b,
b,
a,
c,
a,
b],data1: range(6)})
In
[28]: df
Out[28]:
data1 key
0
0 b
1
1 b
2
2 a
3
3 c
4
4 a
5
5 b
In
[29]: pd.get_dummies(df[key])
Out[29]:
a b c
0
0
1
0
1
0
1
0
2
1
0
0
3
0
0
1
4
1
0
0
5
0
1
0
有時候,你可能想給指標DataFrame的列加上一個前綴,以便能夠跟其他數據進行合併。get_dummies的prefix參數可以實現該功能:
In
[30]: dummies = pd.get_dummies(df[key], prefix=key)
In
[31]: df_with_dummy = df[[data1]].join(dummies)
In
[32]: df_with_dummy
Out[32]:
data1 key_a key_b key_c
0
0
0
1
0
1
1
0
1
0
2
2
1
0
0
3
3
0
0
1
4
4
1
0
0
5
5
0
1
0
一個對統計應用有用的秘訣是:結合get_dummies和諸如cut之類的離散化函數:
In
[33]: np.random.seed(12345)
In
[34]: values = np.random.rand(10)
In
[35]: values
Out[35]:
array([
0.92961609,
0.31637555,
0.18391881,
0.20456028,
0.56772503,
0.5955447
,
0.96451452,
0.6531771
,
0.74890664,
0.65356987])
In
[36]: bins =
[0,
0.2,
0.4,
0.6,
0.8,
1]
In
[37]: pd.get_dummies(pd.cut(values, bins))
Out[37]:
(0.0,
0.2]
(0.2,
0.4]
(0.4,
0.6]
(0.6,
0.8]
(0.8,
1.0]
0
0
0
0
0
1
1
0
1
0
0
0
2
1
0
0
0
0
3
0
1
0
0
0
4
0
0
1
0
0
5
0
0
1
0
0
6
0
0
0
0
1
7
0
0
0
1
0
8
0
0
0
1
0
9
0
0
0
1
0
字元串操作
大部分文本運算都直接做成了字元串對象的內置方法。對於更為複雜的模式匹配和文本操作,則可能需要用到正則表達式。。pandas對此進行了加強,它能使你能夠對整組數據應用字元串表達式和正則表達式,而且能處理煩人的缺失數據。
字元串對象方法
Python內置的字元串方法
方法說明count返回字串在字元串中出現的次數(非重疊)endswith、startswith如果字元串以某個後綴結尾(以某個前綴開頭),則返回Truejoin將字元串用作連接其他字元串序列的分隔符index如果在字元串中找到子串,則返回子串第一個字元所在的位置。如果沒有找到,則引發ValuleErrorfind如果在字元串中找到子串,則返回第一個發現的子串的第一個字元所在的位置。如果沒有找到,則返回-1rfind如果在字元串中找到子串,則返回最後一個發現的子串的第一個字元所在的位置。如果沒有找到,則返回-1replace用另一字元串替換指定子串strip、rstrip、lstrip去除空白符(包括換行符)split通過指定的分隔符將字元串拆分為一組子串lower、upper分別將字母字元轉換為小寫或大寫ljust、rjust用空格(或其他字元)填充字元串的空白側以返回符合最低寬度的字元串
正則表達式
正則表達式方法
方法說明findall、finditer返回字元串中所有的非重疊匹配模式,findall返回的是由所有模式組成的列表,而finditer則返回一個迭代器逐個返回match從字元串起始位置匹配模式,還可以對模式各部分進行分組。如果匹配到模式,則返回一個匹配項對象,否則返回Nonesearch掃描整個字元串以匹配模式。如果找到則返回一個匹配項對象。和match不同,其匹配項可以位於字元串的任意位置,而不僅僅是起始處。split根據找到的模式將字元串拆分為數段sub、subn將字元串中所有的(sub)或前n各(subn)模式替換為指定表達式。
pandas中矢量化的字元串函數
通過map函數,所有字元串和正則表達式方法都能被應用於(傳入lambda表達式或其他函數)各個值,但是如果存在NA就會報錯。為了解決這個問題,Series有一些能夠跳過NA值得字元串操作方法。通過Series的str屬性即可訪問這些方法。例如,我們可以通過str.contains檢查各個電子郵件地址是否含有「gmail」:
In
[160]: data =
{Dava:dave@google.com,Steve:steve@gmail.com,
...:
Rob:rob@gmail.com,Wes:np.nan}
In
[161]: data = pd.Series(data)
In
[162]: data.str.contains(gmail)
Out[162]:
Dava
False
Rob
True
Steve
True
Wes
NaN
dtype:
object
也可以使用正則表達式,還可以加上任意re選項(如IGNORECASE):
In
[163]: pattern =
([A-Z0-9._%+-]+)@([A-Z0-9.-]+).([A-Z]{2,4})
In
[164]:
import re
In
[165]: data.str.findall(pattern,flags=re.IGNORECASE)
Out[165]:
Dava
[(dave, google, com)]
Rob
[(rob, gmail, com)]
Steve
[(steve, gmail, com)]
Wes
NaN
dtype:
object
有兩個辦法可以實現矢量化的元素獲取操作:要麼使用str.get,要麼在str屬性上使用索引。
In
[171]: matches = data.str.match(pattern,flags=re.IGNORECASE)
In
[172]: matches
Out[172]:
Dava
True
Rob
True
Steve
True
Wes
NaN
dtype:
object
In
[173]: matches.str.get(1)
Out[173]:
Dava
NaN
Rob
NaN
Steve
NaN
Wes
NaN
dtype: float64
In
[176]: data.str[:5]
Out[176]:
Dava dave@
Rob rob@g
Steve steve
Wes
NaN
dtype:
object
矢量化字元串方法
推薦閱讀:
※使用 Python 求解拉普拉斯方程
※方勢阱薛定諤方程
※Python有哪些可以做帶約束的二次線性規劃的包?
※Python科學計算與自動控制1-Python入門
※和 C++ 相比,用 Fortran 編程是怎樣的體驗?