Python 和 SPL 对比 6——等值分组

当事物比较多时,人们习惯将这些事物分类,然后再做聚合运算。如查看学校各班级的最高成绩,查看公司员工各部门的平均年龄等,这种运算称为分组,大多数分组也会伴随着下一步的聚合运算。最常见的分组是将属性值相同的成员分成一个组,也叫等值分组,本文对比 Python SPL 在等值分组及聚合方面的运算能力。

单列分组聚合

按某一列分组后聚合是最常见的分组运算了,如:

计算各部门员工人数,平均工资和最大年龄

公司成员信息表:

..

Python

import pandas as   pd

import datetime

import numpy as np

import math

def max_age(s):

    earliest_birth=s.min()

    today = datetime. datetime.today()

    age=math.floor((today-   earliest_birth)/np.timedelta64(1,'Y'))

    return age

file="D:\data\EMPLOYEE.csv"

emp=pd.read_csv(file)

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

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

print(dept_agg)

导入各种库函数

 

 

 

 

计算最大年龄的函数

 

 

 

 

读员工信息

转换日期格式

分组汇总

 

 

Pandas中的 groupby()函数是将 Dataframe 分组形成一个分组对象,“对象”这个东西不好理解,不知道里边是什么。agg() 函数是聚合函数,里边可以用字典的形式定义哪一列执行哪个或者哪几个聚合运算,当没有现成函数时还可以使用自己定义的函数,如本例中的 count,mean 就是现成的聚合函数,max_age 则是自己定义的,这一点很友好,不过要注意,自定义函数的入参是个 Series,这里就是各组的 BIRTHDAY,这需要好好思考分组聚合的原理,否则真的很难理解。当然,最常见的分组聚合运算可能就一个聚合运算,Python 还是很容易写的,比如计算各部门的平均工资:

dept_salary= emp.groupby('DEPT',as_index=False).SALARY.mean()

SPL


A

B

1

D:\data\EMPLOYEE.csv

/公司成员信息路径

2

=file(A1).import@tc()

/读数据

3

=A2.groups(DEPT;count(SALARY):NUM,avg(SALARY):AVG_SALARY,

max(age(BIRTHDAY)):MAX_AVG)

/分组聚合

SPL的 groups()函数执行分组聚合运算,“;”之前是分组的字段,之后是聚合运算,对哪些字段执行什么聚合运算一目了然,而且还可以对聚合后的结果重命名字段,如 count(SALARY):NUM,就是把 SALARY 计数后的结果命名为 NUM,形成新序表。只有一个聚合运算时,形式也是一样的,如计算各部门的平均工资:A3=A2.groups(DEPT;avg(SALARY): AVG_SALARY),没有 Python 那么多变化。

多列分组聚合

有时我们希望按多列进行分组,如:

计算各部门男女员工的平均工资

Python

#续用员工信息emp
avg_salary= emp.groupby(['DEPT','GENDER'],as_index=False).SALARY.mean()
print(avg_salary)

 

 

多列分组聚合

groupby()的分组键支持序列格式,如 ['DEPT','GENDER'],后续的聚合运算是一样的。

SPL


A

B

/A2是员工信息


5

=A2.groups(DEPT,GENDER;avg(SALARY):AVG_SALARY)

/多列分组聚合

SPL的 groups() 函数不需要以序列的形式传入多列分组键,只要在“;”前用“,”隔开即可,形式还是一样的。

分组不聚合

分组不一定总是被强制汇总,有时候我们还对分组后子集感兴趣。如:

将各部门按照入职时间从早到晚进行排序

Python

#续用 emp

emp_sort=emp.groupby('DEPT',as_index=False).apply(lambda   x:x.sort_values('HIREDATE')).reset_index(drop=True)

print(emp_sort)

 

分组排序

 

Python分组后可以用 apply+lambda 表达式来实现各组的排序运算,最后的 reset_index() 是重置索引。apply+lambda 表达式之前在《Python 和 SPL 对比系列 3——循环函数》中提到过,这里的用法是一样的。

SPL


A

B

/A2是员工信息


7

=A2.group(DEPT).conj(~.sort(HIREDATE))

/分组排序

SPL中 group()函数只分组不聚合,返回结果是集合的集合,这也符合分组的本质,而不像 Python 那样返回一个难以理解的对象。既然是集合,那就可以用循环函数来处理,分别对子集排序后再合并这些子集就得到了分组排序结果。这里要多说一句,group() 函数返回子集结果,理论上可以完成 groups()函数的所有功能,但为什么还要设计 groups() 呢?因为 groups()是用迭代函数的方法来计算汇总值的,过程中并不会保持每个分组子集,这样会有更高的效率并占用更少的存储空间。

分组复杂聚合

有时候即使我们想得到分组子集的某个聚合结果,但它并不容易计算,没有简单的聚合函数能写出来,这时我们也需要保持分组子集用来进一步计算。如:

计算各部门年龄最大的员工和年龄最小的员工的工资差

Python

#续用 emp

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

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

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

print(salary_diff)

 

计算工资差函数

年龄最大的员工索引

年龄最小的员工索引

工资差

 

转换时间格式

分组复杂聚合

Python利用一个自定义函数来完成这个复杂的聚合运算,结合 apply 循环每个组,得到结果,这里要注意下自定义函数的入参是分组后的子集成员,这点不太容易想到。

SPL


A

B

/A2是员工信息


9

=A2.group(DEPT;(ma=~.minp(BIRTHDAY),mi=~.maxp(BIRTHDAY),

ma.SALARY-mi.SALARY):SALARY_DIF)

/分组复杂聚合

SPL用 group()分组得到分组子集,minp() 和 maxp()直接返回最大最小值的成员,直接取要计算的字段计算即可,计算下来,一气呵成,完全符合我们的思考逻辑。这句代码和 groups() 类似了,用这种形式只是看起来简单。本质上是这样的:

A9=A2.group(DEPT).new(DEPT,(ma=~.minp(BIRTHDAY),mi=~.maxp(BIRTHDAY),ma.SALARY-mi.SALARY):SALARY_DIF)

这和上例的 group()+conj() 是一个道理,都是处理分组后的子集。

分组唯一计数

数据分析时,可能还会遇到统计分组后的非重复成员数量。如:

统计各部门员工来自几个州

Python

#续用 emp

dept_state=emp.groupby('DEPT',as_index=False).agg({"STATE":pd.Series.nunique})

print(dept_state)

 

分组计数

 

Python有非重复成员的函数,pd.Series.nunique,有了它就和开始介绍的分组聚合是一样的了。

SPL


A

B

/A2是员工信息


11

=A2.groups(DEPT;icount(STATE))

/分组计数

SPL的 icount()返回每个组内的非重复值的数量,这大体相当于在分组内又做了一次分组,但内层分组只做计数而不再做其它汇总了。

小结

Python的分组聚合运算还是比较齐全的,而且也能成体系,groupby+agg 可以完成各式各样的聚合运算,groupby+apply 可以完成分组子集的处理,但 groupby 以后的分组对象很难理解,既不是 Series 也不是 Dataframe,不知道是什么样的数据结构。这对后续的 agg 或者 apply 的运算有巨大的影响,因为不知道输入是什么东西,只能多做练习,一点一点理解 groupby 之后的“对象”了。

SPL是按照正常的思路来的,分组后是个子集,整个结果是集合的集合,之后的处理只要按正常语法处理就可以了,不需要额外理解什么东西,举一反三不在话下。groups() 还可以进一步提高分组聚合的运算效率。