超好用的 pandas 之 groupby

準(zhǔn)備

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

# 讀入一個(gè)數(shù)據(jù)集, 我使用了美國警方擊斃數(shù)據(jù)集.
%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)
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分組, 它也確實(shí)是用來實(shí)現(xiàn)這樣功能的. 比如, 將一個(gè)數(shù)據(jù)集按A進(jìn)行分組, 效果是這樣

我們嘗試使用groupby來嘗試實(shí)現(xiàn)這樣的功能, 不過我們不用A列, 我們將用我們數(shù)據(jù)集里面的"種族"嘗試分組:

data.groupby('race')

<pandas.core.groupby.DataFrameGroupBy object at 0x104fa2208>

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

這篇稿主要介紹如何鼓搗這個(gè)DataFrameGroupBy, 這個(gè)DataFrameGroupBy主要的功能能是允許你在不額外寫循環(huán)的情況下, 快速對(duì)每一組數(shù)據(jù)進(jìn)行操作


基本操作

最基本的就是組內(nèi)計(jì)數(shù), 求和, 求均值, 求方差, 求blablabla...
比如, 要求被不同種族內(nèi)被擊斃人員年齡的均值:

data.groupby('race')['age'].mean()

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

上面我們求得了各個(gè)種族中被擊斃的人員的平均年齡, 得到的是一個(gè)Series, 每一行對(duì)應(yīng)了每一組的mean, 除此之外你還可以換成std, median, min, max這些基本的統(tǒng)計(jì)數(shù)據(jù)

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

data.groupby('race')['signs_of_mental_illness'].value_counts()

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

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

data.groupby('race')['signs_of_mental_illness'].value_counts().unstack()
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

方法總結(jié)

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

注: 如果不選列, 那么第三步的操作會(huì)遍歷所有列, pandas會(huì)對(duì)能成功操作的列進(jìn)行操作, 最后返回的一個(gè)由操作成功的列組成的DataFrame

更多基本操作

選擇一個(gè)組
不細(xì)講啦, 我自己覺得跟篩選數(shù)據(jù)差不多


可視化

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

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

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

  • 一種傳統(tǒng)做法是:
    1. 遍歷每個(gè)組
    2. 然后篩選不同組的數(shù)據(jù)
    3. 逐個(gè)子集畫條形圖 (或者其他表示)
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)

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

data.groupby('race')['flee'].value_counts().unstack().plot(kind='bar', figsize=(20, 4))

方法總結(jié)

  • 首先, 得到分組操作后的結(jié)果data.groupby('race')['flee'].value_counts()
  • 這里有一個(gè)之前介紹的.unstack操作, 這會(huì)讓你得到一個(gè)DateFrame, 然后調(diào)用條形圖, pandas就會(huì)遍歷每一個(gè)組(unstack后為每一行), 然后作各組的條形圖

場景二: 按不同逃逸類型分組, 組內(nèi)的年齡分布是如何的?

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

data.groupby('flee')['age'].plot(kind='kde', legend=True, figsize=(20, 5))

方法總結(jié)

這里data.groupby('flee')['age']是一個(gè)SeriesGroupby對(duì)象, 顧名思義, 就是每一個(gè)組都有一個(gè)Series. 因?yàn)閯澐至瞬煌右蓊愋偷慕M, 每一組包含了組內(nèi)的年齡數(shù)據(jù), 所以直接plot相當(dāng)于遍歷了每一個(gè)逃逸類型, 然后分別畫分布圖.

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


高級(jí)操作

場景三: 有時(shí)我們需要對(duì)組內(nèi)不同列采取不同的操作

比如說, 我們按flee分組, 但是我們需要對(duì)每一組中的年齡求中位數(shù), 對(duì)是否有精神問題求占比

這時(shí)我們可以這樣做

data.groupby('race').agg({'age': np.median, 'signs_of_mental_illness': np.mean})
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

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

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

然后我們得到了一個(gè)DataFrame, 每行對(duì)應(yīng)一個(gè)組, 沒列對(duì)應(yīng)各組DataFrame的合計(jì)信息, 比如第二行第一列表示, 黑人被擊斃者中, 年齡的中位數(shù)是30, 第二行第二列表示, 黑人被擊斃者中, 有精神疾病表現(xiàn)的占15%

場景四: 我們需要同時(shí)求不同組內(nèi), 年齡的均值, 中位數(shù), 方差

data.groupby('flee')['age'].agg([np.mean, np.median, np.std])
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

方法總結(jié)

現(xiàn)在我們對(duì)一個(gè)SeriesGroupby同時(shí)進(jìn)行了多種操作. 相當(dāng)于同時(shí)得到了這三行的結(jié)果:

data.groupby('flee')['age'].mean()
data.groupby('flee')['age'].median()
data.groupby('flee')['age'].std()

所以這其實(shí)是基本操作部分的進(jìn)階

場景五: 結(jié)合場景三和場景四可以嗎?

答案是肯定的, 請看

data.groupby('flee').agg({'age': [np.median, np.mean], 'signs_of_mental_illness': np.mean})
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

但是這里有一個(gè)問題, 這個(gè)列名分了很多層級(jí), 我們可以進(jìn)行重命名:

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

方法總結(jié)
注意這里agg接受的不一定是np.mean這些函數(shù), 你還可以進(jìn)行自定義函數(shù)哦


總結(jié)

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

  • split : 先將數(shù)據(jù)按一個(gè)屬性分組 (得到 DataFrameGroupby / SeriesGroupby )
  • apply : 對(duì)每一組數(shù)據(jù)進(jìn)行操作 (取平均 取中值 取方差 或 自定義函數(shù))
  • combine: 將操作后的結(jié)果結(jié)合起來 (得到一個(gè)DataFrameSeries 或可視化圖像)

希望看完本文你已經(jīng)對(duì)groupby的使用有清晰的印象, 并充滿信心, 如果你需要更細(xì)致的微操作, 多屬性Groupby等, 可以進(jìn)一步閱讀文檔

??~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容