在日常數(shù)據(jù)分析時,經(jīng)常會遇到需要按列分組 (groupby) 的任務(wù),如計算某公司各部門的人數(shù),計算各部門男女平均工資,計算不同年代的員工的平均工資等等。在進行這類運算時,Pandas 提供了 groupby 函數(shù),大多數(shù)問題它都可以解決,但有一些問題使用 groupby 函數(shù)會略顯麻煩,下面我們就這些問題展開細致的討論。
groupby 是 pandas 中非常重要的一個函數(shù), 主要用于數(shù)據(jù)分類和聚合計算. 其思想是“split-apply-combine”(拆分 - 應(yīng)用 - 合并),如下圖:

分組原理圖
一、單列分組聚合
單列分組聚合是指把某一列作為鍵進行分組,然后對各組進行聚合運算。
它是上述分組原理的最簡單應(yīng)用,比如根據(jù)員工信息數(shù)據(jù),計算各部門員工數(shù)。
問題分析:要計算各部門員工數(shù),首先把部門作為鍵進行分組,然后對各組成員進行計數(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ù)
分組計數(shù)
討論:groupby(‘DEPT’) 將數(shù)據(jù)按照部門分組, count() 函數(shù)進行計數(shù)。
二、多列分組聚合
多列分組聚合是指把多列的值同時作為鍵進行分組,然后對各組進行聚合運算。
它和單列分組聚合類似,只是分組的鍵是多列組合而已。如根據(jù)員工信息數(shù)據(jù),計算各部門男女員工的平均工資。
繼續(xù)使用上例中的員工信息數(shù)據(jù)
問題分析:需要分組的鍵有兩個,分別是部門和性別,只要把他們組合起來看作是一個鍵,然后當做單列分組聚合即可。
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ù)衍生列分組聚合,是指需要分組的鍵并不直接在數(shù)據(jù)中,需要根據(jù)數(shù)據(jù)計算出一列新數(shù)據(jù),把它作為鍵對數(shù)據(jù)進行分組。如計算不同年代的員工的平均工資。
問題分析:員工信息數(shù)據(jù)中并沒有年代這一列,因此需要根據(jù)員工的生日列計算出來,把它作為鍵對員工數(shù)據(jù)進行分組,然后再求工資均值。
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)換成日期格式
計算衍生數(shù)組并按此數(shù)組分組,再計算平均工資
討論:年代數(shù)據(jù)在原數(shù)據(jù)中并不存在,使用 np.floor((employee['BIRTHDAY'].dt.year-1900)/10) 計算出衍生列表示年代,然后根據(jù)他分組并計算平均工資。
四、多個聚合
多個聚合,是指分組后對單列或者多列進行多種聚合。
(一)?? 多列單聚合
多列單聚合,指同時對多列聚合,但每列使用一種聚合方式。如:同時計算各部門員工的人數(shù),平均工資。
問題分析:求員工人數(shù)可以對 EID 計數(shù),求平均工資需要對工資列求均值,兩列聚合但每列只用一種聚合方式。
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'}))
分組并對 EID 計數(shù),對 SALARY 求平均
重命名列名
討論:Pandas 的 agg()函數(shù)可以完成這類任務(wù),各列以及各列的聚合方式以字典的形式作為參數(shù)傳入 agg(),聚合的列作為字典的鍵,聚合方式作為字典的值,從而完成聚合運算。
(二)?? 單列多聚合
單列多聚合,指只對一列聚合,但聚合的方式有多種。如上述問題也可以直接對工資計數(shù)并求平均,此時是對工資進行了兩種聚合——計數(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'}))
對 SALARY 計數(shù)并求平均
重命名列名
討論:如果是單列的不同聚合方式,則可以把聚合方式進行組合以列表的形式作為參數(shù)傳入 agg()。
(三)?? 多列多聚合
多列多聚合,指對多列聚合同時也包含單列多聚合的組合聚合方式。聚合方式還可以是自己定義的函數(shù),
如:計算各部門員工人數(shù),平均工資和最大年齡。
問題分析:計算員工人數(shù)和平均工資,是對工資列計數(shù)并求平均(單列多聚合),求最大年齡,需對生日列使用自定義的函數(shù)計算出最大年齡。
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 計數(shù)和求均值,BIRTHDAY 使用 max_age 計算最大年齡
修改列名
討論:這種情況,聚合列和聚合方式還是按照字典的方式傳入,但當某一列需要多種聚合方式時,則需要將其組合,以列表的形式作為字典的值。
五、分組聚合值復(fù)制
分組聚合值復(fù)制,指把分組聚合的結(jié)果轉(zhuǎn)換成與該組等長的列,相當于把聚合的結(jié)果復(fù)制到該組的所有行。如:為員工信息數(shù)據(jù)新增一列各部門的平均工資。
問題分析:各部門的平均工資需要按照部門分組再對工資求平均,把平均工資的值添加到對應(yīng)的組,并保持數(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 分組并對 SALARY 求平均
討論:按照部門分組后,對工資求均值。transform() 函數(shù)在組內(nèi)求聚合值后會按照原索引的順序返回結(jié)果,可以自動按照索引添加結(jié)果,從而保證原數(shù)據(jù)順序不變。
六、分組子集處理
分組應(yīng)用:指分組后對各組進行一些非聚合運算。比如分組排序,分組后不再關(guān)心聚合的結(jié)果,而是關(guān)心組內(nèi)記錄的順序。如:將各部門按照入職時間從早到晚進行排序 。
問題分析:按照部門分組后,不再關(guān)心分組后的聚合結(jié)果,而是關(guān)心員工的入職時間順序。分組后,對各組進行循環(huán)同時對組內(nèi)成員按照入職時間排序就可以了。
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)
修改入職時間格式
按 DEPT 分組,并對各組按照 HIREDATE 排序,最后重置索引
討論:分組后需要對組內(nèi)成員排序,可以使用 apply()函數(shù)結(jié)合 lambda 的方式,其中 lambda 表達式是對各組循環(huán),使用 sort_values() 函數(shù)在組內(nèi)部再排序,返回組內(nèi)排序的結(jié)果。
簡單的運算使用 lambda 函數(shù)計算,但有時會遇到比較復(fù)雜的計算,如:計算各部門年齡最大的員工和年齡最小的員工的工資差。
問題分析:首先需按照部門分組,分組后還需要找到年齡最大的員工和年齡最小的員工的記錄,然后才能計算工資差。
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ù):計算各組工資差
年齡最大的索引
年齡最小的索引
計算工資差
按 DEPT 分組并使用自定義函數(shù)計算
討論:使用 apply()結(jié)合自定義函數(shù)的方式。其中 apply() 會把分組的結(jié)果作為參數(shù)傳入自定義函數(shù)。salary_diff() 函數(shù)是自定義函數(shù),g 實質(zhì)上就是 pandas 的 DataFrame 格式的數(shù)據(jù)框,這里是分組的結(jié)果。對它計算最大年齡和最小年齡的索引后,找到工資字段計算差即得到結(jié)果。
思考:
由上述討論可見,熟練掌握 Pandas 的這些 groupby 方法對我們進行數(shù)據(jù)分析是特別有幫助的。
下面我們以 stack overflow 網(wǎng)站上的一些實際問題來進一步了解 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 個眾數(shù)作為結(jié)果
討論:衍生列計算方式為 np.arange(len(data)) // 3,其結(jié)果是 [0 0 0 1 1 1 2 2 2],把它作為鍵進行分組就可以把數(shù)據(jù)分成每三行一組。而 agg(lambda x: x.mode()) 則是將各組的各列分別求眾數(shù),如第一組 time 的眾數(shù)為 [0,1,2] 而 a 和 b 的眾數(shù)分別是 [0.5] 和[-2.0]分別取最后 1 個眾數(shù) iloc[-1]即得到想要的結(jié)果。
八、值變化分組
值變化分組,指在有序的數(shù)據(jù)中,發(fā)生數(shù)據(jù)變化時就分出一個新組。舉例如下:
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 分組后,各組當 location 連續(xù)相同時對 duration 進行求和,location 變化時則重新求和。理想結(jié)果如下:
? ?duration? location?? user
??????? 15??? house??? A
???????? 5????? gym??? A
???????? 4????? gym??? B
??????? 10???? shop??? B
??????? 10????? gym??? B
問題分析:location 列的順序很重要,連續(xù)相同時可以視為一組,當變化時則重新分一組,如 user=B 時,第 4 行 (索引為 3) 的 location 為 [gym,shop,gym,gym], 不可以把其中的 3 個 gym 分到 1 組,而應(yīng)該把第一個 gym 單獨作為 1 組,shop 與 gym 不同,值發(fā)生了變化,把 shop 分到下一組,后面兩個 gym 沒有值變化,可以分到同一組,分組的結(jié)果為[[gym],[shop],[gym,gym]],所以這里不可以使用 df.groupby(['user','location']).duration.sum() 來計算結(jié)果,而是要想辦法生成一個衍生列作為分組依據(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 和衍生列分組,對 duraton 求和
討論:衍生列 derive 是當 location 與前者不同時進行累加,得到 [1 1 2 2 3 4 4]。然后按照 user,location 和該數(shù)列分組,再對 duration 求和。
九、條件變化分組
條件變化分組:指在有序的數(shù)據(jù)中,當滿足某一條件時重新分組。舉例如下:
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 列的每兩個 x 中間隨機取一行
理想結(jié)果形式如下:
333_c_132?? n06
999_c_133?? n12
998_c_134?? n18
997_c_135?? n36
996_c_136?? n18
問題分析:取兩個 x 之間的隨機一條記錄,可以轉(zhuǎn)化成每當 code 等于 x 時開始新的一組,不等于 x 時分組不變,然后從該組中隨機取一行。因此這里還是需要生成衍生列,把它作為鍵分組才能完成任務(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 的方式隨機抽樣
重置索引
討論:code.eq(x) 表示 code 等于 x 時為 True,其余為 False,cumsum()表示對其累加,生成的衍生列為 [1 1 1 1 1 1 1 1 1 2 2…],過濾掉等于 x 的列再根據(jù)該列進行分組并抽樣即可。
思考:
前面所有的例子都是將原集合根據(jù)某個條件,將數(shù)據(jù)劃分成若干個子集,且滿足以下兩點:
1)沒有空子集
2)原集合的任何成員都屬于且只屬于某一個子集
我們稱這種劃分方式為完全劃分。那么有沒有不完全劃分呢?
來看下面這幾個例子
十、對位分組
對位分組,指先羅列出一個基準集合,然后將待分組集合成員的某個屬性(字段或表達式)與基準集合成員比較,相同者則分到一個子集中,最后拆分出來的子集數(shù)量和基準集合成員數(shù)是相同的。對位分組有三個特點:
1)可能出現(xiàn)空子集(比如基準集合的某些成員在待分組集合中并不存在);
2)可能有待分組集合成員未被分到任何子集(比如有些不重要的成員未被列入基準集合);
3)每個成員最多只出現(xiàn)在一個子集中。
(一)出現(xiàn)空子集
公司統(tǒng)計各部門男女人數(shù),如果某個部門沒有男員工或者沒有女員工,則將該部門的男員工人數(shù)或女員工人數(shù)填為 0。
問題分析:如果直接按照部門和性別分組,則如果某個部門沒有女員工或沒有男員工時,該部門將只被分成 1 組,就會丟失掉缺少的性別的統(tǒng)計信息,因此不可以直接 groupby([‘DEPT’,’GENDER’])。很容易想到的方案就是,先按部門分組,羅列出 [男, 女] 的基準集合,使用左連接 (left join) 的方式與各組連接,再對連接后的結(jié)果按照性別分組,最后匯總結(jié)果,這樣就能保證分組的結(jié)果總會有 [男, 女] 了。
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ù),對位分組
生成對照的 dataframe
利用 merge 完成對位運算
分組
指定序列
按 DEPT 分組,再對各組使用函數(shù)對位分組,對 EID 進行計數(shù)
討論:
自定義函數(shù) align_group,使用 merge()函數(shù)完成羅列集合與待分組集合的 left join,再按 merge 的列進行分組。按部門分組后,使用 apply() 結(jié)合 lambda 表達式的方式對每組使用自定義函數(shù)對位分組,最后對 EID 列計數(shù)得到最終結(jié)果。(注意:這里不可以對 GENDER 計數(shù),因為 merge 時 GENDER 的成員都被保留了,如果有空子集時,對它計數(shù)結(jié)果將是 1,而其他列(比如 EID), 在 left join 時會是空值,所以對 EID 計數(shù)結(jié)果是 0)。
(二)有待分組集合成員未被分到任何子集
按指定的部門 ['Administration', 'HR', 'Marketing', 'Sales'] 分組,只查詢這幾個部門的人數(shù)且部門先后順序保持不變。
問題分析:與出現(xiàn)空子集的情況類似,此時也可以使用 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ù),對位分組
指定順序的部門子集
使用對位分組函數(shù)分組,再對 EID 計數(shù)
討論:Pandas 不直接支持對位分組的功能,因此完成起來成本就會比較高,而且使用 merge 函數(shù)也會導致運行效率低下。
十一、枚舉分組
枚舉分組:事先指定一組條件,將待分組集合的成員作為參數(shù)計算這批條件,條件成立者被劃分到與該條件對應(yīng)的一個子集中,結(jié)果集的子集和事先指定的條件一一對應(yīng)。枚舉分組的特點:允許集合成員重復(fù)出現(xiàn)在不同的子集中。
舉例如下:
按在公司的工齡將員工分組統(tǒng)計每組的男女員工人數(shù)(分組條件重合時,列出所有滿足條件的員工,分組的條件是 [工齡 <5 年,5 年 <= 工齡 <10 年,工齡 >=10 年,工齡 >=15 年])
問題分析:工齡 >=10 年和工齡 >=15 年兩個條件有重復(fù)的區(qū)間,即工齡大于 15 年的員工,其工齡也一定大于 10 年,這時如果使用構(gòu)造衍生列的方式來完成,將無法使同一個成員重復(fù)出現(xiàn)在兩個分組中,因此需要考慮每個條件都分一次組,然后找出滿足條件的組,最后再匯總。
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)表達式
分組條件
計算入職時間
循環(huán)分組條件
按分組條件分組
分組索引
如果沒有滿足條件的成員
男女員工數(shù)為 0
滿足條件
獲取分組
計算男女員工人數(shù)
匯總各個分組條件的計算結(jié)果
討論:EMPLOYED 是根據(jù)入職時間 HIREDATE 新增加的一列,表示工齡。自定義函數(shù) eval_g(),是把分組的條件轉(zhuǎn)換成表達式,比如當條件是 s<5 時,eval_g(dd,ss)的表達式就是 emp_info['EMPLOYED']<5,根據(jù)這個衍生列來對數(shù)據(jù)分組。對分組條件進行循環(huán),按該衍生列分成兩組,get_group(True) 表示取滿足條件的組,最后把所有滿足條件的結(jié)果使用 concat() 函數(shù)匯總。
總結(jié)
Python 在進行分組處理時,多數(shù)情況可以比較優(yōu)雅的處理,但在處理有序分組時,如值變化分組、條件變化分組時則需要自己想辦法生成滿足分組條件的衍生列,略顯麻煩。對位分組和枚舉分組的兩種情況更是糟糕,需要自己想辦法去繞,要么使用 merge 運算,要么多次分組,使分組的成本變得很高,這樣看來,Pandas 的分組運算還有其局限性。
對于分組運算,相比之下,esProc SPL 處理的更完善。 esProc 是專業(yè)的數(shù)據(jù)計算引擎,SPL 提供了豐富的分組運算,可以方便的完成上述任務(wù),代碼風格的一致程度也更好。
兩個分組運算函數(shù) groups()和 group(),分別實現(xiàn)分組聚合和分組子集,可以比 Python 更簡潔地解決前面六個常規(guī)分組問題:



分組子集運算
對于這六個簡單分組計算,Python 的分組計算方法同樣方便。但涉及了很多其他函數(shù),如 agg,transform,apply,lambda 表達式甚至是自定義函數(shù)等等,代碼風格差別比較大。而 SPL 則基本保持了 groups(x;y) 或者是 group(x).(y) 這樣統(tǒng)一的代碼風格。
對于問題七、八、九,Python 就略顯煩瑣,需想辦法生成衍生列,而 SPL 本身基于有序集合設(shè)計,提供了有序分組的選項,仍可以優(yōu)雅的保持簡單運算時的代碼風格。
問題SPL代碼簡單說明

根據(jù)分組后直接聚合還是分組后針對子集計算,靈活選擇 groups 和 group 函數(shù)。
最后兩個問題,對位分組和枚舉分組,確實有點難為 Python 了,不過不管是使用 merge 函數(shù)繞還是多次分組,總算是完成了任務(wù)。而 SPL 提供了專門的對位分組函數(shù) align()和枚舉分組函數(shù) enum(),可以繼續(xù)優(yōu)雅。

有成員被分到不同子集
需要提到的是,Python 還有一個致命缺點——大數(shù)據(jù)(無法一次性讀入內(nèi)存)分組,它涉及到外存讀寫和 hash 分組,對于非專業(yè)的程序員來說,使用 Python 完成這個任務(wù)幾乎是不可能的。有興趣可以參考以下文章:
這里介紹了 Python 處理大數(shù)據(jù)存在的問題(包括大數(shù)據(jù)分組),也簡單介紹了 esProc SPL 中的游標系統(tǒng),其中 group 和 groupx() 函數(shù)仍然可以優(yōu)雅的完成大數(shù)據(jù)分組任務(wù)。