本文主要是介绍Pandas分组函数groupby、聚合函数agg和转换函数transform,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
pandas中的分组函数groupby()可以完成各种分组操作,聚合函数agg()可以将多个函数的执行结果聚合到一起,这两类函数经常在一起使用。
groupby用法和参数介绍
groupby(self, by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=no_default, observed=False, dropna=True)
:
by: 指定根据哪个/哪些字段分组,默认值是None,按多个字段分组时传入列表。by参数可以按位置参数的方式传入。
axis: 设置按列分组还是按行分组,0或index表示按列分组,1或columns表示按行分组,默认值为0。
level: 当DataFrame的索引为多重索引时,level参数指定用于分组的索引,可以传入多重索引中索引的下标(0,1…)或索引名,多个用列表传入。
level参数不能与by参数同时使用,如果两者同时存在,当by参数传入的是多重索引中的索引,则level不生效,当by参数传入的是DataFrame的列名,则报错。
as_index: 分组结果默认将分组列的值作为索引,如果按单列分组,结果默认是单索引,如果按多列分组,结果默认是多重索引。将as_index设置为False可以重置索引(0,1…)。
sort: 结果按分组列的值升序排列,将sort设置为False则不排序,可以提升性能。
dropna: 默认情况下,分组列的NaN在分组结果中不保留,将dropna设置为False,可以保留NaN分组。
其他三个参数不用关注,group_keys参数在源码中未使用,squeeze参数因为类型不兼容,官方已弃用,observed参数表示重设索引时,保留创建的全NaN行。
分组对象的内部结构
import pandas as pddf = pd.DataFrame({'name': ["张三", "李四", "小明", "小张"],'sex': ['男', '男', '男', '女'],'age': [21, 22, 21, 23]
})
print(df)
# name sex age
# 0 张三 男 21
# 1 李四 男 22
# 2 小明 男 21
# 3 小张 女 23
grouped = df.groupby('sex')
print(type(grouped))
# pandas.core.groupby.generic.DataFrameGroupBy
groupby()分组得到的是一个DataFrameGroupBy对象,直接打印DataFrameGroupBy对象只能看到它的内存地址,看不到内部的结构。
for name, group in grouped:print(name)print(group)print()# 女
# name sex age
# 3 小张 女 23# 男
# name sex age
# 0 张三 男 21
# 1 李四 男 22
# 2 小明 男 21
for group in grouped:print(group)print(type(group), type(group[0]), type(group[1]))
('女', name sex age
3 小张 女 23)
<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>
('男', name sex age
0 张三 男 21
1 李四 男 22
2 小明 男 21)
<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>
DataFrameGroupBy是一个可迭代对象,可以转换成list打印,也可以直接遍历打印出来。遍历出来的是一个个元组,每个元组对应一个分组,元组的第一个元素与分组列里的值对应,元组的第二个元素是分到当前小组的数据,是一个DataFrame。
DataFrameGroupBy对象的内部结构为:[(分组名1, 子DataFrame1), (分组名2, 子DataFrame2), …],相当于groupby()将DataFrame按字段值分成了多个小的DataFrame,然后将字段值和小的DataFrame用元组的方式保存在DataFrameGroupBy对象中。
print(grouped.groups)
group_name = [gn for gn in grouped.groups.keys()]
print(group_name)
group = grouped.get_group(group_name[2])
print('-'*40, '\n', group, sep='')
{'女': [3], '男': [0, 1, 2]}
['女', '男']
分组对象的groups属性可以返回分组信息,结果是一个形似字典的对象,由分组名和此分组数据在原DataFrame中的行索引组成。
借用groups可以提取出所有分组的分组名,分组对象的get_group()方法可以返回指定分组名的子DataFrame。
按多重索引分组
# 按多重索引分组
multi_grouped = df.set_index(['sex', 'age'])
print('-'*40, '\n', multi_grouped, sep='')
# ----------------------------------------
# name
# sex age
# 男 21 张三
# 22 李四
# 21 小明
# 女 23 小张# 按多重索引中的指定索引进行分组
grouped = multi_grouped.groupby(level='sex')
print('-'*40, '\n', grouped.count(), sep='')
# ----------------------------------------
# name
# sex
# 女 1
# 男 3# 按多重索引中的多个索引分组
grouped = multi_grouped.groupby(level=[0, 1])
print('-'*40, '\n', grouped.count(), sep='')
# ----------------------------------------
# name
# sex age
# 女 23 1
# 男 21 2
# 22 1
level参数用于设置按多重索引中的指定索引分组,level传入的方式可以是索引name,也可以是索引在多重索引中的下标,还可以是排除某个索引外的其他索引。指定多个时用列表的方式传入。
# 传递给by参数,结果一样
grouped = multi_grouped.groupby('sex')
print('-'*40, '\n', grouped.count(), sep='')
grouped = multi_grouped.groupby(['sex', 'age'])
print('-'*40, '\n', grouped.count(), sep='')
多重索引中的索引也可以传给groupby()的by参数,分组结果与将多重索引作为DataFrame的列是一样的。
如果用DataFrame的列作为分组列,多重索引会被转换成列保留在结果中。也可以用多重索引中的索引与列一起组合分组,相当于先对DataFrame重设索引再分组。
分组结果的索引默认是分组列的值,将as_index设置为False可以重置索引,相当于先分组再调用reset_index()函数。
提取分组结果的指定列
print('-'*40)
grouped = df.groupby('sex')
for name, group in grouped['name']:print(name)print(group)
# ----------------------------------------
# 女
# 3 小张
# Name: name, dtype: object
# 男
# 0 张三
# 1 李四
# 2 小明
# Name: name, dtype: objectprint('-'*40)
grouped = df['name'].groupby(df['sex'])
for name, group in grouped:print(name)print(group)
# ----------------------------------------
# 女
# 3 小张
# Name: name, dtype: object
# 男
# 0 张三
# 1 李四
# 2 小明
# Name: name, dtype: object
从groupby()分组结果中取一列,得到的是一个SeriesGroupBy对象,直接打印SeriesGroupBy对象只能看到它的内存地址,看不到内部的结构。
SeriesGroupBy的内部结构与DataFrameGroupBy相似,SeriesGroupBy对象的内部结构为:[(分组名1, 子Series1), (分组名2, 子Series2), …],因此SeriesGroupBy也可以转换成list打印,也可以遍历取出每一个元素。
也可以先指定需要获取的列,再按DataFrame的另一个列进行分组,结果与先分组再获取指定列相同。
agg()参数和用法介绍
agg(self, func=None, axis=0, *args, **kwargs)
:
func: 用于聚合数据的函数,如max()、mean()、count()等,函数必须满足传入一个DataFrame能正常使用,或传递到DataFrame.apply()中能正常使用。
func参数可以接收函数的名字、函数名的字符串、函数组成的列表、行/列标签和函数组成的字典。
axis: 设置按列还是按行聚合。设置为0或index,表示对每列应用聚合函数,设置为1或columns,表示对每行应用聚合函数。
*args: 传递给函数func的位置参数。
**kwargs: 传递给函数func的关键字参数。
返回的数据分为三种:scalar(标量)、Series或DataFrame。
scalar: 当Series.agg()聚合单个函数时返回标量。
Series: 当DataFrame.agg()聚合单个函数时,或Series.agg()聚合多个函数时返回Series。
DataFrame: 当DataFrame.agg()聚合多个函数时返回DataFrame。
传入单个参数
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6],'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},index=['A', 'B', 'C'])
print(df)
# Col-1 Col-2 Col-3 Col-4
# A 1 2 9 3
# B 3 4 8 6
# C 5 6 7 9res1 = df.agg(np.mean)
print('-' * 30, '\n', res1, sep='')
# ------------------------------
# Col-1 3.0
# Col-2 4.0
# Col-3 8.0
# Col-4 6.0
# dtype: float64res2 = df.mean() # 调用Python内置函数
print('-' * 30, '\n', res2, sep='')
# ------------------------------
# Col-1 3.0
# Col-2 4.0
# Col-3 8.0
# Col-4 6.0
# dtype: float64res3 = df['Col-1'].agg(np.mean)
print('-' * 30, '\n', res3, sep='')
# ------------------------------
# 3.0
DataFrame应用单个函数时,agg()的结果与用apply()的结果等效,用DataFrame调用Python的内置函数也可以实现相同效果。
Series对象在agg()中传入单个函数,聚合结果为标量值,也就是单个数据。
多种方式传入函数func
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6],'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},index=['A', 'B', 'C'])
print(df)
# Col-1 Col-2 Col-3 Col-4
# A 1 2 9 3
# B 3 4 8 6
# C 5 6 7 9# 用列表的方式传入
res4 = df.agg([np.mean, np.max, np.sum])
print('-' * 30, '\n', res4, sep='')
# ------------------------------
# Col-1 Col-2 Col-3 Col-4
# mean 3.0 4.0 8.0 6.0
# amax 5.0 6.0 9.0 9.0
# sum 9.0 12.0 24.0 18.0# 用字典的方式传入
res5 = df.agg({'Col-1': [sum, max], 'Col-2': [sum, min], 'Col-3': [max, min]})
print('-' * 30, '\n', res5, sep='')
# ------------------------------
# Col-1 Col-2 Col-3
# sum 9.0 12.0 NaN
# max 5.0 NaN 9.0
# min NaN 2.0 7.0# 函数名用字符串的方式传入
res6 = df.agg({'Col-1': ['sum', 'max'], 'Col-2': ['sum', 'min'], 'Col-3': ['max', 'min']})
print('-' * 30, '\n', res6, sep='')
# ------------------------------
# Col-1 Col-2 Col-3
# sum 9.0 12.0 NaN
# max 5.0 NaN 9.0
# min NaN 2.0 7.0
在agg()中,可以用列表的方式传入多个函数,会将这些函数在每一列的执行结果聚合到一个DataFrame中,结果DataFrame中的索引为对应的函数名。
也可以用字典的方式按列/行指定聚合函数,会将指定列/行与对应函数的执行结果聚合到一个DataFrame中,列/行和函数没有对应关系的位置填充空值。
在上面的情况中,函数名都可以换成用字符串的方式传入,结果一样。
# 用元组的方式按列/行传入函数
res7 = df.agg(X=('Col-1', 'sum'), Y=('Col-2', 'max'), Z=('Col-3', 'min'),)
print('-' * 30, '\n', res7, sep='')
res8 = df.agg(X=('Col-1', 'sum'), Y=('Col-2', 'max'), Zmin=('Col-3', 'min'), Zmax=('Col-3', 'max'))
print('-' * 30, '\n', res8, sep='')
# ------------------------------
# Col-1 Col-2 Col-3
# X 9.0 NaN NaN
# Y NaN 6.0 NaN
# Z NaN NaN 7.0
# ------------------------------
# Col-1 Col-2 Col-3
# X 9.0 NaN NaN
# Y NaN 6.0 NaN
# Zmin NaN NaN 7.0
# Zmax NaN NaN 9.0
agg()还支持将不同的列/行和函数组合成元组,赋值给一个自定义的索引名,聚合结果DataFrame的索引按自定义的值重命名。
用这种方式传入函数时,元组中只能有两个元素:列/行名和一个函数,不能同时传入多个函数,如果要对同一列/行执行多个函数,需要用多个元组多次赋值。
传入自定义函数和匿名函数
def fake_mean(s):return (s.max()+s.min())/2res9 = df.agg([fake_mean, lambda x: x.mean()])
print('-' * 40, '\n', res9, sep='')
res10 = df.agg([fake_mean, lambda x: x.max(), lambda x: x.min()])
print('-' * 40, '\n', res10, sep='')
# ----------------------------------------
# Col-1 Col-2 Col-3 Col-4
# fake_mean 3.0 4.0 8.0 6.0
# <lambda> 3.0 4.0 8.0 6.0
# ----------------------------------------
# Col-1 Col-2 Col-3 Col-4
# fake_mean 3.0 4.0 8.0 6.0
# <lambda> 5.0 6.0 9.0 9.0
# <lambda> 1.0 2.0 7.0 3.0
传入自定义函数和匿名函数时,聚合结果中对应的索引也是显示函数名字,匿名函数显示<lambda>
,有多个匿名函数时,同时显示<lambda>
。
这里需要注意,只有匿名函数可以传入重复的函数,自定义函数和内置函数等不能重复,会报错SpecificationError: Function names must be unique if there is no new column names assigned。
自定义实现describe函数的效果
import pandas as pd
import numpy as np
from functools import partialdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6],'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},index=['A', 'B', 'C'])print(df.describe())# 20%分为数
per_20 = partial(pd.Series.quantile, q=0.2)
per_20.__name__ = '20%'
# 80%分为数
per_80 = partial(pd.Series.quantile, q=0.8)
per_80.__name__ = '80%'
res11 = df.agg([np.min, per_20, np.median, per_80, np.max])
print('-' * 40, '\n', res11, sep='')
Col-1 Col-2 Col-3 Col-4
count 3.0 3.0 3.0 3.0
mean 3.0 4.0 8.0 6.0
std 2.0 2.0 1.0 3.0
min 1.0 2.0 7.0 3.0
25% 2.0 3.0 7.5 4.5
50% 3.0 4.0 8.0 6.0
75% 4.0 5.0 8.5 7.5
max 5.0 6.0 9.0 9.0
----------------------------------------Col-1 Col-2 Col-3 Col-4
amin 1.0 2.0 7.0 3.0
20% 1.8 2.8 7.4 4.2
median 3.0 4.0 8.0 6.0
80% 4.2 5.2 8.6 7.8
amax 5.0 6.0 9.0 9.0
describe()函数包含了数值个数、均值、标准差、最小值、1/4分位数、中位数、3/4分位数、最大值。
用agg()函数可以聚合实现describe()相同的效果,只要将函数组合在一起传给agg()即可。所以我们可以根据自己的需要来增加或裁剪describe()中的内容。
上面的例子中,pd.Series.quantile()是pandas中求分位数的函数,默认是求中位数,指定q参数可以计算不同的分位数。
partial()是Python的functools内置库中的函数,作用是给传入它的函数固定参数值,如上面分别固定quantile()的q参数为0.2/0.8。
分组聚合结合使用
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6],'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},index=['A', 'B', 'C'])# 先用groupby()分组再用agg()聚合
res12 = df.groupby('Col-1').agg([np.min, np.max])
print('-' * 40, '\n', res12, sep='')
# 分组后只聚合某一列
res13 = df.groupby('Col-1').agg({'Col-2': [np.min, np.mean, np.max]})
print('-' * 40, '\n', res13, sep='')# ----------------------------------------
# Col-2 Col-3 Col-4
# amin amax amin amax amin amax
# Col-1
# 1 2 2 9 9 3 3
# 3 4 4 8 8 6 6
# 5 6 6 7 7 9 9
# ----------------------------------------
# Col-2
# amin mean amax
# Col-1
# 1 2 2.0 2
# 3 4 4.0 4
# 5 6 6.0 6
agg()经常接在分组函数groupby()的后面使用,先分组再聚合,分组之后可以对所有组聚合,也可以只聚合需要聚合的组。
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6],'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},index=['A', 'B', 'C'])res14 = df.groupby('Col-1').agg(c2_min=pd.NamedAgg(column='Col-2', aggfunc='min'),c3_min=pd.NamedAgg(column='Col-3', aggfunc='min'),c2_sum=pd.NamedAgg(column='Col-2', aggfunc='sum'),c3_sum=pd.NamedAgg(column='Col-3', aggfunc='sum'),c4_sum=pd.NamedAgg(column='Col-4', aggfunc='sum')
)
print('-' * 40, '\n', res14, sep='')# ----------------------------------------
# c2_min c3_min c2_sum c3_sum c4_sum
# Col-1
# 1 2 9 2 9 3
# 3 4 8 4 8 6
# 5 6 7 6 7 9
pd.NamedAgg可以对聚合进行更精准的定义,它包含column和aggfunc两个定制化的字段,column设置用于聚合的列,aggfunc设置用于聚合的函数。
借助pd.NamedAgg,可以给column和aggfunc的组合自定义命名,自定义命名体现为聚合结果中的列名。
转换函数transform
transform()是pandas中的转换函数,对DataFrame执行传入的函数后返回一个相同形状的DataFrame。
transform()参数和用法介绍
transform(func, axis=0, *args, **kwargs)
:
func: 用于转换数据的函数,函数必须满足传入一个DataFrame能正常使用,或传递到DataFrame.apply()中能正常使用。
func可以接收函数的名字、函数名的字符串、函数组成的列表、行/列标签和函数名组成的字典。
axis: 设置按列还是按行转换。设置为0或index,表示对每列应用转换函数,设置为1或columns,表示对每行应用转换函数。
args: 传递给函数func的位置参数。
kwargs: 传递给函数func的关键字参数。
返回数据:
返回的结果是一个与自身形状相同的DataFrame。
transform()传入单个函数
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [5, 7, 9]})
print(df)
# Col-1 Col-2
# 0 1 5
# 1 3 7
# 2 5 9res1 = df.transform(np.square)
print(res1)
# Col-1 Col-2
# 0 1 25
# 1 9 49
# 2 25 81res2 = df.transform('sqrt')
print(res2)
# Col-1 Col-2
# 0 1.000000 2.236068
# 1 1.732051 2.645751
# 2 2.236068 3.000000res3 = df.transform(lambda x: x*10)
print(res3)
# Col-1 Col-2
# 0 10 50
# 1 30 70
# 2 50 90
在transform()中传入单个函数进行转换,transform()的结果与apply()/applymap()等效。
函数可以是库函数、自定义函数或匿名函数。因为transform()的返回结果与自身形状相同,所以不支持直接传入会将DataFrame“降维”的函数,如会将Series处理成标量的聚合函数min,mean,std等。传入这些函数时,会报错:ValueError: Function did not transform.
虽然transform()是按行/列来处理数据,但它对数据的处理有点像元素级的处理。上面这种传入单个函数对DataFrame做行列级处理的情况,更推荐使用apply()。
transform()传入多个函数
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [5, 7, 9]})
print(df)
# Col-1 Col-2
# 0 1 5
# 1 3 7
# 2 5 9res5 = df.transform([np.square, np.sqrt])
print(res5)
# Col-1 Col-2
# square sqrt square sqrt
# 0 1 1.000000 25 2.236068
# 1 9 1.732051 49 2.645751
# 2 25 2.236068 81 3.000000
# 传入相同名字的函数时,只有最后一个函数生效
res6 = df.transform([np.abs, lambda x: x+10, lambda x:np.square(x)])
print(res6)
# Col-1 Col-2
# absolute <lambda> absolute <lambda>
# 0 1 1 5 25
# 1 3 9 7 49
# 2 5 25 9 81
在transform()中传入多个函数对DataFrame进行转换,结果中的索引变成多层索引,第一层索引是DataFrame的列名,第二层索引是执行的函数名。按第一层索引来比较,DataFrame的形状并没有变化。
在传入多个函数时,传入的方式是用列表传入,此时如果有多个名字相同的函数,只有最后一个函数生效。(聚合函数agg()中多个匿名函数可以同时生效)
按字典的方式传入函数
import pandas as pd
import numpy as npdf = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [5, 7, 9]})
print(df)
# Col-1 Col-2
# 0 1 5
# 1 3 7
# 2 5 9res7 = df.transform({'Col-1': np.square, 'Col-2': np.sqrt})
print(res7)
# Col-1 Col-2
# 0 1 2.236068
# 1 9 2.645751
# 2 25 3.000000res8 = df.transform({'Col-1': [np.square, lambda x: x*10], 'Col-2': np.sqrt})
print(res8)
# Col-1 Col-2
# square <lambda> sqrt
# 0 1 10 2.236068
# 1 9 30 2.645751
# 2 25 50 3.000000
在transform()中可以用字典的方式给不同的列传入不同的函数,如果字典中的value有列表,则结果的索引变成多层索引。
transform()对Series进行转换
import pandas as pd
import numpy as nps = pd.Series(range(3))
print(s)
# 0 0
# 1 1
# 2 2
# dtype: int64res9 = s.transform(lambda x: x+10)
print(res9)
print(res9.shape)
# 0 10
# 1 11
# 2 12
# dtype: int64
# (3,)print('-' * 20)
res10 = s.transform([np.square, lambda x: x*x])
print(res10)
print(res10.shape)
print(type(res10))
# --------------------
# square <lambda>
# 0 0 0
# 1 1 1
# 2 4 4
# (3, 2)
# <class 'pandas.core.frame.DataFrame'>
transform()传入单个函数对Series转换时,结果是Series,传入多个函数对Series转换时,结果是DataFrame,列名是调用的函数名。
在这种情况下,Series的形状发生了变化。(也可以理解成结果是多层索引,第一层是Series的列索引,第二层是函数名,只是Series的列索引没有显示。)
与groupby()配合使用
import pandas as pd
import numpy as npscore_df = pd.DataFrame({'姓名': ['Tom', 'Tom', 'Tom', 'Andy', 'Andy', 'Andy', 'Andy', 'Kitty', 'Kitty', 'Kitty'],'课程': ['高数', '英语', '体育', '高数', '英语', '体育', '计算机', '高数', '英语', '体育'],'成绩': [60, 90, 80, 90, 80, 70, 90, 70, 90, 80],'评级': ['C', 'A', 'B', 'A', 'B', 'C', 'A', 'C', 'A', 'B']}
)
print(score_df)
# 姓名 课程 成绩 评级
# 0 Tom 高数 60 C
# 1 Tom 英语 90 A
# 2 Tom 体育 80 B
# 3 Andy 高数 90 A
# 4 Andy 英语 80 B
# 5 Andy 体育 70 C
# 6 Andy 计算机 90 A
# 7 Kitty 高数 70 C
# 8 Kitty 英语 90 A
# 9 Kitty 体育 80 Bresult = score_df.groupby('姓名')['成绩'].transform(np.sum)
print('-'*30, '\n', result, sep='')
# ------------------------------
# 0 230
# 1 230
# 2 230
# 3 330
# 4 330
# 5 330
# 6 330
# 7 240
# 8 240
# 9 240
# Name: 成绩, dtype: int64result = score_df.groupby('姓名')['成绩'].agg(np.sum)
print('-'*30, '\n', result, sep='')
# ------------------------------
# 姓名
# Andy 330
# Kitty 240
# Tom 230
# Name: 成绩, dtype: int64
在与分组函数groupby()配合使用时,transform()转换的结果与agg()聚合的结果不一样,transform()会保持每一个分组的形状与原始数据形状相同,而agg()会将每个分组的结果聚合成一个标量值。
这是transform()和agg()最主要的功能差异,也是最有用的一点,在适合的场景里非常有用。
计算每个人的成绩与同一门课程平均成绩的差。
import pandas as pd
import numpy as npscore_df = pd.DataFrame({'姓名': ['Tom', 'Tom', 'Tom', 'Andy', 'Andy', 'Andy', 'Andy', 'Kitty', 'Kitty', 'Kitty'],'课程': ['高数', '英语', '体育', '高数', '英语', '体育', '计算机', '高数', '英语', '体育'],'成绩': [60, 90, 80, 90, 80, 70, 90, 70, 90, 80],'评级': ['C', 'A', 'B', 'A', 'B', 'C', 'A', 'C', 'A', 'B']}
)# 第一种方法:分两步,先求得平均成绩,再相减
score_df['平均成绩'] = score_df.groupby('课程')['成绩'].transform(np.mean)
print('-'*30, '\n', score_df, sep='')
score_df['成绩差异'] = score_df['成绩'] - score_df['平均成绩']
print('-'*30, '\n', score_df, sep='')# 第二种方法:用匿名函数一步到位
score_df['成绩差异'] = score_df.groupby('课程')['成绩'].transform(lambda x: x - x.mean())
print('-'*30, '\n', score_df, sep='')
# ------------------------------
# 姓名 课程 成绩 评级 平均成绩 成绩差异
# 0 Tom 高数 60 C 73.333333 -13.333333
# 1 Tom 英语 90 A 86.666667 3.333333
# 2 Tom 体育 80 B 76.666667 3.333333
# 3 Andy 高数 90 A 73.333333 16.666667
# 4 Andy 英语 80 B 86.666667 -6.666667
# 5 Andy 体育 70 C 76.666667 -6.666667
# 6 Andy 计算机 90 A 90.000000 0.000000
# 7 Kitty 高数 70 C 73.333333 -3.333333
# 8 Kitty 英语 90 A 86.666667 3.333333
# 9 Kitty 体育 80 B 76.666667 3.333333
第一步,使用groupby()函数按课程分组,然后使用transform()计算出每门课程成绩的平均值。这里transform()会保证结果的形状与原来相同,极大地方便了下一步计算差值。
第二步,用成绩减平均成绩,就可以得到成绩与此门课程平均成绩的差。
实际使用时,这两个步骤可以直接合并成一步,因为“平均成绩”只是用于计算差值的中间值,不需要保存,直接一步计算出差值即可。
填充缺失值,用每门课程的平均值填充第四位同学的成绩。
import pandas as pd
import numpy as npscore_df = pd.DataFrame({'姓名': ['Tom', 'Tom', 'Tom', 'Andy', 'Andy', 'Andy', 'Andy', 'Kitty', 'Kitty', 'Kitty'],'课程': ['高数', '英语', '体育', '高数', '英语', '体育', '计算机', '高数', '英语', '体育'],'成绩': [60, 90, 80, 90, 80, 70, 90, 70, 90, 80],'评级': ['C', 'A', 'B', 'A', 'B', 'C', 'A', 'C', 'A', 'B']}
)score_truman = pd.DataFrame({'姓名': ['Truman', 'Truman', 'Truman'],'课程': ['高数', '英语', '体育'],'成绩': [np.NAN, np.NAN, np.NAN],'评级': [np.NAN, np.NAN, np.NAN]}
)
score_df = pd.concat([score_df, score_truman]).reset_index()
print('-'*30, '\n', score_df, sep='')
# ------------------------------
# index 姓名 课程 成绩 评级
# 0 0 Tom 高数 60.0 C
# 1 1 Tom 英语 90.0 A
# 2 2 Tom 体育 80.0 B
# 3 3 Andy 高数 90.0 A
# 4 4 Andy 英语 80.0 B
# 5 5 Andy 体育 70.0 C
# 6 6 Andy 计算机 90.0 A
# 7 7 Kitty 高数 70.0 C
# 8 8 Kitty 英语 90.0 A
# 9 9 Kitty 体育 80.0 B
# 10 0 Truman 高数 NaN NaN
# 11 1 Truman 英语 NaN NaN
# 12 2 Truman 体育 NaN NaNscore_df['成绩'] = score_df.groupby('课程')['成绩'].transform(lambda x: x.fillna(x.mean()))
print('-'*30, '\n', score_df, sep='')
# ------------------------------
# index 姓名 课程 成绩 评级
# 0 0 Tom 高数 60.000000 C
# 1 1 Tom 英语 90.000000 A
# 2 2 Tom 体育 80.000000 B
# 3 3 Andy 高数 90.000000 A
# 4 4 Andy 英语 80.000000 B
# 5 5 Andy 体育 70.000000 C
# 6 6 Andy 计算机 90.000000 A
# 7 7 Kitty 高数 70.000000 C
# 8 8 Kitty 英语 90.000000 A
# 9 9 Kitty 体育 80.000000 B
# 10 0 Truman 高数 73.333333 NaN
# 11 1 Truman 英语 86.666667 NaN
# 12 2 Truman 体育 76.666667 NaN
用平均值来填充同一组数据中的空值,这是数据处理时非常常用的方式,借用transform()可以一步完成此功能。
这篇关于Pandas分组函数groupby、聚合函数agg和转换函数transform的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!