- 注:本文演示基于Python 3.9.7和Pandas 1.3.4
(2022.05.12 Thur)
Python的Pandas工具包提供了类似于SQL中group的操作指令groupby
,但功能更为强大。本文介绍基于groupby
的数据分组和聚合操作。此外,还介绍了pandas.transform
的使用。
GroupBy机制
GroupBy机制可以简单的描述为split-appy-combine过程。pandas对象中的数据,e.g., Series/DataFrame or others,根据用户提供的一个或多个key,被切分(split)成多个group。切分操作按照特定轴进行,即行(axis=0)或列(axis=1)。之后应用(apply)某个函数在切分的group上,生成新值诸如此类。最后将处理后的数据组合(combine)成新的数据对象。
用于分组的键(key)可以有多种形式:
- 与分组的轴有相同数据长度的list/array
- DataFrame中的列名
- 提供了被分组的值和分组名的字典/Series
- a function to be invoked on the axis index or the individual labels in the index
案例如下
import pandas as pd
import numpy as np
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'],
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randn(5),
'data2': np.random.randn(5)})
查看df
>> df
key1 key2 data1 data2
0 a one 0.162224 -0.497908
1 a two 0.497138 0.195447
2 b one 1.158331 -0.580097
3 b two 1.639932 -0.997953
4 a one -0.303192 0.525740
下面根据key1
列的值对data1
列做分组,注意groupby接收的参数是df['key1'],而非'key1',事实上如果被分组的对象是一个Series,则groupby的参数是一个Series,如果被分组对象是一个DataFrame,则groupby的参数可以是一个Series,也可以是该Series的名字,参考下面示例。返回结果grouped
的类型是SeriesGroupBy对象。
>> grouped = df['data1'].groupby(df['key1'])
>> grouped
<pandas.core.groupby.generic.SeriesGroupBy object at 0x117a01370>
该分组结果计算sum
>> grouped.sum()
key1
a 0.356170
b 2.798262
Name: data1, dtype: float64
同时也可以对整个DataFrame df
做分组,再计算其中列data1
的聚合值
>> df.groupby('key1')['data1'].sum() # or df.groupby('key1')[['data1']].sum()
key1
a 0.356170
b 2.798262
Name: data1, dtype: float64
可以根据多个列做分组,比如下面用key1
和key2
对DataFrame做分组。
>> g2 = df.groupby(['key1', 'key2'])
>> g2.sum()
data1 data2
key1 key2
a one -0.140968 0.027832
two 0.497138 0.195447
b one 1.158331 -0.580097
two 1.639932 -0.997953
上面的g2
结果也可以通过unstack
方法表示为包含独一无二键值对的分级索引(hierarchical index)
>> g2.sum.unstack()
data1 data2
key2 one two one two
key1
a -0.140968 0.497138 0.027832 0.195447
b 1.158331 1.639932 -0.580097 -0.997953
分组的依据也可以是相同长度的其他数组,比如
>> states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
>> years = np.array([2005, 2005, 2006, 2005, 2006])
>> df['data1'].groupby([states, years]).sum()
California 2005 0.497138
2006 1.158331
Ohio 2005 1.802156
2006 -0.303192
Name: data1, dtype: float64
我们再来看有哪些列参与了分组。下面的这个例子,用key1
列对DataFrame做分组,结果中只显示了data1
和data2
而没有key2
。因为key2
列是非数值列,被排除在分组聚合的结果中。
>> df.groupby('key1').sum()
data1 data2
key1
a 0.356170 0.223279
b 2.798262 -1.578050
size
方法返回分组的数字
>> df.groupby(['key1', 'key2']).size()
key1 key2
a one 2
two 1
b one 1
two 1
dtype: int64
(2022.05.13 Fri)
迭代groupby对象
>> for k,v in df.groupby('key1'):
... print(k,'\n')
... print(v)
a
key1 key2 data1 data2
0 a one 0.162224 -0.497908
1 a two 0.497138 0.195447
4 a one -0.303192 0.525740
b
key1 key2 data1 data2
2 b one 1.158331 -0.580097
3 b two 1.639932 -0.997953
也可以转化成list对象迭代
>> list(df.groupby('key1'))
[('a',
key1 key2 data1 data2
0 a one 0.162224 -0.497908
1 a two 0.497138 0.195447
4 a one -0.303192 0.525740),
('b',
key1 key2 data1 data2
2 b one 1.158331 -0.580097
3 b two 1.639932 -0.997953)]
注意,当使用多个key做分组时,key的不同元素的组合保存为tuple
>> for k, g in df.groupby(['key1', 'key2']):
... print(k, '\n')
... print(g)
('a', 'one')
key1 key2 data1 data2
0 a one 0.162224 -0.497908
4 a one -0.303192 0.525740
('a', 'two')
key1 key2 data1 data2
1 a two 0.497138 0.195447
('b', 'one')
key1 key2 data1 data2
2 b one 1.158331 -0.580097
('b', 'two')
key1 key2 data1 data2
3 b two 1.639932 -0.997953
按字典和Series分组
除了数组之外,DataFrame还可按字典和Series分组。
考虑如下DataFrame
people = pd.DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people.iloc[2:3, [1, 2]] = np.nan
打印
>> people
a b c d e
Joe 1.317179 -0.209542 0.943849 -2.226055 -0.430271
Steve -1.757563 -1.531038 -0.519030 -0.533200 0.447961
Wes 0.407967 NaN NaN 0.658021 0.440425
Jim -0.320255 -1.165006 0.275818 1.276247 -1.346920
Travis -1.678433 -1.272230 1.391566 -0.006185 -0.109339
定义一个字典
>> mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f' : 'orange'}
可以字典为key,对people
进行分组
>> by_column = people.groupby(mapping, axis=1)
>> by_column.sum()
blue red
Joe -1.282207 0.677367
Steve -1.052230 -2.840639
Wes 0.658021 0.848391
Jim 1.552065 -2.832181
Travis 1.385381 -3.060002
也可以将字典转化为Series并以此为依据对数据做分组
>> ms = pd.Series(mapping)
>> ms
a red
b red
c blue
d blue
e red
f orange
dtype: object
>> people.groupby(ms, axis=1).sum()
blue red
Joe -1.282207 0.677367
Steve -1.052230 -2.840639
Wes 0.658021 0.848391
Jim 1.552065 -2.832181
Travis 1.385381 -3.060002
根据函数分组
函数也可作为group key。一旦函数做为group key,将index值作为函数的输入,而返回值作为组名。比如上面的people
数组,调用len
作为group key,则返回的index值的长度将作为组名。
>> people.groupby(len).sum()
a b c d e
3 1.404891 -1.374547 1.219667 -0.291787 -1.336766
5 -1.757563 -1.531038 -0.519030 -0.533200 0.447961
6 -1.678433 -1.272230 1.391566 -0.006185 -0.109339
另一个例子,一个函数检测string的结尾字母,如果是e或s则分别返回,其他情况返回others。将这个函数作为group key,查看结果。
def endswith(k):
if k.endswith('e'):
return 'e'
elif k.endswith('s'):
return 's'
else:
return 'others'
>> people.groupby(endswith).sum()
a b c d e
e -0.440383 -1.740579 0.424819 -2.759255 0.017690
others -0.320255 -1.165006 0.275818 1.276247 -1.346920
s -1.270466 -1.272230 1.391566 0.651836 0.331085
亦可混合使用
>> key_list = ['one', 'one', 'one', 'two', 'two']
>> people.groupby([len, key_list]).min()
a b c d e
3 one 0.407967 -0.209542 0.943849 -2.226055 -0.430271
two -0.320255 -1.165006 0.275818 1.276247 -1.346920
5 one -1.757563 -1.531038 -0.519030 -0.533200 0.447961
6 two -1.678433 -1.272230 1.391566 -0.006185 -0.109339
根据分级索引(hierarchical index)分组
>> columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
[1, 3, 5, 1, 3]],
names=['cty', 'tenor'])
>> hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns)
>> hier_df
cty US JP
tenor 1 3 5 1 3
0 0.560145 -1.265934 0.119827 -1.063512 0.332883
1 -2.359419 -0.199543 -1.541996 -0.970736 -1.307030
2 0.286350 0.377984 -0.753887 0.331286 1.349742
3 0.069877 0.246674 -0.011862 1.004812 1.327195
>> hier_df.groupby(level='cty', axis=1).count()
cty JP US
0 2 3
1 2 3
2 2 3
3 2 3
Data Aggregation数据聚合
数据聚合指的是从数组生成标量(scalar)值的数据变换过程。常见的数据聚合包括前面出现的几种,比如mean
,sum
,count
,min
/max
,median
,std, var
,prod
,first
,last
等。还包括quantile
用于计算分位点。比如前面的根据key1
分组的data1
数据,计算其0.9分位点
>> grouped.quantile(0.9)
key1
a 0.430156
b 1.591772
Name: data1, dtype: float64
也可以自定义聚合函数,并用agg
方法引入。比如定义一个80分位点与20分位点的差值函数q82
,并用agg
方法调用该聚合函数。
def q82(arr):
return np.percentile(arr, 80) - np.percentile(arr, 20)
>> grouped.agg(q82)
key1
a 0.480198
b 0.288961
Name: data1, dtype: float64
注意,如果是自定义的函数,在被agg
方法调用时直接写函数名即可,如果是内置聚合函数如上面提到的,则需要在名字上加引号,且与不用agg
方法的直接调用返回相同的结果。
>> grouped.agg('mean')
key1
a 0.118723
b 1.399131
Name: data1, dtype: float64
>> grouped.mean()
key1
a 0.118723
b 1.399131
Name: data1, dtype: float64
可通过describe
方法验证上面的结果
>> grouped.describe()
count mean std min 25% 50% 75%
max
key1
a 3.0 0.118723 0.401935 -0.303192 -0.070484 0.162224 0.329681 0.497138
b 2.0 1.399131 0.340543 1.158331 1.278731 1.399131 1.519531 1.639932
当然也可以用实现其他功能的函数,比如对分组结果排序
def sf(arr):
arr = list(map(lambda x: round(x, 2), arr))
return sorted(arr)
>> grouped.agg(sf)
key1
a [-0.3, 0.16, 0.5]
b [1.16, 1.64]
Name: data1, dtype: object
列级(colume-wise)应用和多函数应用
对单独一个列做多个函数操作,参考如下案例
>> tips = pd.read_csv('/Users/.../pydata-book/examples/tips.csv')
>> tips.shape
(244, 6)
>> tips.columns
Index(['total_bill', 'tip', 'smoker', 'day', 'time', 'size'], dtype='object')
>> tips.head(5)
total_bill tip smoker day time size
0 16.99 1.01 No Sun Dinner 2
1 10.34 1.66 No Sun Dinner 3
2 21.01 3.50 No Sun Dinner 3
3 23.68 3.31 No Sun Dinner 2
4 24.59 3.61 No Sun Dinner 4
>> tips['tip_pct'] = tips['tip']/tips['total_bill'] # 生成小费比例字段
>> grouped = tips.groupby(['day', 'smoker']) #根据day和smoker两个字段对数据分组
>> grouped_pct = grouped['tip_pct'] # 取得tip_pct字段
计算平均
>> grouped_pct.agg('mean')
day smoker
Fri No 0.151650
Yes 0.174783
Sat No 0.158048
Yes 0.147906
Sun No 0.160113
Yes 0.187250
Thur No 0.160298
Yes 0.163863
Name: tip_pct, dtype: float64
计算一列的多个统计量/聚合值,用agg
方法,统计量用list保存
>> grouped_pct.agg(['mean', 'max', q82])
mean max q82
day smoker
Fri No 0.151650 0.187735 0.034600
Yes 0.174783 0.263480 0.097666
Sat No 0.158048 0.291990 0.061659
Yes 0.147906 0.325733 0.112116
Sun No 0.160113 0.252672 0.072194
Yes 0.187250 0.710345 0.144385
Thur No 0.160298 0.266312 0.060163
Yes 0.163863 0.241255 0.061858
此外,你可以为作用于列的统计量/聚合函数命名,并以新命名显示,以tuple in list为表示。
>> rn = [('func_mean', 'mean'), ('func_q82', q82)]
>> grouped_pct.agg(rn)
func_mean func_q82
day smoker
Fri No 0.151650 0.034600
Yes 0.174783 0.097666
Sat No 0.158048 0.061659
Yes 0.147906 0.112116
Sun No 0.160113 0.072194
Yes 0.187250 0.144385
Thur No 0.160298 0.060163
Yes 0.163863 0.061858
此方法也可用于多个列使用相同的聚合方法,注意多列的名字表示成一个list,作为grouped的index。注意到对多个列使用不同的聚合方法,DataFrame将返回分级列(hierarchical column)。
>> tmp = grouped[['tip_pct', 'total_bill']].agg(['mean', 'max', q82])
>> tmp
tip_pct total_bill
mean max q82 mean max q82
day smoker
Fri No 0.151650 0.187735 0.034600 18.420000 22.75 8.022
Yes 0.174783 0.263480 0.097666 16.813333 40.17 11.166
Sat No 0.158048 0.291990 0.061659 19.661778 48.33 7.724
Yes 0.147906 0.325733 0.112116 21.276667 50.81 15.168
Sun No 0.160113 0.252672 0.072194 20.506667 48.17 11.844
Yes 0.187250 0.710345 0.144385 24.120000 45.35 17.224
Thur No 0.160298 0.266312 0.060163 17.113111 41.19 11.706
Yes 0.163863 0.241255 0.061858 19.190588 43.11 7.284
上面结果可按列读取
>> tmp['tip_pct']
mean max q82
day smoker
Fri No 0.151650 0.187735 0.034600
Yes 0.174783 0.263480 0.097666
Sat No 0.158048 0.291990 0.061659
Yes 0.147906 0.325733 0.112116
Sun No 0.160113 0.252672 0.072194
Yes 0.187250 0.710345 0.144385
Thur No 0.160298 0.266312 0.060163
Yes 0.163863 0.241255 0.061858
而对不同的列采用不同的聚合函数,需要使用字典。比如对tip
列要计算最大值,对size
列计算求和,可用列名做key,聚合函数名或聚合函数形成的列做value
>> ac = {'tip': ['max', 'mean'], 'size': 'sum'}
>> grouped.agg(ac)
tip size
max mean sum
day smoker
Fri No 3.50 2.812500 9
Yes 4.73 2.714000 31
Sat No 9.00 3.102889 115
Yes 10.00 2.875476 104
Sun No 6.00 3.167895 167
Yes 6.50 3.516842 49
Thur No 6.70 2.673778 112
Yes 5.00 3.030000 40
聚合结果取消index
对分组后的聚合结果取消index,只需要在做groupby
操作时加入as_index = False
选项
>> tips.groupby(['day', 'smoker'], as_index=False).mean()
day smoker total_bill tip size tip_pct
0 Fri No 18.420000 2.812500 2.250000 0.151650
1 Fri Yes 16.813333 2.714000 2.066667 0.174783
2 Sat No 19.661778 3.102889 2.555556 0.158048
3 Sat Yes 21.276667 2.875476 2.476190 0.147906
4 Sun No 20.506667 3.167895 2.929825 0.160113
5 Sun Yes 24.120000 3.516842 2.578947 0.187250
6 Thur No 17.113111 2.673778 2.488889 0.160298
7 Thur Yes 19.190588 3.030000 2.352941 0.163863
使用聚合函数的agg
方法与apply
的差别
agg
方法应用于列、Series
apply
方法应用于这个DataFrame
(待验证)
Apply-一般性的Split-Apply-Combine
Groupby方法里面最常用的部分是Apply,即对数据分组之后,再对每个分组piece进行的操作。Apply操作之后即是对数据做concatenate。注意Apply作用的对象是group piece,也是一个DataFrame。
仍然考虑上面的数据框tips
,找出每天的中晚饭小费收入占比最高的前五位。首先写个函数返回一个数据框按某一列排序最高的前5位所有数据。
def top(df, n=5, column='tip_pct'):
return df.sort_value(name=column)[-n:]
下面按day
和time
分组,查看每组有多少笔小费,并对分组执行top
函数。可以看出周六周日两天人们普遍支付更多比例的小费。
>> tips.groupby(['day', 'time']).size()
day time
Fri Dinner 12
Lunch 7
Sat Dinner 87
Sun Dinner 76
Thur Dinner 1
Lunch 61
dtype: int64
>> tips.groupby(['day', 'time']).apply(top)
total_bill tip smoker day time size tip_pct
day time
Fri Dinner 91 22.49 3.50 No Fri Dinner 2 0.155625
92 5.75 1.00 Yes Fri Dinner 2 0.173913
101 15.38 3.00 Yes Fri Dinner 2 0.195059
100 11.35 2.50 Yes Fri Dinner 2 0.220264
93 16.32 4.30 Yes Fri Dinner 2 0.263480
Lunch 220 12.16 2.20 Yes Fri Lunch 2 0.180921
223 15.98 3.00 No Fri Lunch 3 0.187735
226 10.09 2.00 Yes Fri Lunch 2 0.198216
222 8.58 1.92 Yes Fri Lunch 1 0.223776
221 13.42 3.48 Yes Fri Lunch 2 0.259314
Sat Dinner 20 17.92 4.08 No Sat Dinner 2 0.227679
214 28.17 6.50 Yes Sat Dinner 3 0.230742
109 14.31 4.00 Yes Sat Dinner 2 0.279525
232 11.61 3.39 No Sat Dinner 2 0.291990
67 3.07 1.00 Yes Sat Dinner 1 0.325733
Sun Dinner 181 23.33 5.65 Yes Sun Dinner 2 0.242177
51 10.29 2.60 No Sun Dinner 2 0.252672
183 23.17 6.50 Yes Sun Dinner 4 0.280535
178 9.60 4.00 Yes Sun Dinner 2 0.416667
172 7.25 5.15 Yes Sun Dinner 2 0.710345
Thur Dinner 243 18.78 3.00 No Thur Dinner 2 0.159744
Lunch 200 18.71 4.00 Yes Thur Lunch 3 0.213789
87 18.28 4.00 No Thur Lunch 2 0.218818
88 24.71 5.85 No Thur Lunch 2 0.236746
194 16.58 4.00 Yes Thur Lunch 2 0.241255
149 7.51 2.00 No Thur Lunch 2 0.266312
...
在apply
中调用的函数可以通过如下方式传递参数。比如查看每天每个时段最高价的bill,可指定n=1
和columns=total_bill
。
>> tips.groupby(['day', 'time']).apply(top, n=1, columns='total_bill')
total_bill tip smoker day time size tip_pct
day time
Fri Dinner 95 40.17 4.73 Yes Fri Dinner 4 0.117750
Lunch 225 16.27 2.50 Yes Fri Lunch 2 0.153657
Sat Dinner 170 50.81 10.00 Yes Sat Dinner 3 0.196812
Sun Dinner 156 48.17 5.00 No Sun Dinner 6 0.103799
Thur Dinner 243 18.78 3.00 No Thur Dinner 2 0.159744
Lunch 197 43.11 5.00 Yes Thur Lunch 4 0.115982
再比如,查看分组后某个数据的describe
信息,除了对分组结果调用内置的describe
方法,也可将describe
写成lambda函数通过apply
调用
>> tips.groupby('smoker')['tip_pct'].describe()
count mean std min 25% 50% 75% max
smoker
No 151.0 0.159328 0.039910 0.056797 0.136906 0.155625 0.185014 0.291990
Yes 93.0 0.163196 0.085119 0.035638 0.106771 0.153846 0.195059 0.710345
>> f = lambda x: x.describe()
>> tips.groupby('smoker')['tip_pct'].apply(f)
smoker
No count 151.000000
mean 0.159328
std 0.039910
min 0.056797
25% 0.136906
50% 0.155625
75% 0.185014
max 0.291990
Yes count 93.000000
mean 0.163196
std 0.085119
min 0.035638
25% 0.106771
50% 0.153846
75% 0.195059
max 0.710345
Name: tip_pct, dtype: float64
(2022.05.16 Mon)
不显示分组键Suppressing the group key:
上面的例子中结果数据在初始对象的索引之上保存了分级索引hierachical index,可使用group_keys=False
指令取消分级索引:
>> tips.groupby(['day', 'time'], group_keys=False).apply(top, n=1, columns='total_bill')
total_bill tip smoker day time size tip_pct
95 40.17 4.73 Yes Fri Dinner 4 0.117750
225 16.27 2.50 Yes Fri Lunch 2 0.153657
170 50.81 10.00 Yes Sat Dinner 3 0.196812
156 48.17 5.00 No Sun Dinner 6 0.103799
243 18.78 3.00 No Thur Dinner 2 0.159744
197 43.11 5.00 Yes Thur Lunch 4 0.115982
分位数和桶分析Quantile and Bucket Analysis
可使用pandas.cut
指令对一个Series做分位分析,并结合groupby
指令对数组做分析。
>> frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
>> frame.head()
data1 data2
0 -0.754650 -0.085965
1 -0.922789 0.523089
2 -1.001486 0.306347
3 1.169146 -1.424664
4 2.311694 0.280592
>> quartiles = pd.cut(frame.data1, 4)
>> quartiles.head()
0 (-1.563, 0.154]
1 (-1.563, 0.154]
2 (-1.563, 0.154]
3 (0.154, 1.871]
4 (1.871, 3.588]
Name: data1, dtype: category
Categories (4, interval[float64, right]): [(-3.287, -1.563] < (-1.563, 0.154] < (0.154, 1.871] < (1.871, 3.588]]
cut
方法返回的Categorial
对象可传递给groupby
方法。我们可以以此为依据对data2
字段做分组。
def get_stats(df):
return {'mean': df.mean(), 'min': df.min(), 'max': df.max(), 'count': df.count()}
>> q = frame.data2.groupby(quartiles).apply(get_stats).unstack()
mean min max count
data1
(-3.287, -1.563] 0.069223 -2.676886 2.390891 57.0
(-1.563, 0.154] -0.089421 -3.099386 2.872714 514.0
(0.154, 1.871] -0.001327 -3.319630 3.122984 394.0
(1.871, 3.588] 0.093478 -2.111966 2.091880 35.0
注意这里的cut
得到的分组是范围的长度尺度上相同/相似的结果。如果要每个分组内值的数量相同/相似,则需要使用qcut
指令。
GroupBy应用
Case 1:按组填充NA值
按组填充该组中的空值。有下面数据,保存美国若干州的某项数据。这些州分属东西两部分。我们设置其中一些值为空。
>> states = ['Ohio', 'New York', 'Vermont', 'Florida',\
'Oregon', 'Nevada', 'California', 'Idaho']
>> group_key = ['East'] * 4 + ['West'] * 4
>> data = pd.Series(np.random.randn(8), index=states)
>> data
Ohio 0.869617
New York -1.113901
Vermont -0.225091
Florida -0.010972
Oregon -0.312229
Nevada -0.122721
California -1.171598
Idaho 0.361301
dtype: float64
>> data[::2] = np.nan
>> data
Ohio NaN
New York -1.113901
Vermont NaN
Florida -0.010972
Oregon NaN
Nevada -0.122721
California NaN
Idaho 0.361301
dtype: float64
如果此时分组,计算得到的是非空值的统计信息,不包含空值部分
>> data.groupby(group_key).mean()
East -0.562437
West 0.119290
dtype: float64
下面按组填充数据,比如对每组中的空值填充该组其他值的mean
>> f_mean = lambda x: x.fillna(x.mean())
>> data.groupby(group_key).apply(f_mean)
Ohio -0.562437
New York -1.113901
Vermont -0.562437
Florida -0.010972
Oregon 0.119290
Nevada -0.122721
California 0.119290
Idaho 0.361301
dtype: float64
也可针对某个组填充特定值
>> fill_values = {'East': 5, 'West': 10}
>> fill_func = lambda g: g.fillna(fill_values[g.name])
>> data.groupby(group_key).apply(fill_func)
Ohio 5.000000
New York -1.113901
Vermont 5.000000
Florida -0.010972
Oregon 10.000000
Nevada -0.122721
California 10.000000
Idaho 0.361301
dtype: float64
Case 2:分组计算均值
df = pd.DataFrame({'category': ['a', 'a', 'a', 'a',\
'b', 'b', 'b', 'b'],\
'data': np.random.randn(8),\
'weights': np.random.rand(8)})
>> df
category data weights
0 a 1.561587 0.957515
1 a 1.219984 0.347267
2 a -0.482239 0.581362
3 a 0.315667 0.217091
4 b -0.047852 0.894406
5 b -0.454145 0.918564
6 b -0.556774 0.277825
7 b 0.253321 0.955905
加权平均的函数get_wa
get_wa = lambda x: np.average(x.data, weights=x.weights)
对df
这个dataframe按分组执行加权平均操作
>> df.groupby('category').apply(get_wa)
category
a 0.111150
b -1.091834
dtype: float64
基于pandas.groupby
的pandas.transform
指令
前面已经提到,用pandas.groupby
指令对DataFrame分组之后,可针对各组做操作。比如对各组求其mean操作
>> dd = pd.DataFrame({'keys': ['a', 'b', 'c', 'c','c','a','b','b','a'], 'values': np.arange(9,)})
>> dd.groupby('keys').values.mean()
keys
a 4.333333
b 4.666667
c 3.000000
Name: values, dtype: float64
假定我们想要得到一个Series和原序列长度相同,但是新Series中的值用原序列分组后的mean值代替。这种情况下pandas提供了transform
方法执行这种操作。下面计算对根据key keys
做分组的values
的值做归一化操作的过程,归一化的结果写入原DataFrame中。
>> f = lambda x: (x - x.mean())/x.std()
>> dd['mean'] = dd.groupby('keys').values.transform('mean')
>> dd['std'] = dd.groupby('keys').values.transform('std')
>> dd['norm'] = dd.groupby('keys').values.transform(f)
>> dd
keys values mean std norm
0 a 0 4.333333 4.041452 -1.072222
1 b 1 4.666667 3.214550 -1.140647
2 c 2 3.000000 1.000000 -1.000000
3 c 3 3.000000 1.000000 0.000000
4 c 4 3.000000 1.000000 1.000000
5 a 5 4.333333 4.041452 0.164957
6 b 6 4.666667 3.214550 0.414781
7 b 7 4.666667 3.214550 0.725866
8 a 8 4.333333 4.041452 0.907265
Reference
1 Python for Data Analysis, Wes McKinney