1. 程式人生 > >資料基礎---《利用Python進行資料分析·第2版》第8章 資料規整:聚合、合併和重塑

資料基礎---《利用Python進行資料分析·第2版》第8章 資料規整:聚合、合併和重塑

之前自己對於numpy和pandas是要用的時候東學一點西一點,直到看到《利用Python進行資料分析·第2版》,覺得只看這一篇就夠了。非常感謝原博主的翻譯和分享。

在許多應用中,資料可能分散在許多檔案或資料庫中,儲存的形式也不利於分析。本章關注可以聚合、合併、重塑資料的方法。

首先,我會介紹pandas的層次化索引,它廣泛用於以上操作。然後,我深入介紹了一些特殊的資料操作。在第14章,你可以看到這些工具的多種應用。

8.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]])
data
a  1   -0.626605
   2   -0.099608
   3    1.832826
b  1    0.764571
   3    1.241071
c  1   -1.656578
   2   -0.837847
d  2   -1.563933
   3    1.566208
dtype: float64

看到的結果是經過美化的帶有MultiIndex索引的Series的格式。索引之間的“間隔”表示“直接使用上面的標籤”:

data.index
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])

對於一個層次化索引的物件,可以使用所謂的部分索引,使用它選取資料子集的操作更簡單:

data['b']
1    0.764571
3    1.241071
dtype: float64
data['b':'c']
b  1    0.764571
   3    1.241071
c  1   -1.656578
   2   -0.837847
dtype: float64
data.loc[['b','c']]
b  1    0.764571
   3    1.241071
c  1   -1.656578
   2   -0.837847
dtype: float64

有時甚至還可以在“內層”中進行選取:

data.loc[:,2]
a   -0.099608
c   -0.837847
d   -1.563933
dtype: float64

這裡是Series,[:,2]中逗號的兩邊都是表示行索引,前者是外層索引,後者是內層索引

層次化索引在資料重塑和基於分組的操作(如透視表生成)中扮演著重要的角色。例如,可以通過unstack方法將這段資料重新安排到一個DataFrame中:

data.unstack()
1 2 3
a -0.626605 -0.099608 1.832826
b 0.764571 NaN 1.241071
c -1.656578 -0.837847 NaN
d NaN -1.563933 1.566208

unstack的逆運算是stack:

data.unstack().stack()
a  1   -0.626605
   2   -0.099608
   3    1.832826
b  1    0.764571
   3    1.241071
c  1   -1.656578
   2   -0.837847
d  2   -1.563933
   3    1.566208
dtype: float64

stack和unstack將在本章後面詳細講解。

對於一個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']])
frame
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11

各層都可以有名字(可以是字串,也可以是別的Python物件)。如果指定了名稱,它們就會顯示在控制檯輸出中:

frame.index.names=['key1','key2']
frame.columns.names=['state','color']
frame
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11

注意:小心區分索引名state、color與行標籤。

有了部分列索引,因此可以輕鬆選取列分組:

frame['Ohio']
color Green Red
key1 key2
a 1 0 1
2 3 4
b 1 6 7
2 9 10

可以單獨建立MultiIndex然後複用。上面那個DataFrame中的(帶有分級名稱)列可以這樣建立:

from pandas import MultiIndex
MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']],names=['state','color'])
MultiIndex(levels=[['Colorado', 'Ohio'], ['Green', 'Red']],
           labels=[[1, 1, 0], [0, 1, 0]],
           names=['state', 'color'])

重排與分級排序

有時,你需要重新調整某條軸上各級別的順序,或根據指定級別上的值對資料進行排序。swaplevel接受兩個級別編號或名稱,並返回一個互換了級別的新物件(但資料不會發生變化):

frame.swaplevel('key1','key2')
state Ohio Colorado
color 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 Colorado
color 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 Colorado
color Green Red Green
key2 key1
1 a 0 1 2
b 6 7 8
2 a 3 4 5
b 9 10 11

根據級別彙總統計

許多對DataFrame和Series的描述和彙總統計都有一個level選項,它用於指定在某條軸上求和的級別。再以上面那個DataFrame為例,我們可以根據行或列上的級別來進行求和:level指的是保留的索引,按這個索引來分組

frame.sum()#預設是對各列求和
state     color
Ohio      Green    18
          Red      22
Colorado  Green    26
dtype: int64
frame.sum(level='key2')#還是對列求和,只是分了組
state Ohio Colorado
color Green Red Green
key2
1 6 8 10
2 12 14 16
frame.sum(level='color',axis=1)
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10

這其實是利用了pandas的groupby功能,本書稍後將對其進行詳細講解。

使用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]})
frame
a 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 3

DataFrame的set_index函式會將其一個或多個列轉換為行索引,並建立一個新的DataFrame:

frame2=frame.set_index(['c','d'])
frame2
a b
c d
one 0 0 7
1 1 6
2 2 5
two 0 3 4
1 4 3
2 5 2
3 6 1

預設情況下,那些列會從DataFrame中移除,但也可以將其保留下來:

frame.set_index(['c','d'],drop=False)
a b c d
c d
one 0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
two 0 3 4 two 0
1 4 3 two 1
2 5 2 two 2
3 6 1 two 3

reset_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 1

8.2 合併資料集

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)})
df1
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
df2
data2 key
0 0 a
1 1 b
2 2 d

這是一種多對一的合併。df1中的資料有多個被標記為a和b的行,而df2中key列的每個值則僅對應一行。對這些物件呼叫merge即可得到:

pd.merge(df1,df2)
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就會將重疊列的列名當做鍵。不過,最好明確指定一下:

pd.merge(df1,df2,on='key')
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

如果兩個物件的列名不同,也可以分別進行指定:

df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],'data1': range(7)})
df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],'data2': range(3)})
df3
data1 lkey
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
df4
data2 rkey
0 0 a
1 1 b
2 2 d
pd.merge(df3,df4,left_on='lkey',right_on='rkey')
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做的是“內連線”;結果中的鍵是交集。其他方式還有"left"、“right"以及"outer”。外連線求取的是鍵的並集,組合了左連線和右連線的效果:

pd.merge(df1,df2,how='outer')
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

表8-1對這些選項進行了總結。

選項 說明
inner 使用兩個表都有的鍵
left 使用左表中所有的鍵
right 使用右表中所有的鍵
outer 使用兩個表中所有的鍵
表8-1 不同的連線型別

多對多的合併有些不直觀。看下面的例子:

df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],'data1': range(6)})
df2 = pd.DataFrame({'key': ['a', 'b', 'a', 'b', 'd'], 'data2': range(5)})
df1
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 b
df2
data2 key
0 0 a
1 1 b
2 2 a
3 3 b
4 4 d
pd.merge(df1,df2,on='key',how='left')
data1 key data2
0 0 b 1.0
1 0 b 3.0
2 1 b 1.0
3 1 b 3.0
4 2 a 0.0
5 2 a 2.0
6 3 c NaN
7 4 a 0.0
8 4 a 2.0
9 5 b 1.0
10 5 b 3.0

多對多連線產生的是行的笛卡爾積。由於左邊的DataFrame有3個"b"行,右邊的有2個,所以最終結果中就有6個"b"行。連線方式隻影響出現在結果中的不同的鍵的值:

pd.merge(df1,df2,on='key',how='inner')
data1 key data2
0 0 b 1
1 0 b 3
2 1 b 1
3 1 b 3
4 5 b 1
5 5 b 3
6 2 a 0
7 2 a 2
8 4 a 0
9 4 a 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]})
left
key1 key2 lval
0 foo one 1
1 foo two 2
2 bar one 3
right
key1 key2 rval
0 foo one 4
1 foo one 5
2 bar one 6
3 bar two 7
pd.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

結果中會出現哪些鍵組合取決於所選的合併方式,你可以這樣來理解:多個鍵形成一系列元組,並將其當做單個連線鍵(當然,實際上並不是這麼回事)。

注意:在進行列-列連線時,DataFrame物件中的索引會被丟棄。

對於合併運算需要考慮的最後一個問題是對重複列名的處理。雖然你可以手工處理列名重疊的問題(檢視前面介紹的重新命名軸標籤),但merge有一個更實用的suffixes選項,用於指定附加到左右兩個DataFrame物件的重疊列名上的字串:

pd.merge(left,right,on='key1')
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
pd.merge(left,right,on='key1',suffixes=['_left','_right'])
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的引數請參見表8-2。使用DataFrame的行索引合併是下一節的主題。

表8-2 merge函式的引數
引數 說明
left 參與合併的左側 Data Frame
right 參與合併的右側 Data Frame
how "inner","outer"、"left"、 "right"其中之ー。認為inner
on 用於連線的列名。必須存在於左右兩個 Data Frame物件中。如果未指定,且其他連線鍵也未指定,則以left和right列名的交集作為連線鍵
left_on 左側 Data Frame中用作連線健的列
right_on 右側 Dataframe r中用作連線鍵的列
left_index 將左側的行索引用作其連線鍵
right_index 類似於 left_ndex
sort 根據連線健對合並後的資料進行排序,預設為True,有時在處理大資料集時,禁用該選項可獲得更好的效能
suffixes 字串值元組,用於追加到重複列名的末尾,預設為(’_x’,’_y’)。例如,如果左右兩個 Dataframe物件都有"data”,則結果中就會出現’data_x’和’data_y’
copy 設定為 False,可以在某些特殊情況下避免將資料複製到結果資料結構中。預設總是複製
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'])
left1
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5