分組運算有時也被稱為 “split-apply-combine” 操作。其中的 “split” 便是借由 obj.groupby()
方法來實現的。
.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False)
方法作用於一條軸向上,並接受一個分組鍵(by)參數來給調用者分組。分組鍵可以是Series 或列表,要求其長度與待分組的軸一致;也可以是映射函數、字典甚至數組的某條列名(字符串),但這些參數類型都只是快捷方式,其最終仍要用於生成一組用於拆分對象的值。
lang:python
>>> df = DataFrame({'key1':['a','a','b','b','a'],
'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),
'data2':np.random.randn(5)})
>>> df
data1 data2 key1 key2
0 0.922269 0.110285 a one
1 -0.181773 1.022435 a two
2 0.635899 0.279316 b one
3 0.527926 0.482807 b two
4 -1.586040 -1.312042 a one
[5 rows x 4 columns]
>>> grouped = df.groupby(df['key1'])
>>> grouped
<pandas.core.groupby.DataFrameGroupBy object at 0x0000000005BC25F8>
這裡使用 df['key1']
做了分組鍵,即按 a 和 b 進行分組。但實際分組鍵並不需要與數組對象之間存在聯系,只要長度相同即可,使用數組的列只是圖方便。上例中如果使用 [1,1,2,2,3]
這樣的列表做分組鍵的話,結果與 df['key1']
是相同的。
groupby 方法返回的 DataFrameGroupBy 對象實際並不包含數據內容,它記錄的是有關分組鍵——df['key1']
的中間數據。當你對分組數據應用函數或其他聚合運算時,pandas 再依據 groupby 對象內記錄的信息對 df 進行快速分塊運算,並返回結果。
上面這段話其實想說是: groupby 方法的調用本身並不涉及運算,因此速度很快。而在操作這個 grouped 對象的時候,還是將其看成一個保存了實際數據的對象比較方便。比如我們可以直接對其應用很多方法,或索引切片:
lang:python
>>> grouped.mean()
data1 data2
key1
a -0.281848 -0.059774
b 0.581912 0.381061
[2 rows x 2 columns]
上例中沒有顯示 key2
列,是因為其值不是數字類型,被 mean() 方法自動忽視了。當想要只看某一(些)列的時候,可以通過索引來實現,在 groupby 方法調用前後均可(這是一種語法糖):
lang:python
>>> df['data1'].groupby(df['key1']).mean()
key1
a -0.281848
b 0.581912
dtype: float64
>>> df.groupby(df['key2'])['data2'].mean()
key2
one -0.307481
two 0.752621
Name: data2, dtype: float64
如果分組鍵使用的是多個數組,就會得到一個層次化索引的結果:
lang:python
>>> df.groupby([df['key1'],df['key2']]).mean()
data1 data2
key1 key2
a one -0.331885 -0.600879
two -0.181773 1.022435
b one 0.635899 0.279316
two 0.527926 0.482807
[4 rows x 2 columns]
最後,可以使用 GroupBy 對象(不論是 DataFrameGroupBy 還是 SeriesGroupBy)的 .size()
方法查看分組大小:
lang:python
>>> grouped.size()
key1
a 3
b 2
dtype: int64
<br /> ###對分組進行迭代 GroupBy 對象是可以通過 for 循環迭代的,可以產生一組二元組,分別為分組名和組內數據。下面是一個多重分組鍵的情況:
lang:python
>>> for i,j in df.groupby([df['key1'],df['key2']]):
print(i)
print('-----------')
print(j)
('a', 'one')
-----------
data1 data2 key1 key2
0 0.922269 0.110285 a one
4 -1.586040 -1.312042 a one
[2 rows x 4 columns]
('a', 'two')
-----------
data1 data2 key1 key2
1 -0.181773 1.022435 a two
[1 rows x 4 columns]
('b', 'one')
-----------
data1 data2 key1 key2
2 0.635899 0.279316 b one
[1 rows x 4 columns]
('b', 'two')
-----------
data1 data2 key1 key2
3 0.527926 0.482807 b two
[1 rows x 4 columns]
<br /> ###使用字符串列名作分組鍵 前面曾提到過可以使用**字符串形式的列名**作為分組鍵,但上面例子中都沒有用。是因為這種方法雖然方便,卻存在隱患——使用這種方法時,調用者必須是 DataFrame 對象自身而不可以是 DataFrame 的索引形式。即 `df.groupby('key1')['data1']` 是 ok 的,但 `df['data1'].groupby('key1')` 會報錯。使用時當注意區分。 <br /> ###使用 字典或Series作分組鍵 這兩種參數需要提供一種從行(列)名到組名的映射關系。(還記得 Series 就是一種定長有序字典 這種說法嘛)
lang:python
>>> df.groupby({0:'a',1:'a',2:'b',3:'b',4:'a'}).mean()
data1 data2
a -0.281848 -0.059774
b 0.581912 0.381061
[2 rows x 2 columns]
<br /> ###通過函數進行分組 函數的作用有些類似於字典,或者說這些奇怪的分組鍵都類似於字典——利用某種映射關系將待分組的軸轉化為一個等長的由分組名組成的序列。
如果說行列名是作為索引傳遞給字典以獲取組名的話,那麼在函數分組鍵中,行列名就會作為參數傳遞給函數。這便是你需要提供的函數類型:
lang:python
>>> df.groupby(lambda x:'even' if x%2==0 else 'odd').mean()
data1 data2
even -0.009290 -0.307481
odd 0.173076 0.752621
[2 rows x 2 columns]
<br /> ###根據索引級別分組 當根據高級別索引來分組的時候,參數就不再是 `by=None` 了,而要換成 `level=None`,值可以是索引級別的編號或名稱:
lang:python
>>> index = pd.MultiIndex.from_arrays([['even','odd','even','odd','even'],
[0,1,2,3,4]],names=['a','b'])
>>> df.index = index
>>> df.groupby(level='a').mean()
data1 data2
a
even -0.009290 -0.307481
odd 0.173076 0.752621
[2 rows x 2 columns]
>>> df.groupby(level=0).mean()
data1 data2
a
even -0.009290 -0.307481
odd 0.173076 0.752621
[2 rows x 2 columns]
<br /> #數據聚合(Aggregation) --- 數據聚合,指的是任何能夠從數組產生標量值的數據轉換過程。你也可以簡單地將其理解為統計計算,如 mean(), sum(), max() 等。
數據聚合本身與分組並沒有直接關系,在任何一列(行)或全部列(行)上都可以進行。不過當這種運算被應用在分組數據上的時候,結果可能會變得更有意義。
對於 GroupBy 對象可以應用的聚合運算包括:
GroupBy.aggregate()
或 GroupBy.agg()
來實現其中自定義函數的參數應當為一個數組類型,即 GroupBy 對象迭代出的元組的第二個元素。如
lang:python
>>> df.groupby('key1')['data1','data2'].agg(lambda arr:arr.max()-arr.min())
data1 data2
key1
a 2.508309 2.334477
b 0.107973 0.203492
[2 rows x 2 columns]
但其實自定義函數的效率很慢,遠不如 GroupBy 對象已經優化過的內建方法,這些方法包括: <br />
<table > <tr> <td>############</td> <td>****************************************************</td> </tr> <tr> <td>count</td> <td>分組中非 NA 值得數量</td> </tr> <tr> <td>sum</td> <td>非 NA 值的和</td> </tr> <tr> <td>mean</td> <td>非 NA 值的平均值</td> </tr> <tr> <td>median</td> <td>非 NA 值的算數中位數</td> </tr> <tr> <td>std, var</td> <td>無偏(分母為 n-1)標准差和方差</td> </tr> <tr> <td>min, max</td> <td>非 NA 值的最小值和最大值</td> </tr> <tr> <td>prod</td> <td>非 NA 值的積</td> </tr> <tr> <td>first, last</td> <td>第一個和最後一個非 NA 值</td> </tr> </table> <br /> ###面向列的多函數應用 前面的例子中,我們每次都只調用一個聚合方法。對於多函數應用,我們可以分兩種情況討論:
第一種是相同列應用多個函數從而得到多個結果的情況,這時只需給 agg() 傳入一個函數列表即可:
lang:python
>>> df.groupby('key1')['data1','data2'].agg(['min','max'])
data1 data2
min max min max
key1
a -1.586040 0.922269 -1.312042 1.022435
b 0.527926 0.635899 0.279316 0.482807
[2 rows x 4 columns]
這裡一個技巧是,對上節中那些統計方法,可以將方法名以字符串的形式傳入 agg()。另外,如果你不喜歡列的命名方式,或你使用的干脆是 lambda 匿名函數,你可以把函數參數替換成(name,function)的元組格式,這樣結果集中的列就不再以函數名命名而是以你給出的 name 為准。
第二種是對不同列應用不同函數的情況,這時需要傳給 agg() 一個從列名映射到函數名的字典:
lang:python
>>> df.groupby('key1').agg({'data1':'min','data2':'max'})
data1 data2
key1
a -1.586040 1.022435
b 0.527926 0.482807
[2 rows x 2 columns]
聚合只是分組運算的一種,更多種類的分組運算可以通過 .transform()
和 apply()
方法實現。 <br /> ###transform 前面進行聚合運算的時候,得到的結果是一個以分組名為 index 的結果對象。如果我們想使用原數組的 index 的話,就需要進行 merge 轉換。transform(func, *args, **kwargs)
方法簡化了這個過程,它會把 func 參數應用到所有分組,然後把結果放置到原數組的 index 上(如果結果是一個標量,就進行廣播):
lang:python
>>> df
data1 data2 key1 key2
a b
even 0 0.922269 0.110285 a one
odd 1 -0.181773 1.022435 a two
even 2 0.635899 0.279316 b one
odd 3 0.527926 0.482807 b two
even 4 -1.586040 -1.312042 a one
[5 rows x 4 columns]
>>> df.groupby('key1').transform('mean')
data1 data2
a b
even 0 -0.281848 -0.059774
odd 1 -0.281848 -0.059774
even 2 0.581912 0.381061
odd 3 0.581912 0.381061
even 4 -0.281848 -0.059774
[5 rows x 2 columns]
<br /> ###apply `apply(func, *args, **kwargs)` 會將待處理的對象拆分成多個片段,然後對各片段調用傳入的函數,最後嘗試用 `pd.concat()` 把結果組合起來。func 的返回值可以是 pandas 對象或標量,並且數組對象的大小不限。
lang:python
>>> df
data1 data2 key1 key2
0 0.721150 -0.359337 a one
1 -1.727197 1.539508 a two
2 -0.339751 0.171379 b one
3 -0.291888 -1.000769 b two
4 -0.127029 0.506162 a one
[5 rows x 4 columns]
>>> def foo(df,n=12):
return pd.DataFrame(np.arange(n).reshape(3,4))
>>> df.groupby('key1').apply(foo)
0 1 2 3
key1
a 0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
b 0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
[6 rows x 4 columns]
這是一個毫無意義的例子,因為傳給 apply 的 func 參數沒有對 df 做任何處理,直接返回了一個(3,4)的數組。而實際上,這樣一個毫無意義的例子恰好說明了 apply 方法的通用性——你可以返回任意的結果。多數時候,限制 apply 發揮的其實是用戶的腦洞。 <br /> ###透視表和交叉表 DataFrame 對象有一個 .pivot_table(data, values=None, rows=None, cols=None, aggfunc='mean', fill_value=None, margins=False, dropna=True)
方法可以用來制作透視表,同時 pd.pivot_table()
也是一個頂層函數。
例:
lang:python
>>> df
A B C D
0 foo one small 1
1 foo one large 2
2 foo one large 2
3 foo two small 3
4 foo two small 3
5 bar one large 4
6 bar one small 5
7 bar two small 6
8 bar two large 7
>>> table = pivot_table(df, values='D', rows=['A', 'B'],
... cols=['C'], aggfunc=np.sum)
>>> table
small large
foo one 1 4
two 6 NaN
bar one 5 4
two 6 7
<br /> 交叉表(cross-tabulation,crosstab)是一種用於計算分組頻數的特殊透視表。
crosstab(rows, cols, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, dropna=True)
lang:python
>>> a
array([foo, foo, foo, foo, bar, bar,
bar, bar, foo, foo, foo], dtype=object)
>>> b
array([one, one, one, two, one, one,
one, two, two, two, one], dtype=object)
>>> c
array([dull, dull, shiny, dull, dull, shiny,
shiny, dull, shiny, shiny, shiny], dtype=object)
>>> crosstab(a, [b, c], rownames=['a'], colnames=['b', 'c'])
b one two
c dull shiny dull shiny
a
bar 1 2 1 0
foo 2 2 1 2
Python Make student management
This blog is used to put Mac O