GroupBy技术
主要流程为split-apply-combine(拆分-应用-合并),具体为:
分组运算的第一个阶段,pandas对象中的数据会根据你所提供的一个或多个键被拆分为多组,拆分操作实在特定的轴上执行的。然后,将一个函数应用到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并到最终的结果对象中。
- 根据一个或多个键拆分pandas对象
分组键可以有多种形式,且类型不必相同:
- 列表或数组,其长度与带分组的轴一样
- 表示DataFrame某个列名的值
- 字典或Series,给出待分组轴上的值与分组名之间的对应关系
- 函数,用于处理轴索引或索引中的各个标签
来看示例:
df
Out[5]:
data1 data2 key1 key2
0 0.242283 1.316144 a one
1 0.535346 -1.398294 a two
2 0.516628 2.207073 b one
3 -1.262803 -0.352256 b two
4 0.653567 -0.407961 a one
按key1进行分组,并计算data1列的平均值
grouped = df['data1'].groupby(df['key1'])
grouped
Out[7]: <pandas.core.groupby.SeriesGroupBy object at 0x000001B734984160>
变量grouped是一个GroupBy对象,实际上他还没有进行任何运算。
grouped.mean()
Out[8]:
key1
a 0.477065
b -0.373088
Name: data1, dtype: float64
如果一次传入过个数组,就会得到一个层次化索引的Series
means=df['data1'].groupby([df['key1'],df['key2']]).mean()
means
Out[10]:
key1 key2
a one 0.447925
two 0.535346
b one 0.516628
two -1.262803
Name: data1, dtype: float64
同样可以将列名作为分组键:
df.groupby('key1').mean()
Out[11]:
data1 data2
key1
a 0.477065 -0.163370
b -0.373088 0.927409
对分组进行迭代
for name, group in df.groupby('key1'):
print (name)
print (group)
a
data1 data2 key1 key2
0 0.242283 1.316144 a one
1 0.535346 -1.398294 a two
4 0.653567 -0.407961 a one
b
data1 data2 key1 key2
2 0.516628 2.207073 b one
3 -1.262803 -0.352256 b two
groupy默认是axis=0上进行分组的,可以设置axis在其他任何轴上进行分组。
选取一个或一组列
对于有DataFrame产生的GroupBy对象,如果用一个或一组列名对其索引,就能实现选取部分列进行聚合的目的。
df.groupby('key1')['data1']
Out[14]: <pandas.core.groupby.SeriesGroupBy object at 0x000001B7343A6550>
#它是df['data1'].groupby(df['key1'])的语法糖
- 应用组内转换或其他运算
数据聚合分组运算的一种,它接受能够将一维数组简化为标量值的函数。
如下面例子中,计算DataFrame列的样本分位数
df
Out[5]:
data1 data2 key1 key2
0 -0.039369 0.080425 a one
1 -0.629809 0.198063 a two
2 1.179436 0.510897 b one
3 0.478062 0.304403 b two
4 0.772346 -0.315774 a one
grouped=df.groupby('key1')
grouped['data1'].quantile(0.9)
Out[7]:
key1
a 0.610003
b 1.109298
Name: data1, dtype: float64
如果你想使用你自己的聚合函数,只需将其传入aggregate或agg即可
def peak_to_peak(arr):
return arr.max() - arr.min()
grouped.agg(peak_to_peak)
Out[9]:
data1 data2
key1
a 1.402154 0.513837
b 0.701374 0.206494
transform方法
people
Out[11]:
a b c d e
Joe -1.827769 -0.355652 0.552273 -1.898069 -0.543428
Steve -1.601029 0.562845 1.050669 -0.215205 -1.099391
Wes 1.866622 -1.534953 0.692820 -0.043697 0.240079
Jim -0.765684 1.837627 0.281838 0.218192 1.704804
Travis -0.716688 -1.390355 -0.725222 1.312762 0.374653
key = ['one','two','one','two','one']
people.groupby(key).mean()
Out[13]:
a b c d e
one -0.225945 -1.093653 0.173290 -0.209668 0.023768
two -1.183356 1.200236 0.666253 0.001493 0.302707
people.groupby(key).transform(np.mean)
Out[14]:
a b c d e
Joe -0.225945 -1.093653 0.173290 -0.209668 0.023768
Steve -1.183356 1.200236 0.666253 0.001493 0.302707
Wes -0.225945 -1.093653 0.173290 -0.209668 0.023768
Jim -1.183356 1.200236 0.666253 0.001493 0.302707
Travis -0.225945 -1.093653 0.173290 -0.209668 0.023768
可以看出,transform会将一个函数应用到各个分组,然后将结果放置到适当的位置上。如果个分组产生的是一个标量值,则该值就会被广播出去。
假设你希望从各组中减去平均值,我们此案创建一个距平化函数,病传给transform。
def demean(arr):
return arr - arr.mean()
demeaned = people.groupby(key).transform(demean)
demeaned
Out[17]:
a b c d e
Joe -1.601824 0.738002 0.378982 -1.688400 -0.567196
Steve -0.417672 -0.637391 0.384416 -0.216699 -1.402097
Wes 2.092567 -0.441300 0.519529 0.165971 0.216311
Jim 0.417672 0.637391 -0.384416 0.216699 1.402097
Travis -0.490743 -0.296701 -0.898512 1.522430 0.350885
apply方法:apply会将待处理的对象分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。
假设你想要根据分组选出最高的5个tip_pct值,首先,编写一个选取指定列具有最大值的行的函数
def top(df,n=5,column='tic_pct'):
return df.sort_index(by=column)[-n:]
top(tips,n=6)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)
Out[35]:
total_bill tip smoker day time size tip_pct tic_pct
109 14.31 4.00 Yes Sat Dinner 2 0.279525 0.279525
183 23.17 6.50 Yes Sun Dinner 4 0.280535 0.280535
232 11.61 3.39 No Sat Dinner 2 0.291990 0.291990
67 3.07 1.00 Yes Sat Dinner 1 0.325733 0.325733
178 9.60 4.00 Yes Sun Dinner 2 0.416667 0.416667
172 7.25 5.15 Yes Sun Dinner 2 0.710345 0.710345
现在,如果对smoker分组应用用该函数
tips.groupby('smoker').apply(top)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)
Out[36]:
total_bill tip smoker day time size tip_pct tic_pct
smoker
No 88 24.71 5.85 No Thur Lunch 2 0.236746 0.236746
185 20.69 5.00 No Sun Dinner 5 0.241663 0.241663
51 10.29 2.60 No Sun Dinner 2 0.252672 0.252672
149 7.51 2.00 No Thur Lunch 2 0.266312 0.266312
232 11.61 3.39 No Sat Dinner 2 0.291990 0.291990
Yes 109 14.31 4.00 Yes Sat Dinner 2 0.279525 0.279525
183 23.17 6.50 Yes Sun Dinner 4 0.280535 0.280535
67 3.07 1.00 Yes Sat Dinner 1 0.325733 0.325733
178 9.60 4.00 Yes Sun Dinner 2 0.416667 0.416667
172 7.25 5.15 Yes Sun Dinner 2 0.710345 0.710345
可以看出,top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。所以,最终结果就有了一个层次化索引,其内层索引值来自原DataFrame。
分位数和桶分析
pandas有一些能根据指定面元或样本分位数将数据拆分分成的工具。将这些函数跟GroupBy结合起来,就能非常轻松地实现对数据集的桶或分位数分析了。
frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
factor = pd.cut(frame.data1, 4)
factor[:10]
Out[39]:
0 (1.002, 2.789]
1 (-2.579, -0.785]
2 (1.002, 2.789]
3 (-2.579, -0.785]
4 (-0.785, 1.002]
5 (-2.579, -0.785]
6 (-0.785, 1.002]
7 (-0.785, 1.002]
8 (1.002, 2.789]
9 (-0.785, 1.002]
Name: data1, dtype: category
Categories (4, interval[float64]): [(-2.579, -0.785] < (-0.785, 1.002] < (1.002, 2.789] < (2.789, 4.576]]
由cut返回的Factor对象可直接用于GroupBy。因此,我们可以像下面这样对data2做一些统计计算:
def get_stats(group):
return {'min': group.min(), 'max': group.max(),
'count':group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(factor)
grouped.apply(get_stats).unstack()
Out[42]:
count max mean min
data1
(-2.579, -0.785] 200.0 2.614581 0.058889 -2.904774
(-0.785, 1.002] 636.0 3.180617 -0.078317 -3.641501
(1.002, 2.789] 162.0 2.740718 0.064263 -2.914572
(2.789, 4.576] 2.0 -0.027067 -0.200847 -0.374627
- 计算透视表或交叉表
透视表是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。待续。
709

被折叠的 条评论
为什么被折叠?



