分组模式及其对象
1. 分组的一般模式
模式 | 分类 | 例子 | 备注 |
---|---|---|---|
df.groupby(分组依据)[数据来源].使用操作 | 单一维度 | df.groupby('Gender')['Longevity'].mean() | 明确三个要素:、、 |
多维度 | df.groupby(['School', 'Gender'])['Height'].mean() | ||
复杂逻辑 | condition = df.Weight > df.Weight.mean()<br />df.groupby(condition)['Height'].mean() | ||
分组依据的本质:只需在 groupby 中传入相应列名构成的列表即可 |
自定义分组依据 | item = np.random.choice(list('abc'), df.shape[0])<br />df.groupby(item)['Height'].mean() | df里并没有item列 |
df.groupby([condition, item])['Height'].mean() | 传入两个分组依据,最后分组的一句就是这两个序列对应的唯一组合 |
【练一练】
请根据上下四分位数分割,将体重分为high、normal、low三组,统计身高的均值。
df['Wight2'] = df['Weight'].mask(df.Weight < df.Weight.quantile(0.25), 'low').mask((df.Weight >= df.Weight.quantile(0.25))&(df.Weight <= df.Weight.quantile(0.75)), 'normal').mask(df.Weight > df.Weight.quantile(0.75), 'high')
df.groupby('Wight2')['Height'].mean()
'''
Wight2
high 174.935714
low 153.753659
normal 161.883516
Name: Height, dtype: float64
'''
2. Groupby对象
gb = df.groupby(['School', 'Grade'])
属性,方法 | 例子 | 备注 |
---|---|---|
ngroups | gb.ngroups | 分组个数 |
groups | res = gb.groups<br />res.keys() | 可以返回从 组名 映射到 组索引列表 的字典 |
size | gb.size() | 在 groupby 对象上表示统计每个组的元素个数 |
get_group | gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3] | 可以直接获取所在组对应的行,此时必须知道组的具体名字 |
【练一练】
上一小节介绍了可以通过drop_duplicates
得到具体的组类别,现请用groups
属性完成类似的功能。
res = gb.groups
reslist= []
for element in res.keys():
reslist.append(list(element))
df2 = pd.DataFrame(data = reslist,
columns = ['school', 'grade'])
df2
'''
school grade
0 Fudan University Freshman
1 Fudan University Junior
2 Fudan University Senior
3 Fudan University Sophomore
4 Peking University Freshman
5 Peking University Junior
6 Peking University Senior
7 Peking University Sophomore
8 Shanghai Jiao Tong University Freshman
9 Shanghai Jiao Tong University Junior
10 Shanghai Jiao Tong University Senior
11 Shanghai Jiao Tong University Sophomore
12 Tsinghua University Freshman
13 Tsinghua University Junior
14 Tsinghua University Senior
15 Tsinghua University Sophomore
'''
分组的三大操作:聚合agg
1. 内置聚合函数
因为它的速度基本都会经过内部的优化,使用功能时应当优先考虑。
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
使用:df.groupby('Wight2')['Height'].mean()
练一练
请查阅文档,明确 all/any/mad/skew/sem/prod
函数的含义。
函数 | 参数 | 含义 |
---|---|---|
all | axis : {index (0), columns (1)} skipna : boolean, default True 排除NA/null值,如果一整行或一整列都是缺失值,则结果为NA level : int or level name, default None If the axis is a MultiIndex (hierarchical), count along a particular level, collapsing into a Series bool_only : boolean, default None 只包括布尔型的列。如果没有,会尝试使用所有(?),然后会只使用布尔类型的数据。系列值没有这个选项 |
如果这个组所有值都是True,则为true,否则为false |
any | axis : {index (0), columns (1)} skipna : boolean, default True 排除NA/null值,如果一整行或一整列都是缺失值,则结果为NA level : int or level name, default None If the axis is a MultiIndex (hierarchical), count along a particular level, collapsing into a Series bool_only : boolean, default None 只包括布尔型的列。如果没有,会尝试使用所有(?),然后会只使用布尔类型的数据。系列值没有这个选项 |
如果这个组有任意值是True,则为true,否则为false |
mad | axis : {index (0), columns (1)} skipna : boolean, default True 排除NA/null值,如果一整行或一整列都是缺失值,则结果为NA level : int or level name, default None 如果轴是多索引的,计算特定的层级 numeric_only : boolean, default None Include only float, int, boolean columns. If None, will attempt to use everything, then use only numeric data. Not implemented for Series.只包括浮点型,布尔型,整型的列。如果没有,会尝试使用所有(?),然后会只使用数值类型的数据。系列值没有这个选项 |
返回平均绝对离差 |
skew | axis : {index (0), columns (1)} skipna : boolean, default True 排除NA/null值,如果一整行或一整列都是缺失值,则结果为NA level : int or level name, default None 如果轴是多索引的,计算特定的层级 numeric_only : boolean, default None 只包括浮点型,布尔型,整型的列。如果没有,会尝试使用所有(?),然后会只使用数值类型的数据。系列值没有这个选项 |
返回组的样本偏度 |
sem | ddof : integer,默认值为1,表示自由度 | 返回标准误差,不包含缺失值 |
prod | 每个组所有数的乘积 |
2. agg方法
可以解决内置聚合函数的4个问题:
问题 | 例子 | 备注 |
---|---|---|
同时使用多个函数 | gb.(['sum', 'idxmax', 'skew']) | 用列表的形式把内置聚合函数对应的字符串传入 |
对特定的列使用特定的聚合函数 | gb.({'Height':['mean','max'], 'Weight':'count'}) | 通过构造字典传入 agg 中实现,其中字典以列名为键,以聚合字符串或字符串列表为值。 |
使用自定义的聚合函数 | gb.(lambda x: x.mean()-x.min()) | 方法1;需要注意传入函数的参数是之前数据源中的列,逐列进行计算 |
def my_func(s): <br /> res = 'High' <br /> if s.mean() <= df[s.name].mean(): <br /> res = 'Low' <br /> return res<br />gb.(my_func) | 方法2 | |
直接对结果的列名在聚合前进行自定义命名 | gb.([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])<br />gb.({'Height': [('my_func', my_func), 'sum'], 'Weight': lambda x:x.max()}) | 将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数;<br />另外需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串 |
练一练
请使用传入字典的方法完成gb.agg(['sum', 'idxmax', 'skew'])等价的聚合任务。
gb.agg({'Height':['sum', 'idxmax', 'skew'], 'Weight':['sum', 'idxmax', 'skew']})
在 groupby
对象中可以使用 describe
方法进行统计信息汇总,请同时使用多个聚合函数,完成与该方法相同的功能。
gb.agg(['count','mean','std','min',('25%', lambda x: x.quantile(0.25)),('50%', lambda x: x.quantile(0.25)),('75%', lambda x: x.quantile(0.25)),'max'])
分组的三大操作:变换
1.内置变换函数:
累计函数cumcount/cumsum/cumprod/cummax/cummin
练一练
在 groupby
对象中, rank
方法也是一个实用的变换函数,请查阅它的功能并给出一个使用的例子。
功能:计算沿轴的数值数据等级(1到n)。相等的值被分配一个等级,这个等级是这些值等级的平均值(默认)
参数:
axis : {0 or ‘index’, 1 or ‘columns’}, default 0
method : {‘average’, ‘min’, ‘max’, ‘first’, ‘dense’}相同值以什么方法取它的等级
dense: 类似最小值,但排名在组之间以1为步长增加
-
numeric_only : boolean, default None
只包括布尔值,浮点型和整型数据
-
na_option : {‘keep’, ‘top’, ‘bottom’}
- keep: 保留缺失值
- top: 如果升序则缺失值是最小值
- bottom: 如果降序则缺失值是最小值
ascending : boolean, default True是否升序
pct : boolean, default False计算数据的百分比排名
gb.rank(method="max") #将Height和Weight分别取它的排名
'''
Height Weight
0 59.0 50.0
1 5.0 21.0
2 50.0 54.0
3 NaN 16.0
4 27.0 33.0
... ... ...
195 22.0 50.0
196 83.0 86.0
197 22.0 44.0
198 31.0 24.0
199 1.0 1.0
'''
2.自定义变换:transform 方法
被调用的自定义函数, 其传入值为数据源的序列 ,与 agg
的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的 DataFrame
。
#1. 传入lambda或函数
gb.transform(lambda x: (x-x.mean())/x.std()).head()
#2. 传入标量
gb.transform('mean').head()
练一练
对于 transform
方法无法像 agg
一样,通过传入字典来对指定列使用特定的变换,如果需要在一次 transform
的调用中实现这种功能,请给出解决方案。
def test(x):
if x.name == 'Height':
return np.sum(x)
if x.name =='Weight':
return np.std(x)
df.groupby('Gender')['Height','Weight'].transform(test)
分组的三大操作:过滤
gb.filter(lambda x: x.shape[0] > 100).head()
练一练
从概念上说,索引功能是组过滤功能的子集,请使用
filter
函数完成loc[.]
的功能,这里假设 ”.
“是元素列表。
def test2(x, listA):
result = True
for i in listA:
if i not in x['Height'].values:
result = False
return result
gb.filter(lambda x:test2(x,[158.9,162.5]))
四、跨列分组apply
传入:和filter相同
输出:3种情况:
输出 | 例子 | 索引情况 |
---|---|---|
标量 | def BMI(x): Height = x['Height']/100 Weight = x['Weight'] BMI_value = Weight/Height**2 return BMI_value.mean() gb.apply(BMI)<br />df.groupby(['Gender','Test_Number'])[['Height','Weight']]<br />gb.apply(lambda x: 0) |
结果得到的是 Series ,索引与 agg 的结果一致 |
一维Series
|
gb.apply(lambda x: pd.Series([0,0],index=['a','b'])) | 行索引与标量情况一致,列索引为 Series 的索引 |
二维 DataFrame
|
练一练
- 请尝试在
apply
传入的自定义函数中,根据组的某些特征返回相同长度但索引不同的Series
,会报错吗?
会。。不知道是不是这样:
gb.apply(lambda x: pd.Series([0,0],index=['a',np.random.choice(list('qwertyu'))]))
报的错是索引名称需要是可以hash的类型(Series.name must be a hashable type)
- 请尝试在
apply
传入的自定义函数中,根据组的某些特征返回相同大小但列索引不同的DataFrame
,会报错吗?如果只是行索引不同,会报错吗?
不报错
列索引不同:
gb.apply(lambda x: pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('w',np.random.choice(list('xabcd'))),('y','z')])))
<img src="typora-user-images/image-20201225223748508.png" alt="image-20201225223748508" style="zoom:50%;" />
行索引不同:
gb.apply(lambda x: pd.DataFrame(np.ones((2,2)), index = [np.random.choice(list('abcd')),'b'], columns=pd.Index([('w','x'),('y','z')])))
<img src="typora-user-images/image-20201225223939672.png" alt="image-20201225223939672" style="zoom:50%;" />
- 在 groupby 对象中还定义了 cov 和 corr 函数,从概念上说也属于跨列的分组处理。请利用之前定义的 gb 对象,使用apply函数实现与 gb.cov() 同样的功能并比较它们的性能。
每次运行的时间不太相同,数据量小,时间差距不太大。
import datetime
beg_time = datetime.datetime.now()
gb.cov()
end_time = datetime.datetime.now()
print ("gb.cov():", end_time - beg_time)
beg_time = datetime.datetime.now()
gb.apply(lambda x:x.cov())
end_time = datetime.datetime.now()
print ("gb.apply(lambda x:x.cov()):", end_time - beg_time)
'''
gb.cov(): 0:00:00.009975
gb.apply(lambda x:x.cov()): 0:00:00.014961
'''
五、练习
Ex1:汽车数据集
现有一份汽车数据集,其中 Brand, Disp., HP
分别代表汽车品牌、发动机蓄量、发动机输出。
In [45]: df = pd.read_csv('data/car.csv')
In [46]: df.head(3)
Out[46]:
Brand Price Country Reliability Mileage Type Weight Disp. HP
0 Eagle Summit 4 8895 USA 4.0 33 Small 2560 97 113
1 Ford Escort 4 7402 USA 2.0 33 Small 2345 114 90
2 Ford Festiva 4 6319 Korea 4.0 37 Small 1845 81 63
- 先过滤出所属
Country
数超过2个的汽车,即若该汽车的Country
在总体数据集中出现次数不超过2则剔除,再按Country
分组计算价格均值、价格变异系数、该Country
的汽车数量,其中变异系数的计算方法是标准差除以均值,并在结果中把变异系数重命名为CoV
。
country = df['Country'].value_counts()
df2 = df.loc[df.Country.isin(country[country>2].index.tolist())] # index并不是列表,需要用tolist来转成list
def Cov_x(x):
return np.std(x)/np.mean(x)
df2.groupby('Country')['Price','Brand'].agg({'Price':['mean',('CoV',lambda x:Cov_x(x))], 'Brand':'count' })
#答案
df.groupby('Country').filter(lambda x:x.shape[0]>2).groupby('Country')['Price'].agg([('CoV', lambda x: x.std()/x.mean()), 'mean', 'count'])
- 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计
Price
的均值。
df['position'] = df.index+1
df['position'] = df['position'].mask(df.index<= df['position'].count() /3, 'low').mask((df.index<= df['position'].count()/3*2)&(df.index> df['position'].count()/3), 'middle').mask(df.index>= df['position'].count() /3*2, 'high')
df.groupby('position')['Price'].mean()
答案
condition = ['Head']*20+['Mid']*20+['Tail']*20
df.groupby(condition)['Price'].mean()
- 对类型
Type
分组,对Price
和HP
分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引。
res = df.groupby('Type').agg({'Price':['max'], 'HP':'min'})
res.columns = res.columns.map(lambda x:'_'.join(x))
- 对类型
Type
分组,对HP
进行组内的min-max
归一化。
def normalize(s):
s_min, s_max = s.min(), s.max()
res = (s - s_min)/(s_max - s_min)
return res
df.groupby('Type')['HP'].transform(normalize).head()
- 对类型
Type
分组,计算Disp.
与HP
的相关系数。
df.groupby('Type')[['HP', 'Disp.']].apply(lambda x:np.corrcoef(x['HP'].values, x['Disp.'].values)[0,1])