pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要
等操作。根据一个或多个键(可以是函数、数组或DataFrame列名)拆分pandas对象。计算分组摘要统
计,如计数、平均值、标准差,或用户自定义函数。对DataFrame的列应用各种各样的函数。应用组内转换
或其他运算,如规格化、线性回归、排名或选取子集等。计算透视表或交叉表。执行分位数分析以及其他分
组分析。
groupby()是一个分组函数,对数据进行分组操作的过程可以概括为:split-apply-combine三步:
- 按照键值(key)或者分组变量将数据分组。
- 对于每组应用我们的函数,这一步非常灵活,可以是python自带函数,可以是我们自己编写的函数。
- 将函数计算后的结果聚合。
返回值:返回重构格式的DataFrame,特别注意,groupby里面的字段内的数据重构后都会变成索引
groupby(),一般和sum()、mean()一起使用,如下例:
官网:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html
groupby分组函数:
DataFrame.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, **kwargs)
by : 接收映射、函数、标签或标签列表;用于确定聚合的组
axis : 接收 0/1;用于表示沿行(0)或列(1)分割。
level : 接收int、级别名称或序列,默认为None;如果轴是一个多索引(层次化),则按一个或多个特定级别分组
as_index : 接收布尔值,默认Ture;Ture则返回以组标签为索引的对象,False则不以组标签为索引
基本操作
在进行对groupby函数进行学习之前,首先需要明确的是,通过对DataFrame对象调用groupby()函数返回的结果是一个DataFrameGroupBy对象,而不是一个DataFrame或者Series对象,所以,它们中的一些方法或者函数是无法直接调用的,需要按照GroupBy对象中具有的函数和方法进行调用。
import pandas as pd
import numpy as np
df = pd.DataFrame({'key1':list('aabba'),
'key2': ['one','two','one','two','one'],
'data1': [8,6,2,4,3],
'data2': [6,9,5,2,-7]})
key1 key2 data1 data2
0 a one 8 6
1 a two 6 9
2 b one 2 5
3 b two 4 2
4 a one 3 -7
grouped = df.groupby('key2')
print(type(grouped))
print(grouped)
#输出结果如下:
<class 'pandas.core.groupby.generic.DataFrameGroupBy'>
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000292E0778B50>
普通分组,单值分组
按key1分组并求均值
print(df.groupby('key1').mean(),'
')
data1 data2
key1
a 5.666667 2.666667
b 3.000000 3.500000
print(df.groupby('key1',as_index=False).mean(),'
')
#注意使用as_index=False和不使用的区别
key1 data1 data2
0 a 5.666667 2.666667
1 b 3.000000 3.500000
#功能与上一句相同:print(df.groupby(['key1'],as_index=False).mean(),'
')
普通分组,多值分组
按states,years分组并求均值
states=np.array(['Ohio','California','California','Ohio','Ohio'])
years=np.array([2005,2005,2006,2005,2006])
print(df['data1'].groupby([states,years]).mean(),'
')
#输出结果如下:
California 2005 6
2006 2
Ohio 2005 6
2006 3
指定多个列名个单个列名后的区别在于,分组的主键或者索引(indice)将一个是单个主键,另一个则是一个元组的形式:
grouped = df.groupby('key1')
grouped_muti = df.groupby([states,years])
print(grouped.size())
key1
a 3
b 2
dtype: int64
print(grouped_muti.size())
California 2005 1
2006 1
Ohio 2005 2
2006 1
dtype: int64
print(grouped.get_group('a'))
key1 key2 data1 data2
0 a one 8 6
1 a two 6 9
4 a one 3 -7
print(grouped_muti.get_group(('Ohio', 2005)))
key1 key2 data1 data2
0 a one 8 6
3 b two 4 2
使用Series和字典作为分组
除了使用上述List类型的数据作为分组依据,还可以使用Series和字典作为分组依据。下面仅以字典类型为例:
import pandas as pd
import numpy as np
data=pd.DataFrame(np.arange(20).reshape(4,5),index=list('1234'),columns=list('12345'))
by_dict={'1':'red','2':'yellow','3':'yellow','4':'black','5':'white'}
by_dict1={'1':'red','2':'yellow','3':'yellow','5':'white'}
data_1=data.groupby(by_dict)
print("按by_dict分组的结果:")
for key,group in data_1:
print(key)
print(group)
data_2=data.groupby(by_dict1)
print("按by_dict1分组的结果:")
data_3=data.groupby(by_dict1)
for key,group in data_3:
print(key)
print(group)
注意:
使用字典或Series作为依据对数据进行分组时,如果行索引或列索引在分组依据(代码中的by_dict和by_dict1变量)中并没有找到对应关系,则对应的行或列是不参与最终的分组的(不是自成一组,可以从by_dict1分组的结果中看出此结论)。
分组依据中可以出现行索引或列索引中没有出现的值。比如by_dict1中的5
使用Series和字典时,可以设置axis参数。
grouped的函数操作
通过调用get_group()函数可以返回一个按照分组得到的DataFrame对象,所以可以将DataFrameGroupBy对象理解为是多个DataFrame组成的。
而没有调用get_group()函数之前,此时的数据结构任然是DataFrameGroupBy,此时进行对DataFrameGroupBy按照列名进行索引,
就可以得到SeriesGroupBy对象,取多个列名,则得到的任然是DataFrameGroupBy对象,这里可以类比DataFrame和Series的关系。
#A single group can be selected using get_group():
grouped.get_group("bar")
#Out:
A B C D
1 bar one 0.254161 1.511763
3 bar three 0.215897 -0.990582
5 bar two -0.077118 1.211526
Or for an object grouped on multiple columns:
#for an object grouped on multiple columns:
df.groupby(["A", "B"]).get_group(("bar", "one"))
因此,在没有进行调用get_group(),也就是没有取出特定某一组数据之前,此时的数据结构任然是DataFrameGroupBy,其中也有很多函数和方法可以调用,
如max()、count()、std()等,返回的结果是一个DataFrame对象。
调用get_group()函数后得到了Series的对象,下面的操作就可以按照Series对象中的函数行了。
print(grouped.count())
print(grouped.max()[['Age', 'Score']])
print(grouped.mean()[['Age', 'Score']])
如果其中的函数无法满足需求,也可以选择使用聚合函数aggregate,传递numpy或者自定义的函数,前提是返回一个聚合值。
关于使用自定义对数据进行分组时,要注意以下两点:
- 除了自定义的函数,python中的内建函数,比如len等,也可以直接用来进行分组。(此处并没有举例)
- 这些函数不仅可以作用在索引列上,也可以自己指定作用列。
- 指定了自定义函数的作用列之后,作用列中的每个值都会传入到函数中执行。并根据其函数运行结果对原始数据进行分组。
具体可参考官网的例子:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html
gb = df.groupby("key1")
gb.<TAB> #(输入gb.后按Tab键,可以看到以下提示:)
gb.agg gb.boxplot gb.cummin gb.describe gb.filter gb.get_group gb.height gb.last gb.median gb.ngroups
gb.plot gb.rank gb.std gb.transform gb.aggregate gb.count gb.cumprod gb.dtype gb.first gb.groups
gb.hist gb.max gb.min gb.nth gb.prod gb.resample gb.sum gb.var gb.apply gb.cummax
gb.cumsum gb.fillna gb.gender gb.head gb.indices gb.mean gb.name gb.ohlc gb.quantile gb.size
gb.tail gb.weight
def getSum(data):
total = 0
for d in data:
total+=d
return total
print(grouped.aggregate(np.median))
print(grouped.aggregate({'Age':np.median, 'Score':np.sum}))
print(grouped.aggregate({'Age':getSum}))
aggregate函数不同于apply,前者是对所有的数值进行一个聚合的操作,而后者则是对每个数值进行单独的一个操作:
def addOne(data):
return data + 1
df['Age'] = df['Age'].apply(addOne)
df['Age'] = df['Age'].apply(int)
更复杂的agg方法 pd.NamedAgg
animals = pd.DataFrame({"kind": ["cat", "dog", "cat", "dog"],
"height": [9.1, 6.0, 9.5, 34.0],
"weight": [7.9, 7.5, 9.9, 198.0]})
grouped_agg = animals.groupby("kind").agg(min_height=pd.NamedAgg(column="height", aggfunc="min"),
max_height=pd.NamedAgg(column="height", aggfunc="max"),
average_weight=pd.NamedAgg(column="weight", aggfunc=np.mean))
对grouped里的元素进行遍历
for name, group in grouped:
print(name)
print(group)
通过循环,对value进行拼接。
# 循环拼接
for key, value in data_group:
new_data = pd.concat([new_data, value])
print(new_data)
在x,y轴上进行分组
Pandas中使用groupby时默认是在axis=0轴上进行分组的,也可以通过设置在axis=1轴上进行分组。
import pandas as pd
import numpy as np
def odd(num):
return int(num)%2==0
data=pd.DataFrame(np.arange(20).reshape(4,5),index=list('1234'),columns=list('12345'))
print("原始数据:")
print(data)
data_axis0=data.groupby(odd,axis=0)#默认依据index在odd上的运行结果进行分组
print("按axis=0进行分组结果如下:")
for key,group in data_axis0:
print(key)
print(group)
data_axis1=data.groupby(odd,axis=1)#默认依据column在odd上的运行结果进行分组
print("按axis=1进行分组结果如下:")
for key,group in data_axis1:
print(key)
print(group)
分组频率计数
使用nunique或value_counts方法获取Series的唯一计数值和计数频率。
df.groupby('year')['country'].nunique() #对df按'year'分组,并求出'country'列的唯一值的数目
案例应用
本例使用在openml.org网站上称为“ credit-g”的数据集。 该数据集由提出贷款申请的客户的许多功能和一个目标变量组成,该目标变量指示信贷是否还清。
可以在此处下载数据(https://www.openml.org/d/31),也可以使用Scikit-learn API导入数据,如下所示。
import pandas as pd
import numpy as np
from sklearn.datasets import fetch_openml
X,y = fetch_openml(name='credit-g', as_frame=True, return_X_y=True)
df = X
df['target'] = y
print(df)
#输出:
checking_status duration ... foreign_worker target
0 <0 6.0 ... yes good
1 0<=X<200 48.0 ... yes bad
2 no checking 12.0 ... yes good
3 <0 42.0 ... yes good
4 <0 24.0 ... yes bad
.. ... ... ... ... ...
995 no checking 12.0 ... yes good
996 <0 30.0 ... yes good
997 no checking 12.0 ... yes good
998 <0 45.0 ... yes bad
999 0<=X<200 45.0 ... yes good
将所有内容按工作类型分组并计算了所有数值变量的平均值。 输出显示在代码下方。
df.groupby(['job']).mean()
Out[1]:
duration ... num_dependents
job ...
unemp/unskilled non res 17.363636 ... 1.136364
unskilled resident 16.535000 ... 1.260000
skilled 21.411111 ... 1.125397
high qualif/self emp/mgmt 25.168919 ... 1.141892
想要更具体一些,可以取dataframe的一个子集,只计算特定列的统计信息。在下面的代码中,只选择credit_amount。
df[['job', 'credit_amount']].groupby(['job']).mean()
Out[2]:
credit_amount
job
unemp/unskilled non res 2745.136364
unskilled resident 2358.520000
skilled 3070.965079
high qualif/self emp/mgmt 5435.493243
也可以按多个变量分组。这里按工作和住房类型计算了平均信贷金额。
df[['job', 'housing','credit_amount']].groupby(['job', 'housing']).mean()
Out[3]:
credit_amount
job housing
unemp/unskilled non res rent 3110.600000
own 2739.538462
for free 2306.500000
unskilled resident rent 2376.947368
own 2289.376623
for free 3602.000000
skilled rent 3107.226087
own 2900.807522
for free 4225.587302
high qualif/self emp/mgmt rent 4558.523810
own 5139.436170
for free 6836.878788
多聚合
groupby后面使用agg函数能够计算变量的多个聚合。在下面的代码计算了每个作业组的最小和最大值。
df[['job', 'credit_amount']].groupby(['job']).agg([min, max])
Out[4]:
credit_amount
min max
job
unemp/unskilled non res 609.0 14555.0
unskilled resident 250.0 11998.0
skilled 338.0 15945.0
high qualif/self emp/mgmt 629.0 18424.0
也可以对不同的列使用不同的聚合。以下计算credit_amount的最小和最大金额以及每种工作类型的平均年龄。
df[['job', 'credit_amount', 'age']].groupby(['job']).agg(
{'credit_amount': ['min', 'max'], 'age': 'mean'})
Out[15]:
credit_amount age
min max mean
job
unemp/unskilled non res 609.0 14555.0 40.090909
unskilled resident 250.0 11998.0 36.540000
skilled 338.0 15945.0 34.253968
high qualif/self emp/mgmt 629.0 18424.0 39.027027
聚合命名
NamedAgg函数允许为多个聚合提供名称,从而提供更清晰的输出。
df[['target', 'credit_amount', 'age']].groupby('target').agg(
min_credit_amount=pd.NamedAgg('credit_amount', 'min'),
max_credit_amount=pd.NamedAgg('credit_amount', 'max'),
average_age=pd.NamedAgg('age', 'mean'))
Out[5]:
min_credit_amount max_credit_amount average_age
target
good 250.0 15857.0 36.224286
bad 433.0 18424.0 33.963333
自定义聚合
也可以将自定义功能应用于groupby对聚合进行自定义的扩展。例如,如果我们要计算每种工作类型的不良贷款的百分比,我们可以使用下面的代码。
job_count = df[['job', 'target']].groupby(['job', 'target']).agg({'target': 'count'})
job_percent = job_count.groupby(level=0).apply(lambda x:
100 * x / float(x.sum()))
job_percent
Out[13]:
target
job target
unemp/unskilled non res good 68.181818
bad 31.818182
unskilled resident good 72.000000
bad 28.000000
skilled good 70.476190
bad 29.523810
high qualif/self emp/mgmt good 65.540541
bad 34.459459
可视化绘图
我们可以将pandas 内置的绘图功能添加到GroupBy,以更好地可视化趋势和模式。
用上一节中创建的代码,以创建堆叠的条形图,以更好地可视化每种工作类型的好坏贷款的分布。
df.groupby(['job', 'target'])['job'].count().unstack('target').fillna(0).plot(kind='bar', stacked=True)
————————————————
参考文件:
https://blog.csdn.net/jingshuiliushen_zj/article/details/83211650
https://blog.csdn.net/m0_45210226/article/details/109406562
https://blog.csdn.net/FrankieHello/article/details/97272990
https://blog.csdn.net/brucewong0516/article/details/78768443
https://blog.csdn.net/yeshang_lady/article/details/102488971