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