1.分組運算
所謂的“分組運算”是多個步驟的一個組合,我們可以拆分爲“split-apply-combine”(拆分-應用-合併),我覺得這個詞很好的描述了整個過程。分組運算的第一個階段,pandas對象(無論是Series,DataFrame還是其他的)中的數據會根據你所提供的一個或多個“key”,被拆分(split)爲多個組。拆分操作是在對象的特定軸上執行的,例如,DataFrame可以在其行(axis = 0)或者列(axis = 1)上進行分組,然後,將一個函數應用(apply)到各個分組,併產生一個新值。最後,所有的這些函數的執行結果會被合併(combine)到最終的結果對象中。結果對象的形式一般取決於數據上所執行的操作。
注意:apply函數爲聚合函數,例如,sum、mean、min、max等
下圖展示了分組聚合的過程:
分組的key可以有多種形式,且類型不必相同:
- 1.列表或數組,但是其長度與待分組的軸是一樣的。
- 2.表示DataFrame某個列明的值
- 3.字典或者Series,給出帶分組軸上的值與分組名之間的對應關係。
- 4.函數,用於處理軸索引或者索引中的各個標籤
** 注意:後三種只是快捷方式而已,其最終的目的仍然是產生一組用於拆分對象的值**
2.代碼演示
如果覺得上面的東西看起來很抽象,不用擔心,我將在下面給出大量示例。首先來看一下下面這個非常簡單的表格型數據集(以DataFrame的形式給出)
import pandas as pd
import numpy as np
df = pd.DataFrame({'key1':['a','a','b','b','a'],
'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),
'data2':np.random.randn(5)})
print(df)
運行結果:
key1 key2 data1 data2
0 a one 0.015108 0.304983
1 a two 2.054185 -0.009759
2 b one -1.057348 -1.703048
3 b two -3.696947 -0.788548
4 a one 1.452735 0.388301
- 如果想按照‘key1’進行分組,並計算data1列的平均值。實現該功能的方式很多,而我們這裏要用的是:訪問data1,並根據key1調用groupby:
grouped = df['data1'].groupby(df['key1'])
print(grouped)
運行結果:
<pandas.core.groupby.groupby.SeriesGroupBy object at 0x1a2308ddd8>
- 變量grouped是一個GroupBy對象,它實際上還沒有進行任何計算,只是含有一些有關分組鍵df[‘key1’]的中間數據而已。換句話說,該對象已經有了接下來對各個分組執行運算所需要的一切信息。例如,我們可以調用GroupBy的mean方法來計算分組平均值:
get_mean = grouped.mean()
print(get_mean)
運行結果:
key1
a 1.174009
b -2.377148
Name: data1, dtype: float64
- 稍後會將會詳細講解
.mean()
的調用過程,這裏最重要的是,數據(Series)根據分組鍵進行了聚合,產生了一個新的Series,其索引爲key1列中的唯一值,之所以結果中的索引名稱爲key1,是因爲原始的DataFrame的列df[‘key1’]就叫這個名字。 - 如果我們一次傳入多個數組,就會得到不同的結果:
means = df['data1'].groupby([df['key1'],df['key2']]).mean()
print(means)
運行結果:
key1 key2
a one 0.733921
two 2.054185
b one -1.057348
two -3.696947
Name: data1, dtype: float64
- 這裏,我通過對兩個鍵對數據進行了分組,得到的Series具有一個層次化索引(由唯一的鍵對組成):
print(means.unstack())
運行結果:
key2 one two
key1
a 0.733921 2.054185
b -1.057348 -3.696947
- 在上面這些示例中,分組鍵均爲Series。實際上,分組鍵可以是任何長度適當的數組:
states = np.array(['Ohio','califonia','califonia','Ohio','Ohio'])
years = np.array([2005,2005,2006,2005,2006])
df['data1'].groupby([states,years]).mean()
運行結果:
Ohio 2005 -1.840920
2006 1.452735
califonia 2005 2.054185
2006 -1.057348
Name: data1, dtype: float64
- 此外,你還可以將列名(可以是字符串,數字或者其他Python對象)用作分組鍵:
print(df.groupby(['key1']).mean())
運行結果:
data1 data2
key1
a 1.174009 0.227842
b -2.377148 -1.245798
print(df.groupby(['key1','key2']).mean())
運行結果:
data1 data2
key1 key2
a one 0.733921 0.346642
two 2.054185 -0.009759
b one -1.057348 -1.703048
two -3.696947 -0.788548
- 你可能已經注意到了,在執行df.groupby(‘key’).mean()時,結果中沒有key2列。這是因爲df[‘key2’]不是數值數據(俗稱“麻煩列”),所以被從結果中排除了。默認情況下,所有數值列都會被聚合,雖然有時可能會被過濾爲一個子集(後面會介紹到)
- 無論你準備拿GroupBy做什麼,都有可能會用到GroupBy的size方法,它可以返回一個含有分組大小的Series:
df.groupby(['key1','key2']).size()
運行結果:
key1 key2
a one 2
two 1
b one 1
two 1
dtype: int64
注意:目前爲止,分組鍵中的任何缺失值都會被排除在結果之外,但是後面的版本也許會對缺失值進行相應的處理
3.對分組進行迭代
GroupBy對象支持迭代,可以產生一組二元元祖(由分組和數據塊組成)。看看下面的一個簡單例子:
for name,group in df.groupby('key1'):
print(name)
print(group)
運行結果:
a
key1 key2 data1 data2
0 a one 0.015108 0.304983
1 a two 2.054185 -0.009759
4 a one 1.452735 0.388301
b
key1 key2 data1 data2
2 b one -1.057348 -1.703048
3 b two -3.696947 -0.788548
- 對於多重鍵的情況,元祖的第一個元素將會是有鍵值組成的元祖:
for (k1,k2),group in df.groupby(['key1','key2']):
print(k1,k2)
print(group)
運行結果:
a one
key1 key2 data1 data2
0 a one 0.015108 0.304983
4 a one 1.452735 0.388301
a two
key1 key2 data1 data2
1 a two 2.054185 -0.009759
b one
key1 key2 data1 data2
2 b one -1.057348 -1.703048
b two
key1 key2 data1 data2
3 b two -3.696947 -0.788548
- 當然,你可以對這些數據片段做任何其他的操作。有一個你可能會覺得有用的運算:將這些數據片段做成一個字典。
pieces = dict(list(df.groupby('key1')))
print("pieces:\n",pieces)
print("pieces['b']:\n",pieces['b'])
運行結果:
pieces:
{'a': key1 key2 data1 data2
0 a one 0.015108 0.304983
1 a two 2.054185 -0.009759
4 a one 1.452735 0.388301, 'b': key1 key2 data1 data2
2 b one -1.057348 -1.703048
3 b two -3.696947 -0.788548}
pieces['b']:
key1 key2 data1 data2
2 b one -1.057348 -1.703048
3 b two -3.696947 -0.788548
- groupby默認是在axis = 0 上進行分組的,通過設置也可以在其他任何軸上進行分組。拿上面的例子的df來說,我們可以根據dtype對列進行分組:
grouped = df.groupby(df.dtypes,axis= 1)
dict(list(grouped))
運行結果:
{dtype('float64'): data1 data2
0 0.015108 0.304983
1 2.054185 -0.009759
2 -1.057348 -1.703048
3 -3.696947 -0.788548
4 1.452735 0.388301,
dtype('O'): key1 key2
0 a one
1 a two
2 b one
3 b two
4 a one}
- 可以看到分組後的結果,是按照數據的類型分組的。並且是按照列進行分組的
4.選取一個或一組列
對於由DataFrame產生的GroupBy對象,如果用一個(單個字符串)或一組(字符串數組)列名對其進行索引,就能實現選取部分列進行聚合的目的。也就是說:
df.groupby('key1')['data1']
df.groupby('key1')[['data2']]
是以下代碼的語法糖:
df['data1'].groupby(df['key1'])
df[['data2']].groupby(df['key1'])
- 尤其對於大數據集,很可能只需要對部分列進行聚合。例如,在前面那個數據集中,如果只需要計算data2列的平均值並以DataFrame形式得到結果,我們可以編寫:
s = df.groupby(['key1','key2'])[['data2']].mean()
print(s)
運行結果:
data2
key1 key2
a one 0.346642
two -0.009759
b one -1.703048
two -0.788548
- 這種索引操作所返回的對象是一個已經分組的DataFrame(如果傳入的是列表或者數組)或者已經分組的Series(如果傳入的是標量形式的單個列名):
s_grouped = df.groupby(['key1','key2'])['data2']
print(s_grouped)
print(s_grouped.mean())
運行結果:
<pandas.core.groupby.groupby.SeriesGroupBy object at 0x1a1ff968d0>
key1 key2
a one 0.346642
two -0.009759
b one -1.703048
two -0.788548
Name: data2, dtype: float64
5.通過字典或者Series進行分組
除數組以外,分組信息還可以由其他形式存在。看另一個示例DataFrame:
people = pd.DataFrame(np.random.randn(5,5),
columns=['a','b','c','d','e'],
index=['Joe','Steve','Wes','Jim','Travis'])
people.ix[2:3,['b','c']] = np.nan # 添加幾個Nan值
print(people)
"""
a b c d e
Joe 0.485915 -1.271190 0.090832 -0.237905 -1.414645
Steve 0.659409 -0.406590 -0.985230 0.429787 1.351408
Wes -1.043782 NaN NaN 0.379168 0.095054
Jim 0.059189 -0.966218 -1.253383 1.774299 1.461221
Travis 1.702478 0.331087 0.568426 -0.985880 0.586774
"""
- 假設已知列的分組關係,並希望根據分組計算列的總計:
# 定義一個字典映射
mapping = {'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}
# 根據映射去進行分組
by_colum = people.groupby(mapping,axis=1)
# 分組後求和
print(by_colum.sum())
"""
blue red
Joe -0.147073 -2.199920
Steve -0.555443 1.604226
Wes 0.379168 -0.948727
Jim 0.520916 0.554192
Travis -0.417454 2.620338
"""
- Series也有同樣的功能,它可以被看做一個固定大小的映射,對於上面那個例子,如果用Series作爲分組鍵,則pandas會檢查Series以確保期索引分組軸是對齊的:
map_series = pd.Series(mapping)
by_series = people.groupby(map_series,axis= 1).count()
print(by_series)
"""
blue red
Joe 2 3
Steve 2 3
Wes 1 2
Jim 2 3
Travis 2 3
"""
6.通過函數進行分組
相較於字典或者Series,Python函數在定義分組映射關係時,可以更有創意且更爲抽象。任何被當做分組鍵的函數都會在各個索引值上被調用一次,其返回值就會被用作分組名稱,具體點說,以上一小節的示例DataFrame爲例,其索引值爲人的名字。假設你希望根據人名的長度進行分組,雖然可以求取一個字符串長度數組,但其實僅僅傳入len函數就可以了:
s = people.groupby(len).sum()
print(s)
"""
a b c d e
3 -0.498678 -2.237408 -1.162551 1.915562 0.141630
5 0.659409 -0.406590 -0.985230 0.429787 1.351408
6 1.702478 0.331087 0.568426 -0.985880 0.586774
"""
- 將函數跟數組、列表、字典、Series混合使用也是可以的,因爲任何東西最後都會被轉化爲數組:
key_list = ['one','one','one','two','two']
s = people.groupby([len,key_list]).min()
print(s)
"""
a b c d e
3 one -1.043782 -1.271190 0.090832 -0.237905 -1.414645
two 0.059189 -0.966218 -1.253383 1.774299 1.461221
5 one 0.659409 -0.406590 -0.985230 0.429787 1.351408
6 two 1.702478 0.331087 0.568426 -0.985880 0.586774
"""
- 如果你足夠細心,會發現本來有5行數據,怎麼現在只有4行,沒錯,其中有NAN的一行被幹掉了!
7.根據索引級別分組
層次化索引數據集最方便的地方就在於它能夠根據索引級別進行聚合。要實現該目的,通過level關鍵字傳入級別編號或名稱即可:
columns = pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],
[1,3,5,1,3]],
names = ['city','tenor'])
hier_df = pd.DataFrame(np.random.randn(4,5),columns = columns)
print(hier_df)
"""
city US JP
tenor 1 3 5 1 3
0 0.700936 1.110087 -1.058324 1.316239 -0.940866
1 0.193931 1.845167 0.191146 -1.081856 -1.396286
2 -0.533317 0.021661 1.216783 -0.777967 -1.105844
3 0.759767 -0.599406 -1.145386 -0.675289 0.465727
"""
- 可以看到定義好的帶有層次索引的DataFrame,下面使用層次索引進行分組:
s = hier_df.groupby(level='city',axis=1).count()
print(s)
"""
city JP US
0 2 3
1 2 3
2 2 3
3 2 3
"""
8.總結
這就是這個GroupBy得使用方法,重點在“key”的使用,本篇演示了多種key的定義方式,可以根據不同的業務需求選擇!