7. 数据的规整:分组、聚合、合并、重塑
7. 數據規整:聚合、合并、重塑和分組
文章目錄
- 7. 數據規整:聚合、合并、重塑和分組
- 數據規整:聚合、合并、重塑和分組
- 1. 層次化索引
- 重排與分級排序
- 根據級別匯總統計
- 使用DataFrame的列進行索引
- 2. 合并數據集
- 數據庫風格的DataFrame合并
- 索引上的合并
- 軸向連接
數據規整:聚合、合并、重塑和分組
在許多應用中,數據可能分散在許多文件或數據庫中,存儲的形式也不利于分析。本章關注可以聚合、合并、重塑數據的方法。
1. 層次化索引
層次化索引(hierarchical indexing)是pandas的一項重要功能,它使你能在一個軸上擁有多個(兩個以上)索引級別。抽象點說,它使你能以低維度形式處理高維度數據。我們先來看一個簡單的例子:創建一個Series,并用一個由列表或數組組成的列表作為索引:
import pandas as pd import numpy as np data = pd.Series(np.random.randn(9),index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],[1, 2, 3, 1, 3, 1, 2, 2, 3]]) dataa 1 -0.2908392 2.3041093 1.634541 b 1 0.5801543 0.467801 c 1 1.8399852 -0.609232 d 2 -0.2680563 -0.946874 dtype: float64看到的結果是經過美化的帶有MultiIndex索引的Series的格式。索引之間的“間隔”表示“直接使用上面的標簽”:
data.index ----------------------------------------------------------------------------- MultiIndex([('a', 1),('a', 2),('a', 3),('b', 1),('b', 3),('c', 1),('c', 2),('d', 2),('d', 3)],)對于一個層次化索引的對象,可以使用所謂的部分索引,使用它選取數據子集的操作更簡單:
MultiIndex([('a', 1),('a', 2),('a', 3),('b', 1),('b', 3),('c', 1),('c', 2),('d', 2),('d', 3)],) data['b'] ----------------------------------------------------------------------------- 1 0.580154 3 0.467801 dtype: float64data['b':'c'] ----------------------------------------------------------------------------- b 1 0.5801543 0.467801 c 1 1.8399852 -0.609232 dtype: float64data.loc[['b','d']] ----------------------------------------------------------------------------- b 1 0.5801543 0.467801 d 2 -0.2680563 -0.946874 dtype: float64有時甚至還可以在“內層”中進行選取:
data.loc[:,2]a 2.304109 c -0.609232 d -0.268056 dtype: float64層次化索引在數據重塑和基于分組的操作(如透視表生成)中扮演著重要的角色。例如,可以通過unstack方法將這段數據重新安排到一個DataFrame中:
data.unstack()1 2 3 a -0.290839 2.304109 1.634541 b 0.580154 NaN 0.467801 c 1.839985 -0.609232 NaN d NaN -0.268056 -0.946874unstack的逆運算是stack
data.unstack().stack() a 1 -0.2908392 2.3041093 1.634541 b 1 0.5801543 0.467801 c 1 1.8399852 -0.609232 d 2 -0.2680563 -0.946874 dtype: float64對于一個DataFrame,每條軸都可以有分層索引:
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],columns=[['Ohio', 'Ohio', 'Colorado'],['Green', 'Red', 'Green']]) frameOhio ColoradoGreen Red Green a 1 0 1 22 3 4 5 b 1 6 7 82 9 10 11各層都可以有名字(可以是字符串,也可以是別的Python對象)。如果指定了名稱,它們就會顯示在控制臺輸出中:
frame.index.names = ['key1', 'key2'] frame.columns.names = ['state', 'color'] framestate Ohio Coloradocolor Green Red Green key1 key2 a 1 0 1 22 3 4 5 b 1 6 7 82 9 10 11有了部分列索引,因此可以輕松選取列分組:
frame['Ohio']color Green Red key1 key2 a 1 0 12 3 4 b 1 6 72 9 10可以單獨創建MultiIndex然后復用。上面那個DataFrame中的(帶有分級名稱)列可以這樣創建:
from pandas import MultiIndex MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']],names=['state', 'color'])MultiIndex([( 'Ohio', 'Green'),( 'Ohio', 'Red'),('Colorado', 'Green')],names=['state', 'color'])重排與分級排序
有時,你需要重新調整某條軸上各級別的順序,或根據指定級別上的值對數據進行排序。swaplevel接受兩個級別編號或名稱,并返回一個互換了級別的新對象(但數據不會發生變化):
frame.swaplevel('key1', 'key2')state Ohio Coloradocolor Green Red Green key2 key1 1 a 0 1 2 2 a 3 4 5 1 b 6 7 8 2 b 9 10 11而sort_index則根據單個級別中的值對數據進行排序。交換級別時,常常也會用到sort_index,這樣最終結果就是按照指定順序進行字母排序了:
frame.sort_index(level=1)state Ohio Coloradocolor Green Red Green key1 key2 a 1 0 1 2 b 1 6 7 8 a 2 3 4 5 b 2 9 10 11 frame.swaplevel(0,1).sort_index(level=0)state Ohio Coloradocolor Green Red Green key2 key1 1 a 0 1 2b 6 7 8 2 a 3 4 5b 9 10 11根據級別匯總統計
許多對DataFrame和Series的描述和匯總統計都有一個level選項,它用于指定在某條軸上求和的級別。再以上面那個DataFrame為例,我們可以根據行或列上的級別來進行求和:
frame.sum(level='key2')state Ohio Colorado color Green Red Green key2 1 6 8 10 2 12 14 16 framestate Ohio Coloradocolor Green Red Green key1 key2 a 1 0 1 22 3 4 5 b 1 6 7 82 9 10 11 frame.sum(level='color', axis=1)color Green Red key1 key2 a 1 2 12 8 4 b 1 14 72 20 10使用DataFrame的列進行索引
人們經常想要將DataFrame的一個或多個列當做行索引來用,或者可能希望將行索引變成DataFrame的列。以下面這個DataFrame為例:
frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),'c': ['one', 'one', 'one', 'two', 'two','two', 'two'],'d': [0, 1, 2, 0, 1, 2, 3]}) framea b c d 0 0 7 one 0 1 1 6 one 1 2 2 5 one 2 3 3 4 two 0 4 4 3 two 1 5 5 2 two 2 6 6 1 two 3DataFrame的set_index函數會將其一個或多個列轉換為行索引,并創建一個新的DataFrame:
frame2 = frame.set_index(['c', 'd']) frame2a b c d one 0 0 71 1 62 2 5 two 0 3 41 4 32 5 23 6 1默認情況下,那些列會從DataFrame中移除,但也可以將其保留下來:
frame.set_index(['c','d'], drop=False)a b c d c d one 0 0 7 one 01 1 6 one 12 2 5 one 2 two 0 3 4 two 01 4 3 two 12 5 2 two 23 6 1 two 3reset_index的功能跟set_index剛好相反,層次化索引的級別會被轉移到列里面:
frame2.reset_index()c d a b 0 one 0 0 7 1 one 1 1 6 2 one 2 2 5 3 two 0 3 4 4 two 1 4 3 5 two 2 5 2 6 two 3 6 12. 合并數據集
pandas對象中的數據可以通過一些方式進行合并:
- pandas.merge可根據一個或多個鍵將不同DataFrame中的行連接起來。SQL或其他關系型數據庫的用戶對此應該會比較熟悉,因為它實現的就是數據庫的join操作。
- pandas.concat可以沿著一條軸將多個對象堆疊到一起。
- 實例方法combine_first可以將重復數據拼接在一起,用一個對象中的值填充另一個對象中的缺失值。
本節將分別對它們進行講解,并給出一些例子。
數據庫風格的DataFrame合并
數據集的合并(merge)或連接(join)運算是通過一個或多個鍵將行連接起來的。這些運算是關系型數據庫(基于SQL)的核心。pandas的merge函數是對數據應用這些算法的主要切入點。
以一個簡單的例子開始:
df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],'data1':range(7)}) df2 = pd.DataFrame({'key':['a', 'b', 'd'],'data2': range(3)}) df1key data1 0 b 0 1 b 1 2 a 2 3 c 3 4 a 4 5 a 5 6 b 6df2key data2 0 a 0 1 b 1 2 d 2這是一種多對一的合并。df1中的數據有多個被標記為a和b的行,而df2中key列的每個值則僅對應一行。對這些對象調用merge即可得到:
pd.merge(df1, df2)key data1 data2 0 b 0 1 1 b 1 1 2 b 6 1 3 a 2 0 4 a 4 0 5 a 5 0注意,這里并沒有指明要用哪個列進行連接。如果沒有指定,merge就會將重疊列的列名當做鍵。不過,最好明確指定一下:
pd.merge(df1, df2, on='key')key data1 data2 0 b 0 1 1 b 1 1 2 b 6 1 3 a 2 0 4 a 4 0 5 a 5 0如果兩個對象的列名不同,也可以分別進行指定:
df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],'data1': range(7)}) df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],'data2': range(3)}) df3lkey data1 0 b 0 1 b 1 2 a 2 3 c 3 4 a 4 5 a 5 6 b 6df4rkey data2 0 a 0 1 b 1 2 d 2 pd.merge(df3, df4, left_on='lkey', right_on='rkey')lkey data1 rkey data2 0 b 0 b 1 1 b 1 b 1 2 b 6 b 1 3 a 2 a 0 4 a 4 a 0 5 a 5 a 0可能你已經注意到了,結果里面c和d以及與之相關的數據消失了。默認情況下,merge做的是“內連接”;結果中的鍵是交集。其他方式還有"left"、“right"以及"outer”。外連接求取的是鍵的并集,組合了左連接和右連接的效果:
pd.merge(df1, df2, how='outer')key data1 data2 0 b 0.0 1.0 1 b 1.0 1.0 2 b 6.0 1.0 3 a 2.0 0.0 4 a 4.0 0.0 5 a 5.0 0.0 6 c 3.0 NaN 7 d NaN 2.0下表對這些選項進行了總結:
多對多的合并有些不直觀??聪旅娴睦?#xff1a;
多對多連接產生的是行的笛卡爾積。由于左邊的DataFrame有3個"b"行,右邊的有2個,所以最終結果中就有6個"b"行。連接方式只影響出現在結果中的不同的鍵的值:
pd.merge(df1, df2, how='inner')key data1 data2 0 b 0 1 1 b 0 3 2 b 1 1 3 b 1 3 4 b 5 1 5 b 5 3 6 a 2 0 7 a 2 2 8 a 4 0 9 a 4 2要根據多個鍵進行合并,傳入一個由列名組成的列表即可:
left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],'key2': ['one', 'two', 'one'],'lval': [1, 2, 3]}) right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],'key2': ['one', 'one', 'one', 'two'],'rval': [4, 5, 6, 7]}) leftkey1 key2 lval 0 foo one 1 1 foo two 2 2 bar one 3rightkey1 key2 rval 0 foo one 4 1 foo one 5 2 bar one 6 3 bar two 7pd.merge(left, right, on=['key1', 'key2'], how='outer')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對象的重疊列名上的字符串:
merge的參數請參見下表:
indicator 添加特殊的列merge,它可以指明每個行的來源,它的值有left_only、right_only或both,根據每行的合并數據的來源。
索引上的合并
有時候,DataFrame中的連接鍵位于其索引中。在這種情況下,你可以傳入left_index=True或right_index=True(或兩個都傳)以說明索引應該被用作連接鍵:
left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],'value': range(6)}) right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])left1key value 0 a 0 1 b 1 2 a 2 3 a 3 4 b 4 5 c 5right1group_val a 3.5 b 7.0 pd.merge(left1, right1, left_on='key', right_index=True)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方法是求取連接鍵的交集,因此你可以通過外連接的方式得到它們的并集:
pd.merge(left1, right1, left_on='key', right_index=True, how='outer')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對于層次化索引的數據,事情就有點復雜了,因為索引的合并默認是多鍵合并:
lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio','Nevada', 'Nevada'],'key2': [2000, 2001, 2002, 2001, 2002],'data': np.arange(5.)}) righth = pd.DataFrame(np.arange(12).reshape((6, 2)),index=[['Nevada', 'Nevada', 'Ohio', 'Ohio','Ohio', 'Ohio'],[2001, 2000, 2000, 2000, 2001, 2002]],columns=['event1', 'event2']) lefthdata key1 key2 0 0.0 Ohio 2000 1 1.0 Ohio 2001 2 2.0 Ohio 2002 3 3.0 Nevada 2001 4 4.0 Nevada 2002righthevent1 event2 Nevada 2001 0 12000 2 3 Ohio 2000 4 52000 6 72001 8 92002 10 11這種情況下,你必須以列表的形式指明用作合并鍵的多個列(注意用how='outer'對重復索引值的處理):
pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True)data key1 key2 event1 event2 0 0.0 Ohio 2000 4 5 0 0.0 Ohio 2000 6 7 1 1.0 Ohio 2001 8 9 2 2.0 Ohio 2002 10 11 3 3.0 Nevada 2001 0 1同時使用合并雙方的索引也沒問題:
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],index=['a', 'c', 'e'],columns=['Ohio', 'Nevada']) right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],index=['b', 'c', 'd', 'e'],columns=['Missouri', 'Alabama']) left2Ohio Nevada a 1.0 2.0 c 3.0 4.0 e 5.0 6.0right2Missouri Alabama b 7.0 8.0 c 9.0 10.0 d 11.0 12.0 e 13.0 14.0 pd.merge(left2, right2, how='outer', left_index=True, right_index=True)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.0DataFrame還有一個便捷的join實例方法,它能更為方便地實現按索引合并。它還可用于合并多個帶有相同或相似索引的DataFrame對象,但要求沒有重疊的列。在上面那個例子中,我們可以編寫:
left2.join(right2, how='outer')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索引:
left1key value 0 a 0 1 b 1 2 a 2 3 a 3 4 b 4 5 c 5right1group_val a 3.5 b 7.0 left1.join(right1, on='key')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函數,也能實現此功能:
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],index=['a', 'c', 'e', 'f'],columns=['New York','Oregon']) anotherNew York Oregon a 7.0 8.0 c 9.0 10.0 e 11.0 12.0 f 16.0 17.0 right2Missouri Alabama b 7.0 8.0 c 9.0 10.0 d 11.0 12.0 e 13.0 14.0left2Ohio Nevada a 1.0 2.0 c 3.0 4.0 e 5.0 6.0 left2.join([right2, another])Ohio Nevada Missouri Alabama New York Oregon a 1.0 2.0 NaN NaN 7.0 8.0 c 3.0 4.0 9.0 10.0 9.0 10.0 e 5.0 6.0 13.0 14.0 11.0 12.0 left2.join([right2, another], how='outer')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的concatenation函數可以用NumPy數組來做:
arr = np.arange(12).reshape((3,4)) arrarray([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])np.concatenate([arr, arr], axis=1)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),帶有標簽的軸使你能夠進一步推廣數組的連接運算。具體點說,你還需要考慮以下這些東西:
- 如果對象在其它軸上的索引不同,我們應該合并這些軸的不同元素還是只使用交集?
- 連接的數據集是否需要在結果對象中可識別?
- 連接軸中保存的數據是否需要保留?許多情況下,DataFrame默認的整數標簽最好在連接時刪掉。
pandas的concat函數提供了一種能夠解決這些問題的可靠方式。這里將給出一些例子來講解其使用方式。假設有三個沒有重疊索引的Series:
s1 = pd.Series([0,1], index=['a', 'b']) s2 = pd.Series([2,3,4], index=['c','d','e']) s3 = pd.Series([5,6], index=['f','g'])對這些對象調用concat可以將值和索引粘合在一起:
pd.concat([s1, s2, s3])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是列):
pd.concat([s1,s2,s3], axis=1)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'即可得到它們的交集:
s4 = pd.concat([s1, s3]) s4a 0 b 1 f 5 g 6 dtype: int64pd.concat([s1, s4], axis=1)0 1 a 0.0 0 b 1.0 1 f NaN 5 g NaN 6pd.concat([s1, s4], axis=1,join='inner')0 1 a 0 0 b 1 1在這個例子中,f和g標簽消失了,是因為使用的是join='inner’選項。
你可以通過join_axes指定要在其它軸上使用的索引:
pd.concat([s1, s4], axis=1, join_axes=[['a','c','b', 'e']])0 1 a 0.0 0.0 c NaN NaN b 1.0 1.0 e NaN NaN不過有個問題,參與連接的片段在結果中區分不開。假設你想要在連接軸上創建一個層次化索引。使用keys參數即可達到這個目的:
result = pd.concat([s1, s1, s3], keys=['one','two', 'three']) resultone a 0b 1 two a 0b 1 three f 5g 6 dtype: int64result.unstack()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總結
以上是生活随笔為你收集整理的7. 数据的规整:分组、聚合、合并、重塑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL主从复制原理学习
- 下一篇: 【微信小程序】零基础学 | 小程序语法