pandas 之 groupby

groupby 的 MutilIndex

df.reset_index()

df.index.get_level_values('abc') / df.index.get_level_values(0)


準備

這個博客是用 Jupyter Notebook 寫的, 如果你沒有用過也不影響閱讀哦. 這裡只要電腦裝了python和pandas就好, 我們會先讀入一個數據集.

<code># 讀入一個數據集, 我使用了美國警方擊斃數據集.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
plt.style.use('ggplot')
path = 'https://raw.githubusercontent.com/HoijanLai/dataset/master/PoliceKillingsUS.csv'
data = pd.read_csv(path, encoding ='latin1')
data.sample(3)
/<code>

name date race age signs_of_mental_illness flee 683 Tyrone Holman 09/09/15 B 37.0 True Not fleeing 1941 Michael Alan Altice 25/12/16 W 61.0 True Not fleeing 652 Manuel Soriano 27/08/15 H 29.0 False Not fleeing


什麼是group by

groupby就是按xx分組, 它也確實是用來實現這樣功能的. 比如, 將一個數據集按A進行分組, 效果是這樣

pandas 之 groupby

我們嘗試使用groupby來嘗試實現這樣的功能, 不過我們不用A列, 我們將用我們數據集裡面的"種族"嘗試分組:

<code>data.groupby('race')
/<code>

<pandas.core.groupby.dataframegroupby>

這裡我們得到了一個叫DataFrameGroupBy的東西, 雖然 pandas 不讓我們直接看它長啥樣, 但是你將它想象成上面那幅分組後的圖(我手繪的)是完全沒有問題的.

這篇稿主要介紹如何鼓搗這個DataFrameGroupBy, 這個DataFrameGroupBy主要的功能能是允許你在不額外寫循環的情況下, 快速對每一組數據進行操作


基本操作

最基本的就是組內計數, 求和, 求均值, 求方差, 求blablabla... 比如, 要求被不同種族內被擊斃人員年齡的均值:

<code>data.groupby('race')['age'].mean()
/<code>

race A 36.605263 B 31.635468 H 32.995157 N 30.451613 O 33.071429 W 40.046980 Name: age, dtype: float64

上面我們求得了各個種族中被擊斃的人員的平均年齡, 得到的是一個Series, 每一行對應了每一組的mean, 除此之外你還可以換成std, median, min, max這些基本的統計數據

上面age是連續屬性, 我們還可以操作離散屬性, 比如對不同取值的計數: .value_counts() 以下嘗試求不同種族內, 是否有精神異常跡象的分別有多少人

<code>data.groupby('race')['signs_of_mental_illness'].value_counts()
/<code>

race signs_of_mental_illness A False 29 True 10 B False 523 True 95 H False 338 True 85 N False 23 True 8 O False 21 True 7 W False 819 True 382 Name: signs_of_mental_illness, dtype: int64

注: 這時, 組內操作的結果不是單個值, 是一個序列, 我們可以用.unstack()將它展開

<code>data.groupby('race')['signs_of_mental_illness'].value_counts().unstack()
/<code>

signs_of_mental_illness False True race A 29 10 B 523 95 H 338 85 N 23 8 O 21 7 W 819 382

方法總結

首先通過groupby得到DataFrameGroupBy對象, 比如data.groupby('race') 然後選擇需要研究的列, 比如['age'], 這樣我們就得到了一個SeriesGroupby, 它代表每一個組都有一個Series 對SeriesGroupby進行操作, 比如.mean(), 相當於對每個組的Series求均值

注: 如果不選列, 那麼第三步的操作會遍歷所有列, pandas會對能成功操作的列進行操作, 最後返回的一個由操作成功的列組成的DataFrame

更多基本操作

選擇一個組 不細講啦, 我自己覺得跟篩選數據差不多


可視化

這是我非常喜歡Groupby的一個地方, 它能夠幫你很輕鬆地分組畫圖, 免去手寫每個組的遍歷的煩惱, 還能為你每個組分好顏色.

場景一: 不同種族中, 逃逸方式分別是如何分佈的?

(屬性A的不同分組中, 離散屬性B的情況是怎麼樣的 )

  • 一種傳統做法是: 遍歷每個組 然後篩選不同組的數據 逐個子集畫條形圖 (或者其他表示)
<code>races = np.sort(data['race'].dropna().unique())
fig, axes = plt.subplots(1, len(races), figsize=(24, 4), sharey=True)
for ax, race in zip(axes, races):
data[data['race']==race]['flee'].value_counts().sort_index().plot(kind='bar', ax=ax, title=race)
/<code>
pandas 之 groupby

還不錯, 但是使用Groupby能讓我們直接免去循環, 而且不需要煩人的篩選, 一行就完美搞定

<code>data.groupby('race')['flee'].value_counts().unstack().plot(kind='bar', figsize=(20, 4))
/<code>
pandas 之 groupby

方法總結

首先, 得到分組操作後的結果data.groupby('race')['flee'].value_counts() 這裡有一個之前介紹的.unstack操作, 這會讓你得到一個DateFrame, 然後調用條形圖, pandas就會遍歷每一個組(unstack後為每一行), 然後作各組的條形圖

場景二: 按不同逃逸類型分組, 組內的年齡分佈是如何的?

(屬性A的不同分組中, 連續屬性B的情況是怎麼樣的)

<code>data.groupby('flee')['age'].plot(kind='kde', legend=True, figsize=(20, 5))
/<code>
pandas 之 groupby

方法總結

這裡data.groupby('flee')['age']是一個SeriesGroupby對象, 顧名思義, 就是每一個組都有一個Series. 因為劃分了不同逃逸類型的組, 每一組包含了組內的年齡數據, 所以直接plot相當於遍歷了每一個逃逸類型, 然後分別畫分佈圖.

pandas 會為不同組的作圖分配顏色, 非常方便


高級操作

場景三: 有時我們需要對組內不同列採取不同的操作

比如說, 我們按flee分組, 但是我們需要對每一組中的年齡求中位數, 對是否有精神問題求佔比

這時我們可以這樣做

<code>data.groupby('race').agg({'age': np.median, 'signs_of_mental_illness': np.mean})
/<code>

age signs_of_mental_illness race A 35.0 0.256410 B 30.0 0.153722 H 31.0 0.200946 N 29.0 0.258065 O 29.5 0.250000 W 38.0 0.318068

方法總結 這裡我們操作的data.groupby('race')是一個DataFrameGroupby, 也就是說, 每一組都有一個DataFrame

我們把對這些DataFrame的操作計劃寫成了了一個字典{'age': np.median, 'signs_of_mental_illness': np.mean}, 然後進行agg, (aggragate, 合計)

然後我們得到了一個DataFrame, 每行對應一個組, 沒列對應各組DataFrame的合計信息, 比如第二行第一列表示, 黑人被擊斃者中, 年齡的中位數是30, 第二行第二列表示, 黑人被擊斃者中, 有精神疾病表現的佔15%

場景四: 我們需要同時求不同組內, 年齡的均值, 中位數, 方差

<code>data.groupby('flee')['age'].agg([np.mean, np.median, np.std])
/<code>

mean median std flee Car 33.911765 33.0 11.174234 Foot 30.972222 30.0 10.193900 Not fleeing 38.334753 36.0 13.527702 Other 33.239130 33.0 9.932043

方法總結

現在我們對一個SeriesGroupby同時進行了多種操作. 相當於同時得到了這三行的結果:

<code>data.groupby('flee')['age'].mean()
data.groupby('flee')['age'].median()
data.groupby('flee')['age'].std()
/<code>

所以這其實是基本操作部分的進階

場景五: 結合場景三和場景四可以嗎?

答案是肯定的, 請看

<code>data.groupby('flee').agg({'age': [np.median, np.mean], 'signs_of_mental_illness': np.mean})
/<code>

age signs_of_mental_illness_mean flee median mean mean Car 33.0 33.911765 0.114286 Foot 30.0 30.972222 0.115646 Not fleeing 36.0 38.334753 0.319174 Other 33.0 33.239130 0.072917

但是這裡有一個問題, 這個列名分了很多層級, 我們可以進行重命名:

<code>agg_df = data.groupby('flee').agg({'age': [np.median, np.mean], 'signs_of_mental_illness': np.mean})
agg_df.columns = ['_'.join(col).strip() for col in agg_df.columns.values]
agg_df
/<code>

age_median age_mean signs_of_mental_illness_mean flee Car 33.0 33.911765 0.114286 Foot 30.0 30.972222 0.115646 Not fleeing 36.0 38.334753 0.319174 Other 33.0 33.239130 0.072917

方法總結 注意這裡agg接受的不一定是np.mean這些函數, 你還可以進行自定義函數哦


總結

Groupby 可以簡單總結為 split, apply, combine, 也就是說:

  • split : 先將數據按一個屬性分組 (得到 DataFrameGroupby / SeriesGroupby )
  • apply : 對每一組數據進行操作 (取平均 取中值 取方差 或 自定義函數)
  • combine: 將操作後的結果結合起來 (得到一個DataFrame 或 Series 或可視化圖像)

希望看完本文你已經對groupby的使用有清晰的印象, 並充滿信心, 如果你需要更細緻的微操作, 多屬性Groupby等, 可以進一步閱讀文檔

https://www.jianshu.com/p/42f1d2909bb6

https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html


分享到:


相關文章: