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)算。

它是上述分組原理的最簡單應(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ì)算不同年代的員工的平均工資。

問題分析:員工信息數(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é)果。

簡單的運(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 列的每兩個(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 更簡潔地解決前面六個(gè)常規(guī)分組問題:

分組子集運(yùn)算

對(duì)于這六個(gè)簡單分組計(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)雅的保持簡單運(yùn)算時(shí)的代碼風(fēng)格。

問題SPL代碼簡單說明

根據(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ù)分組),也簡單介紹了 esProc SPL 中的游標(biāo)系統(tǒng),其中 group 和 groupx() 函數(shù)仍然可以優(yōu)雅的完成大數(shù)據(jù)分組任務(wù)。

?著作權(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)容

  • 幾乎所有的程序語言都能處理數(shù)據(jù),但有些過于通用,缺乏專業(yè)的結(jié)構(gòu)化計(jì)算函數(shù),用于數(shù)據(jù)處理時(shí)代碼比較繁瑣,比如C++、...
    心宇gxy閱讀 527評(píng)論 0 1
  • 職場人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯。不過相信大家一定有過很無助的情況,比如復(fù)雜計(jì)算、重復(fù)計(jì)算、...
    西柚學(xué)報(bào)表閱讀 617評(píng)論 1 6
  • 職場人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯。不過相信大家一定有過很無助的情況,比如復(fù)雜計(jì)算、重復(fù)計(jì)算、...
    心宇gxy閱讀 1,172評(píng)論 1 10
  • 職場人員使用 Excel 進(jìn)行數(shù)據(jù)處理已經(jīng)成為家常便飯。不過相信大家一定有過很無助的情況,比如復(fù)雜計(jì)算、重復(fù)計(jì)算、...
    心宇gxy閱讀 333評(píng)論 0 1
  • 1.添加Age、Fullname字段 esproc: A4:我們用T表示序表。T.derive()表示增加字段。這...
    小黃鴨呀閱讀 421評(píng)論 0 0

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