Python 分組處理

在日常數(shù)據(jù)分析時(shí),經(jīng)常會(huì)遇到需要按列分組 (groupby) 的任務(wù),如計(jì)算某公司各部門的人數(shù),計(jì)算各部門男女平均工資悦荒,計(jì)算不同年代的員工的平均工資等等春贸。在進(jìn)行這類運(yùn)算時(shí)混萝,Pandas 提供了 groupby 函數(shù),大多數(shù)問題它都可以解決萍恕,但有一些問題使用 groupby 函數(shù)會(huì)略顯麻煩逸嘀,下面我們就這些問題展開細(xì)致的討論。

groupby 是 pandas 中非常重要的一個(gè)函數(shù), 主要用于數(shù)據(jù)分類和聚合計(jì)算. 其思想是“split-apply-combine”(拆分 - 應(yīng)用 - 合并)允粤,如下圖:

分組原理圖

一崭倘、單列分組聚合

單列分組聚合是指把某一列作為鍵進(jìn)行分組,然后對(duì)各組進(jìn)行聚合運(yùn)算类垫。

它是上述分組原理的最簡(jiǎn)單應(yīng)用司光,比如根據(jù)員工信息數(shù)據(jù),計(jì)算各部門員工數(shù)悉患。

問題分析:要計(jì)算各部門員工數(shù)残家,首先把部門作為鍵進(jìn)行分組,然后對(duì)各組成員進(jìn)行計(jì)數(shù)售躁。

部分員工信息數(shù)據(jù)如下:

EIDNAMESURNAMEGENDERSTATEBIRTHDAYHIREDATEDEPTSALARY

1RebeccaMooreFCalifornia1974/11/202005/3/11R&D7000

2AshleyWilsonFNew York1980/7/192008/3/16Finance11000

3RachelJohnsonFNew ? Mexico1970/12/172010/12/1Sales9000

4EmilySmithFTexas1985/3/72006/8/15HR7000

5AshleySmithFTexas1975/5/132004/7/30R&D16000

………………………

Python代碼

import pandas as ? pd

employee = ? pd.read_csv("Employees.csv")

dept_emp_num = ? employee.groupby('DEPT')['DEPT'].count()

print(dept_emp_num)


讀取數(shù)據(jù)

分組計(jì)數(shù)

討論:groupby(‘DEPT’) 將數(shù)據(jù)按照部門分組坞淮, count() 函數(shù)進(jìn)行計(jì)數(shù)。

二陪捷、多列分組聚合

多列分組聚合是指把多列的值同時(shí)作為鍵進(jìn)行分組回窘,然后對(duì)各組進(jìn)行聚合運(yùn)算。

它和單列分組聚合類似市袖,只是分組的鍵是多列組合而已啡直。如根據(jù)員工信息數(shù)據(jù),計(jì)算各部門男女員工的平均工資苍碟。

繼續(xù)使用上例中的員工信息數(shù)據(jù)

問題分析:需要分組的鍵有兩個(gè)酒觅,分別是部門和性別,只要把他們組合起來看作是一個(gè)鍵微峰,然后當(dāng)做單列分組聚合即可阐滩。

Python 代碼

import pandas as pd

employee = pd.read_csv("Employees.csv")

dept_gender_salary = ? employee.groupby(['DEPT','GENDER'],as_index=False).SALARY.mean()

print(dept_gender_salary)



多列分組再聚合

討論:groupby(['DEPT','GENDER']),分組的兩列以列表的形式作為參數(shù)县忌,as_index 表示是否把分組列作為索引掂榔,True 表示作為索引继效,這里使用 False 表示不作為索引。使用 mean() 函數(shù)計(jì)算工資的平均值装获。

三瑞信、根據(jù)衍生列分組聚合

根據(jù)衍生列分組聚合,是指需要分組的鍵并不直接在數(shù)據(jù)中穴豫,需要根據(jù)數(shù)據(jù)計(jì)算出一列新數(shù)據(jù)凡简,把它作為鍵對(duì)數(shù)據(jù)進(jìn)行分組。如計(jì)算不同年代的員工的平均工資精肃。

問題分析:?jiǎn)T工信息數(shù)據(jù)中并沒有年代這一列秤涩,因此需要根據(jù)員工的生日列計(jì)算出來,把它作為鍵對(duì)員工數(shù)據(jù)進(jìn)行分組司抱,然后再求工資均值筐眷。

Python 代碼

import pandas as pd

import numpy as np

employee = pd.read_csv("Employees.csv")

employee['BIRTHDAY']=pd.to_datetime(employee['BIRTHDAY'])

years_salary = ? employee.groupby(np.floor((employee['BIRTHDAY'].dt.year-1900)/10)).SALARY.mean()

print(years_salary)




生日列轉(zhuǎn)換成日期格式


計(jì)算衍生數(shù)組并按此數(shù)組分組,再計(jì)算平均工資

討論:年代數(shù)據(jù)在原數(shù)據(jù)中并不存在习柠,使用 np.floor((employee['BIRTHDAY'].dt.year-1900)/10) 計(jì)算出衍生列表示年代匀谣,然后根據(jù)他分組并計(jì)算平均工資。

四资溃、多個(gè)聚合

多個(gè)聚合武翎,是指分組后對(duì)單列或者多列進(jìn)行多種聚合。

(一)?? 多列單聚合

多列單聚合溶锭,指同時(shí)對(duì)多列聚合宝恶,但每列使用一種聚合方式。如:同時(shí)計(jì)算各部門員工的人數(shù)趴捅,平均工資垫毙。

問題分析:求員工人數(shù)可以對(duì) EID 計(jì)數(shù),求平均工資需要對(duì)工資列求均值驻售,兩列聚合但每列只用一種聚合方式露久。

Python 代碼

import pandas as pd

employee = pd.read_csv("Employees.csv")

dept_agg = ? employee.groupby('DEPT',as_index=False).agg({'EID':'count','SALARY':'mean'})

print(dept_agg.rename(columns={'EID':'NUM','SALARY':'AVG_SALARY'}))



分組并對(duì) EID 計(jì)數(shù)更米,對(duì) SALARY 求平均

重命名列名

討論:Pandas 的 agg()函數(shù)可以完成這類任務(wù)欺栗,各列以及各列的聚合方式以字典的形式作為參數(shù)傳入 agg(),聚合的列作為字典的鍵征峦,聚合方式作為字典的值迟几,從而完成聚合運(yùn)算。

(二)?? 單列多聚合

單列多聚合栏笆,指只對(duì)一列聚合类腮,但聚合的方式有多種。如上述問題也可以直接對(duì)工資計(jì)數(shù)并求平均蛉加,此時(shí)是對(duì)工資進(jìn)行了兩種聚合——計(jì)數(shù)和平均蚜枢。

Python 代碼

import pandas as ? pd

employee = ? pd.read_csv("Employees.csv")

dept_agg = employee.groupby('DEPT').SALARY.agg(['count','mean']).reset_index()

print(dept_agg.rename(columns={'count':'NUM','mean':'AVG_SALARY'}))



對(duì) SALARY 計(jì)數(shù)并求平均

重命名列名

討論:如果是單列的不同聚合方式缸逃,則可以把聚合方式進(jìn)行組合以列表的形式作為參數(shù)傳入 agg()。

(三)?? 多列多聚合

多列多聚合厂抽,指對(duì)多列聚合同時(shí)也包含單列多聚合的組合聚合方式需频。聚合方式還可以是自己定義的函數(shù),

如:計(jì)算各部門員工人數(shù)筷凤,平均工資和最大年齡昭殉。

問題分析:計(jì)算員工人數(shù)和平均工資,是對(duì)工資列計(jì)數(shù)并求平均(單列多聚合)藐守,求最大年齡挪丢,需對(duì)生日列使用自定義的函數(shù)計(jì)算出最大年齡。

Python 代碼

import pandas as ? pd

import datetime

def max_age(s):

??? today = datetime. datetime.today().year

??? age = today-s.dt.year

??? return age.max()

employee = pd.read_csv("Employees.csv")

employee['BIRTHDAY']=pd.to_datetime(employee['BIRTHDAY'])

dept_agg = ? employee.groupby('DEPT').agg({'SALARY':['count','mean'],'BIRTHDAY':max_age})




dept_agg.columns ? = ['NUM','AVG_SALARY','MAX_AGE']

print(dept_agg.reset_index())



函數(shù):求最大年齡

年份

求年齡




按 DEPT 分組卢厂,根據(jù) SALARY 計(jì)數(shù)和求均值乾蓬,BIRTHDAY 使用 max_age 計(jì)算最大年齡

修改列名


討論:這種情況,聚合列和聚合方式還是按照字典的方式傳入足淆,但當(dāng)某一列需要多種聚合方式時(shí)巢块,則需要將其組合,以列表的形式作為字典的值巧号。

五族奢、分組聚合值復(fù)制

分組聚合值復(fù)制,指把分組聚合的結(jié)果轉(zhuǎn)換成與該組等長的列丹鸿,相當(dāng)于把聚合的結(jié)果復(fù)制到該組的所有行越走。如:為員工信息數(shù)據(jù)新增一列各部門的平均工資。

問題分析:各部門的平均工資需要按照部門分組再對(duì)工資求平均靠欢,把平均工資的值添加到對(duì)應(yīng)的組廊敌,并保持?jǐn)?shù)據(jù)原序。

Python 代碼

import pandas as pd

employee = pd.read_csv("Employees.csv")

employee['AVG_SALARY'] = ? employee.groupby('DEPT').SALARY.transform('mean')

print(employee)



按照 DEPT 分組并對(duì) SALARY 求平均

討論:按照部門分組后门怪,對(duì)工資求均值骡澈。transform() 函數(shù)在組內(nèi)求聚合值后會(huì)按照原索引的順序返回結(jié)果,可以自動(dòng)按照索引添加結(jié)果掷空,從而保證原數(shù)據(jù)順序不變肋殴。

六、分組子集處理

分組應(yīng)用:指分組后對(duì)各組進(jìn)行一些非聚合運(yùn)算坦弟。比如分組排序护锤,分組后不再關(guān)心聚合的結(jié)果,而是關(guān)心組內(nèi)記錄的順序酿傍。如:將各部門按照入職時(shí)間從早到晚進(jìn)行排序 烙懦。

問題分析:按照部門分組后,不再關(guān)心分組后的聚合結(jié)果赤炒,而是關(guān)心員工的入職時(shí)間順序氯析。分組后亏较,對(duì)各組進(jìn)行循環(huán)同時(shí)對(duì)組內(nèi)成員按照入職時(shí)間排序就可以了。

Python 代碼

import pandas as pd

employee = pd.read_csv("Employees.csv")

employee['HIREDATE']=pd.to_datetime(employee['HIREDATE'])

employee_new = ? employee.groupby('DEPT',as_index=False).apply(lambda ? x:x.sort_values('HIREDATE')).reset_index(drop=True)

print(employee_new)



修改入職時(shí)間格式


按 DEPT 分組掩缓,并對(duì)各組按照 HIREDATE 排序宴杀,最后重置索引


討論:分組后需要對(duì)組內(nèi)成員排序,可以使用 apply()函數(shù)結(jié)合 lambda 的方式拾因,其中 lambda 表達(dá)式是對(duì)各組循環(huán)旺罢,使用 sort_values() 函數(shù)在組內(nèi)部再排序,返回組內(nèi)排序的結(jié)果绢记。

簡(jiǎn)單的運(yùn)算使用 lambda 函數(shù)計(jì)算扁达,但有時(shí)會(huì)遇到比較復(fù)雜的計(jì)算,如:計(jì)算各部門年齡最大的員工和年齡最小的員工的工資差蠢熄。

問題分析:首先需按照部門分組跪解,分組后還需要找到年齡最大的員工和年齡最小的員工的記錄,然后才能計(jì)算工資差签孔。

Python 代碼

import pandas as pd

def salary_diff(g):

??? max_age = ? g['BIRTHDAY'].idxmin()

??? min_age = ? g['BIRTHDAY'].idxmax()

??? diff = ? g.loc[max_age]['SALARY']-g.loc[min_age]['SALARY']

??? return diff

employee = pd.read_csv("Employees.csv")

employee['BIRTHDAY']=pd.to_datetime(employee['BIRTHDAY'])

salary_diff = employee.groupby('DEPT').apply(salary_diff)

print(salary_diff)


函數(shù):計(jì)算各組工資差

年齡最大的索引

年齡最小的索引

計(jì)算工資差




按 DEPT 分組并使用自定義函數(shù)計(jì)算


討論:使用 apply()結(jié)合自定義函數(shù)的方式叉讥。其中 apply() 會(huì)把分組的結(jié)果作為參數(shù)傳入自定義函數(shù)。salary_diff() 函數(shù)是自定義函數(shù)饥追,g 實(shí)質(zhì)上就是 pandas 的 DataFrame 格式的數(shù)據(jù)框图仓,這里是分組的結(jié)果。對(duì)它計(jì)算最大年齡和最小年齡的索引后但绕,找到工資字段計(jì)算差即得到結(jié)果救崔。

思考:

由上述討論可見,熟練掌握 Pandas 的這些 groupby 方法對(duì)我們進(jìn)行數(shù)據(jù)分析是特別有幫助的捏顺。


下面我們以 stack overflow 網(wǎng)站上的一些實(shí)際問題來進(jìn)一步了解 groupby六孵。

七、按位置分組

按位置分組幅骄,指不以某列作為鍵分組劫窒,而是以記錄的位置作為鍵來分組。比如將數(shù)據(jù)每三行分到相同組或者按照位置分成奇數(shù)位置一組拆座,偶數(shù)位置一組等主巍。舉例如下:

source:https://stackoverflow.com/questions/59110612/pandas-groupby-mode-every-n-rows

數(shù)據(jù)片段如下:

time?????????????????????? a?????????????????????? b

0????????????????????????? 0.5??????? ????????????-2.0

1????????????????????????? 0.5??????????????????? -2.0

2????????????????????????? 0.1?????? ?????????????-1.0

3????????????????????????? 0.1??????????????????? -1.0

4????????????????????????? 0.1??????????????????? -1.0

5????????????????? ????????0.5??????????????????? -1.0

6????????????????????????? 0.5??????????????????? -1.0

7???????????????? ?????????0.5??????????????????? -3.0

8????????????????????????? 0.5??????????????????? -1.0

希望每三行分成一組,并把眾數(shù)作為該組的結(jié)果懂拾。理想的結(jié)果如下:

time??????????????????? ???a?????????????????????? b

2????????????????????????? 0.5??????????????????? -2.0

5????????????????????????? 0.1??????????????????? -1.0

8????????????????????????? 0.5??????????????????? -1.0

問題分析:該問題的分組與現(xiàn)有的列沒有關(guān)系煤禽,只與位置相關(guān)铐达,因此需要衍生出一列作為分組依據(jù)岖赋,按位置做整數(shù)乘法即得到衍生列,然后據(jù)此分組即可瓮孙。

Python 代碼

import pandas as pd

import numpy as np

data = pd.read_csv("group3.txt",sep='\t')

res = data.groupby(np.arange(len(data)) // ? 3).agg(lambda x: x.mode().iloc[-1])

print(res)




按照衍生列分組唐断,使用 agg 結(jié)合 lambda 的方式得到眾數(shù)选脊,取各組各列的最后 1 個(gè)眾數(shù)作為結(jié)果

討論:衍生列計(jì)算方式為 np.arange(len(data)) // 3,其結(jié)果是 [0 0 0 1 1 1 2 2 2]脸甘,把它作為鍵進(jìn)行分組就可以把數(shù)據(jù)分成每三行一組恳啥。而 agg(lambda x: x.mode()) 則是將各組的各列分別求眾數(shù),如第一組 time 的眾數(shù)為 [0,1,2] 而 a 和 b 的眾數(shù)分別是 [0.5] 和[-2.0]分別取最后 1 個(gè)眾數(shù) iloc[-1]即得到想要的結(jié)果丹诀。

八钝的、值變化分組

值變化分組,指在有序的數(shù)據(jù)中铆遭,發(fā)生數(shù)據(jù)變化時(shí)就分出一個(gè)新組硝桩。舉例如下:

source:https://stackoverflow.com/questions/41620920/groupby-conditional-sum-of-adjacent-rows-pandas

數(shù)據(jù)片段如下:

????? duration? location? user

0??????? 10??? house??? A

1???????? 5??? house??? A

2???????? 5????? gym??? A

3???????? 4????? gym??? B

4??????? 10???? shop??? B

5???????? 4????? gym??? B

6???????? 6????? gym??? B

按照 user 分組后,各組當(dāng) location 連續(xù)相同時(shí)對(duì) duration 進(jìn)行求和枚荣,location 變化時(shí)則重新求和碗脊。理想結(jié)果如下:

? ?duration? location?? user

??????? 15??? house??? A

???????? 5????? gym??? A

???????? 4????? gym??? B

??????? 10???? shop??? B

??????? 10????? gym??? B

問題分析:location 列的順序很重要,連續(xù)相同時(shí)可以視為一組橄妆,當(dāng)變化時(shí)則重新分一組衙伶,如 user=B 時(shí),第 4 行 (索引為 3) 的 location 為 [gym,shop,gym,gym], 不可以把其中的 3 個(gè) gym 分到 1 組害碾,而應(yīng)該把第一個(gè) gym 單獨(dú)作為 1 組矢劲,shop 與 gym 不同,值發(fā)生了變化慌随,把 shop 分到下一組卧须,后面兩個(gè) gym 沒有值變化,可以分到同一組儒陨,分組的結(jié)果為[[gym],[shop],[gym,gym]]花嘶,所以這里不可以使用 df.groupby(['user','location']).duration.sum() 來計(jì)算結(jié)果,而是要想辦法生成一個(gè)衍生列作為分組依據(jù)蹦漠。

代碼如下:

import pandas as pd

df = pd.DataFrame({'user' : ['A', 'A', 'A', 'B', 'B', ? 'B','B'],

????????????? ? 'location' : ['house','house','gym','gym','shop','gym','gym'],

????????????? ? 'duration':[10,5,5,4,10,4,6]})

derive = (df.location != ? df.location.shift()).cumsum()

res = df.groupby(['user', 'location', derive], ? as_index=False, sort=False)['duration'].sum()

print(res)


生成數(shù)據(jù)




創(chuàng)造衍生列

按照 user,location 和衍生列分組椭员,對(duì) duraton 求和


討論:衍生列 derive 是當(dāng) location 與前者不同時(shí)進(jìn)行累加,得到 [1 1 2 2 3 4 4]笛园。然后按照 user隘击,location 和該數(shù)列分組,再對(duì) duration 求和研铆。

九埋同、條件變化分組

條件變化分組:指在有序的數(shù)據(jù)中,當(dāng)滿足某一條件時(shí)重新分組棵红。舉例如下:

source:https://stackoverflow.com/questions/62461647/choose-random-rows-in-pandas-datafram

數(shù)據(jù)片段如下:

ID????????? code

333_c_132?? x

333_c_132?? n06

333_c_132?? n36

333_c_132?? n60

333_c_132?? n72

333_c_132?? n84

333_c_132?? n96

333_c_132?? n108

333_c_132?? n120

999_c_133?? x

999_c_133?? n06

999_c_133?? n12

999_c_133?? n24

998_c_134?? x

998_c_134?? n06

998_c_134?? n12

998_c_134?? n18

998_c_134?? n36

997_c_135?? x

997_c_135?? n06

997_c_135?? n12

997_c_135?? n24

997_c_135?? n36

996_c_136?? x

996_c_136?? n06

996_c_136?? n12

996_c_136?? n18

996_c_136?? n24

996_c_136?? n36

995_c_137?? x

希望從 code 列的每?jī)蓚€(gè) x 中間隨機(jī)取一行

理想結(jié)果形式如下:

333_c_132?? n06

999_c_133?? n12

998_c_134?? n18

997_c_135?? n36

996_c_136?? n18

問題分析:取兩個(gè) x 之間的隨機(jī)一條記錄凶赁,可以轉(zhuǎn)化成每當(dāng) code 等于 x 時(shí)開始新的一組,不等于 x 時(shí)分組不變,然后從該組中隨機(jī)取一行虱肄。因此這里還是需要生成衍生列致板,把它作為鍵分組才能完成任務(wù)。

代碼如下:

import pandas as pd

df = pd.read_csv("data.txt")

derive = df.code.eq('x').cumsum()

res=df[df.code.ne('x')].groupby(derive).apply(lambda ? x : x.sample(1))

res=res.reset_index(level=0, drop=True)

print(res)?



生成衍生列

根據(jù)衍生列分組咏窿,使用 apply 結(jié)合 lambda 的方式隨機(jī)抽樣

重置索引


討論:code.eq(x) 表示 code 等于 x 時(shí)為 True斟或,其余為 False,cumsum()表示對(duì)其累加集嵌,生成的衍生列為 [1 1 1 1 1 1 1 1 1 2 2…]萝挤,過濾掉等于 x 的列再根據(jù)該列進(jìn)行分組并抽樣即可。

思考:

前面所有的例子都是將原集合根據(jù)某個(gè)條件根欧,將數(shù)據(jù)劃分成若干個(gè)子集平斩,且滿足以下兩點(diǎn):

1)沒有空子集

2)原集合的任何成員都屬于且只屬于某一個(gè)子集

我們稱這種劃分方式為完全劃分。那么有沒有不完全劃分呢咽块?

來看下面這幾個(gè)例子

十绘面、對(duì)位分組

對(duì)位分組,指先羅列出一個(gè)基準(zhǔn)集合侈沪,然后將待分組集合成員的某個(gè)屬性(字段或表達(dá)式)與基準(zhǔn)集合成員比較揭璃,相同者則分到一個(gè)子集中,最后拆分出來的子集數(shù)量和基準(zhǔn)集合成員數(shù)是相同的亭罪。對(duì)位分組有三個(gè)特點(diǎn):

1)可能出現(xiàn)空子集(比如基準(zhǔn)集合的某些成員在待分組集合中并不存在)瘦馍;

2)可能有待分組集合成員未被分到任何子集(比如有些不重要的成員未被列入基準(zhǔn)集合);

3)每個(gè)成員最多只出現(xiàn)在一個(gè)子集中应役。

(一)出現(xiàn)空子集

公司統(tǒng)計(jì)各部門男女人數(shù)情组,如果某個(gè)部門沒有男員工或者沒有女員工,則將該部門的男員工人數(shù)或女員工人數(shù)填為 0箩祥。

問題分析:如果直接按照部門和性別分組院崇,則如果某個(gè)部門沒有女員工或沒有男員工時(shí),該部門將只被分成 1 組袍祖,就會(huì)丟失掉缺少的性別的統(tǒng)計(jì)信息底瓣,因此不可以直接 groupby([‘DEPT’,’GENDER’])。很容易想到的方案就是蕉陋,先按部門分組捐凭,羅列出 [男, 女] 的基準(zhǔn)集合,使用左連接 (left join) 的方式與各組連接凳鬓,再對(duì)連接后的結(jié)果按照性別分組茁肠,最后匯總結(jié)果,這樣就能保證分組的結(jié)果總會(huì)有 [男, 女] 了缩举。

Python 代碼

import pandas as pd

def align_group(g,l,by):

? ??d = pd.DataFrame(l,columns=[by])

??? m = ? pd.merge(d,g,on=by,how='left')

return m.groupby(by,sort=False)

employee = pd.read_csv("Employees.csv")

l = ['M','F']

res = employee.groupby('DEPT').apply(lambda ? x:align_group(x, l, 'GENDER').apply(lambda s:s.EID.count()))

print(res)


函數(shù)垦梆,對(duì)位分組

生成對(duì)照的 dataframe

利用 merge 完成對(duì)位運(yùn)算

分組


指定序列

按 DEPT 分組匹颤,再對(duì)各組使用函數(shù)對(duì)位分組,對(duì) EID 進(jìn)行計(jì)數(shù)


討論:

自定義函數(shù) align_group奶赔,使用 merge()函數(shù)完成羅列集合與待分組集合的 left join,再按 merge 的列進(jìn)行分組杠氢。按部門分組后站刑,使用 apply() 結(jié)合 lambda 表達(dá)式的方式對(duì)每組使用自定義函數(shù)對(duì)位分組,最后對(duì) EID 列計(jì)數(shù)得到最終結(jié)果鼻百。(注意:這里不可以對(duì) GENDER 計(jì)數(shù)绞旅,因?yàn)?merge 時(shí) GENDER 的成員都被保留了,如果有空子集時(shí)温艇,對(duì)它計(jì)數(shù)結(jié)果將是 1因悲,而其他列(比如 EID), 在 left join 時(shí)會(huì)是空值,所以對(duì) EID 計(jì)數(shù)結(jié)果是 0)勺爱。

(二)有待分組集合成員未被分到任何子集

按指定的部門 ['Administration', 'HR', 'Marketing', 'Sales'] 分組晃琳,只查詢這幾個(gè)部門的人數(shù)且部門先后順序保持不變。

問題分析:與出現(xiàn)空子集的情況類似琐鲁,此時(shí)也可以使用 left join 的方式卫旱,將不在預(yù)先羅列的集合成員排除掉,只保留羅列集合中的成員围段。

代碼如下:

import pandas as pd

def align_group(g,l,by):

??? d = ? pd.DataFrame(l,columns=[by])

??? m = ? pd.merge(d,g,on=by,how='left')

??? return ? m.groupby(by,sort=False)

employee = pd.read_csv("Employees.csv")

sub_dept = ['Administration', 'HR', 'Marketing', ? 'Sales']

res = ? align_group(employee,sub_dept,'DEPT').apply(lambda x:x.EID.count())

print(res)


函數(shù)顾翼,對(duì)位分組





指定順序的部門子集

使用對(duì)位分組函數(shù)分組,再對(duì) EID 計(jì)數(shù)

討論:Pandas 不直接支持對(duì)位分組的功能奈泪,因此完成起來成本就會(huì)比較高适贸,而且使用 merge 函數(shù)也會(huì)導(dǎo)致運(yùn)行效率低下。

十一涝桅、枚舉分組

枚舉分組:事先指定一組條件拜姿,將待分組集合的成員作為參數(shù)計(jì)算這批條件,條件成立者被劃分到與該條件對(duì)應(yīng)的一個(gè)子集中冯遂,結(jié)果集的子集和事先指定的條件一一對(duì)應(yīng)砾隅。枚舉分組的特點(diǎn):允許集合成員重復(fù)出現(xiàn)在不同的子集中。

舉例如下:

按在公司的工齡將員工分組統(tǒng)計(jì)每組的男女員工人數(shù)(分組條件重合時(shí)债蜜,列出所有滿足條件的員工晴埂,分組的條件是 [工齡 <5 年,5 年 <= 工齡 <10 年寻定,工齡 >=10 年儒洛,工齡 >=15 年])

問題分析:工齡 >=10 年和工齡 >=15 年兩個(gè)條件有重復(fù)的區(qū)間,即工齡大于 15 年的員工狼速,其工齡也一定大于 10 年琅锻,這時(shí)如果使用構(gòu)造衍生列的方式來完成,將無法使同一個(gè)成員重復(fù)出現(xiàn)在兩個(gè)分組中,因此需要考慮每個(gè)條件都分一次組恼蓬,然后找出滿足條件的組惊完,最后再匯總。

import pandas as pd

import datetime

def eval_g(dd:dict,ss:str):

??? return ? eval(ss,dd)???

emp_file = 'E:\\txt\\employee.txt'

emp_info = pd.read_csv(emp_file,sep='\t')

employed_list = ['Within five years','Five to ten ? years','More than ten years','Over fifteen years']

employed_str_list = ? ["(s<5)","(s>=5) & ? (s<10)","(s>=10)","(s>=15)"]

today = datetime.datetime.today().year

arr = pd.to_datetime(emp_info['HIREDATE'])

employed = today-arr.dt.year

emp_info['EMPLOYED']=employed

dd = {'s':emp_info['EMPLOYED']}

group_cond = []

for n in range(len(employed_str_list)):

??? emp_g = ? emp_info.groupby(eval_g(dd,employed_str_list[n]))

??? emp_g_index ? = [index for index in emp_g.size().index]

??? if True not ? in emp_g_index:

??????? ? female_emp=0

??????? ? male_emp=0

??? else:

??????? group = ? emp_g.get_group(True)

??????? sum_emp ? = len(group)

??????? ? female_emp = len(group[group['GENDER']=='F'])

??????? ? male_emp = sum_emp-female_emp

??? ? group_cond.append([employed_list[n],male_emp,female_emp])

group_df = ? pd.DataFrame(group_cond,columns=['EMPLOYED','MALE','FEMALE'])

print(group_df)



函數(shù)处硬,字符串轉(zhuǎn)表達(dá)式






分組條件




計(jì)算入職時(shí)間




循環(huán)分組條件

按分組條件分組

分組索引

如果沒有滿足條件的成員

男女員工數(shù)為 0


滿足條件

獲取分組

計(jì)算男女員工人數(shù)





匯總各個(gè)分組條件的計(jì)算結(jié)果

討論:EMPLOYED 是根據(jù)入職時(shí)間 HIREDATE 新增加的一列小槐,表示工齡。自定義函數(shù) eval_g()荷辕,是把分組的條件轉(zhuǎn)換成表達(dá)式凿跳,比如當(dāng)條件是 s<5 時(shí),eval_g(dd,ss)的表達(dá)式就是 emp_info['EMPLOYED']<5疮方,根據(jù)這個(gè)衍生列來對(duì)數(shù)據(jù)分組控嗜。對(duì)分組條件進(jìn)行循環(huán),按該衍生列分成兩組骡显,get_group(True) 表示取滿足條件的組疆栏,最后把所有滿足條件的結(jié)果使用 concat() 函數(shù)匯總。

總結(jié)

Python 在進(jìn)行分組處理時(shí)惫谤,多數(shù)情況可以比較優(yōu)雅的處理承边,但在處理有序分組時(shí),如值變化分組石挂、條件變化分組時(shí)則需要自己想辦法生成滿足分組條件的衍生列博助,略顯麻煩。對(duì)位分組和枚舉分組的兩種情況更是糟糕痹愚,需要自己想辦法去繞富岳,要么使用 merge 運(yùn)算,要么多次分組拯腮,使分組的成本變得很高窖式,這樣看來,Pandas 的分組運(yùn)算還有其局限性动壤。

對(duì)于分組運(yùn)算萝喘,相比之下,esProc SPL 處理的更完善琼懊。 esProc 是專業(yè)的數(shù)據(jù)計(jì)算引擎,SPL 提供了豐富的分組運(yùn)算哼丈,可以方便的完成上述任務(wù)启妹,代碼風(fēng)格的一致程度也更好。

兩個(gè)分組運(yùn)算函數(shù) groups()和 group()醉旦,分別實(shí)現(xiàn)分組聚合和分組子集饶米,可以比 Python 更簡(jiǎn)潔地解決前面六個(gè)常規(guī)分組問題:

分組子集運(yùn)算

對(duì)于這六個(gè)簡(jiǎn)單分組計(jì)算桨啃,Python 的分組計(jì)算方法同樣方便。但涉及了很多其他函數(shù)檬输,如 agg照瘾,transform,apply丧慈,lambda 表達(dá)式甚至是自定義函數(shù)等等析命,代碼風(fēng)格差別比較大。而 SPL 則基本保持了 groups(x;y) 或者是 group(x).(y) 這樣統(tǒng)一的代碼風(fēng)格伊滋。

對(duì)于問題七碳却、八队秩、九笑旺,Python 就略顯煩瑣,需想辦法生成衍生列馍资,而 SPL 本身基于有序集合設(shè)計(jì)筒主,提供了有序分組的選項(xiàng),仍可以優(yōu)雅的保持簡(jiǎn)單運(yùn)算時(shí)的代碼風(fēng)格鸟蟹。

問題SPL代碼簡(jiǎn)單說明

根據(jù)分組后直接聚合還是分組后針對(duì)子集計(jì)算乌妙,靈活選擇 groups 和 group 函數(shù)。

最后兩個(gè)問題建钥,對(duì)位分組和枚舉分組藤韵,確實(shí)有點(diǎn)難為 Python 了,不過不管是使用 merge 函數(shù)繞還是多次分組熊经,總算是完成了任務(wù)泽艘。而 SPL 提供了專門的對(duì)位分組函數(shù) align()和枚舉分組函數(shù) enum(),可以繼續(xù)優(yōu)雅镐依。


有成員被分到不同子集

需要提到的是匹涮,Python 還有一個(gè)致命缺點(diǎn)——大數(shù)據(jù)(無法一次性讀入內(nèi)存)分組,它涉及到外存讀寫和 hash 分組槐壳,對(duì)于非專業(yè)的程序員來說然低,使用 Python 完成這個(gè)任務(wù)幾乎是不可能的。有興趣可以參考以下文章:

Python 如何處理大文件

這里介紹了 Python 處理大數(shù)據(jù)存在的問題(包括大數(shù)據(jù)分組)务唐,也簡(jiǎn)單介紹了 esProc SPL 中的游標(biāo)系統(tǒng)雳攘,其中 group 和 groupx() 函數(shù)仍然可以優(yōu)雅的完成大數(shù)據(jù)分組任務(wù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枫笛,一起剝皮案震驚了整個(gè)濱河市来农,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崇堰,老刑警劉巖沃于,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涩咖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡繁莹,警方通過查閱死者的電腦和手機(jī)檩互,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咨演,“玉大人闸昨,你說我怎么就攤上這事”》纾” “怎么了饵较?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遭赂。 經(jīng)常有香客問我循诉,道長,這世上最難降的妖魔是什么撇他? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任茄猫,我火速辦了婚禮,結(jié)果婚禮上困肩,老公的妹妹穿的比我還像新娘划纽。我一直安慰自己,他們只是感情好锌畸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布勇劣。 她就那樣靜靜地躺著,像睡著了一般潭枣。 火紅的嫁衣襯著肌膚如雪比默。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天卸耘,我揣著相機(jī)與錄音退敦,去河邊找鬼。 笑死蚣抗,一個(gè)胖子當(dāng)著我的面吹牛侈百,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翰铡,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钝域,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锭魔?” 一聲冷哼從身側(cè)響起例证,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎迷捧,沒想到半個(gè)月后织咧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胀葱,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年笙蒙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抵屿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捅位,死狀恐怖轧葛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艇搀,我是刑警寧澤尿扯,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站焰雕,受9級(jí)特大地震影響衷笋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淀散,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一右莱、第九天 我趴在偏房一處隱蔽的房頂上張望蚜锨。 院中可真熱鬧档插,春花似錦、人聲如沸亚再。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氛悬。三九已至则剃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間如捅,已是汗流浹背棍现。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镜遣,地道東北人己肮。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像悲关,于是被迫代替她去往敵國和親谎僻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 幾乎所有的程序語言都能處理數(shù)據(jù)寓辱,但有些過于通用艘绍,缺乏專業(yè)的結(jié)構(gòu)化計(jì)算函數(shù),用于數(shù)據(jù)處理時(shí)代碼比較繁瑣秫筏,比如C++诱鞠、...
    心宇gxy閱讀 471評(píng)論 0 1
  • 職場(chǎng)人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯挎挖。不過相信大家一定有過很無助的情況,比如復(fù)雜計(jì)算航夺、重復(fù)計(jì)算肋乍、...
    西柚學(xué)報(bào)表閱讀 556評(píng)論 1 6
  • 職場(chǎng)人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯。不過相信大家一定有過很無助的情況敷存,比如復(fù)雜計(jì)算墓造、重復(fù)計(jì)算、...
    心宇gxy閱讀 1,114評(píng)論 1 10
  • 職場(chǎng)人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯。不過相信大家一定有過很無助的情況涮俄,比如復(fù)雜計(jì)算蛉拙、重復(fù)計(jì)算、...
    心宇gxy閱讀 303評(píng)論 0 1
  • 1.添加Age彻亲、Fullname字段 esproc: A4:我們用T表示序表孕锄。T.derive()表示增加字段。這...
    小黃鴨呀閱讀 382評(píng)論 0 0