摘要: pandas 的 GroupBy 功能可以方便地对数据进行分组、应用函数、转换和聚合等操作。   # 原作者:lionets

GroupBy


分组运算有时也被称为 “split-apply-combine” 操作。其中的 “split” 便是借由 obj.groupby() 方法来实现的。

.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False) 方法作用于一条轴向上,并接受一个分组键(by)参数来给调用者分组。分组键可以是Series 或列表,要求其长度与待分组的轴一致;也可以是映射函数、字典甚至数组的某条列名(字符串),但这些参数类型都只是快捷方式,其最终仍要用于生成一组用于拆分对象的值。

lang:python
>>> df = DataFrame({'key1':['a','a','b','b','a'],
'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),
'data2':np.random.randn(5)})
>>> df
data1 data2 key1 key2
0 0.922269 0.110285 a one
1 -0.181773 1.022435 a two
2 0.635899 0.279316 b one
3 0.527926 0.482807 b two
4 -1.586040 -1.312042 a one [5 rows x 4 columns]
>>> grouped = df.groupby(df['key1'])
>>> grouped
<pandas.core.groupby.DataFrameGroupBy object at 0x0000000005BC25F8>

这里使用 df['key1'] 做了分组键,即按 a 和 b 进行分组。但实际分组键并不需要与数组对象之间存在联系,只要长度相同即可,使用数组的列只是图方便。上例中如果使用 [1,1,2,2,3] 这样的列表做分组键的话,结果与df['key1'] 是相同的。

groupby 方法返回的 DataFrameGroupBy 对象实际并不包含数据内容,它记录的是有关分组键——df['key1'] 的中间数据。当你对分组数据应用函数或其他聚合运算时,pandas 再依据 groupby 对象内记录的信息对 df 进行快速分块运算,并返回结果。

上面这段话其实想说是: groupby 方法的调用本身并不涉及运算,因此速度很快。而在操作这个 grouped 对象的时候,还是将其看成一个保存了实际数据的对象比较方便。比如我们可以直接对其应用很多方法,或索引切片:

lang:python
>>> grouped.mean()
data1 data2
key1
a -0.281848 -0.059774
b 0.581912 0.381061 [2 rows x 2 columns]

上例中没有显示 key2 列,是因为其值不是数字类型,被 mean() 方法自动忽视了。当想要只看某一(些)列的时候,可以通过索引来实现,在 groupby 方法调用前后均可(这是一种语法糖):

lang:python
>>> df['data1'].groupby(df['key1']).mean()
key1
a -0.281848
b 0.581912
dtype: float64
>>> df.groupby(df['key2'])['data2'].mean()
key2
one -0.307481
two 0.752621
Name: data2, dtype: float64

如果分组键使用的是多个数组,就会得到一个层次化索引的结果:

lang:python
>>> df.groupby([df['key1'],df['key2']]).mean()
data1 data2
key1 key2
a one -0.331885 -0.600879
two -0.181773 1.022435
b one 0.635899 0.279316
two 0.527926 0.482807 [4 rows x 2 columns]

最后,可以使用 GroupBy 对象(不论是 DataFrameGroupBy 还是 SeriesGroupBy)的 .size() 方法查看分组大小:

lang:python
>>> grouped.size()
key1
a 3
b 2
dtype: int64

<br />

对分组进行迭代

GroupBy 对象是可以通过 for 循环迭代的,可以产生一组二元组,分别为分组名和组内数据。下面是一个多重分组键的情况:

lang:python
>>> for i,j in df.groupby([df['key1'],df['key2']]):
print(i)
print('-----------')
print(j) ('a', 'one')
-----------
data1 data2 key1 key2
0 0.922269 0.110285 a one
4 -1.586040 -1.312042 a one [2 rows x 4 columns]
('a', 'two')
-----------
data1 data2 key1 key2
1 -0.181773 1.022435 a two [1 rows x 4 columns]
('b', 'one')
-----------
data1 data2 key1 key2
2 0.635899 0.279316 b one [1 rows x 4 columns]
('b', 'two')
-----------
data1 data2 key1 key2
3 0.527926 0.482807 b two [1 rows x 4 columns]

<br />

使用字符串列名作分组键

前面曾提到过可以使用字符串形式的列名作为分组键,但上面例子中都没有用。是因为这种方法虽然方便,却存在隐患——使用这种方法时,调用者必须是 DataFrame 对象自身而不可以是 DataFrame 的索引形式。即df.groupby('key1')['data1'] 是 ok 的,但 df['data1'].groupby('key1') 会报错。使用时当注意区分。 <br />

使用 字典或Series作分组键

这两种参数需要提供一种从行(列)名到组名的映射关系。(还记得 Series 就是一种定长有序字典 这种说法嘛)

lang:python
>>> df.groupby({0:'a',1:'a',2:'b',3:'b',4:'a'}).mean()
data1 data2
a -0.281848 -0.059774
b 0.581912 0.381061 [2 rows x 2 columns]

<br />

通过函数进行分组

函数的作用有些类似于字典,或者说这些奇怪的分组键都类似于字典——利用某种映射关系将待分组的轴转化为一个等长的由分组名组成的序列。

如果说行列名是作为索引传递给字典以获取组名的话,那么在函数分组键中,行列名就会作为参数传递给函数。这便是你需要提供的函数类型:

lang:python
>>> df.groupby(lambda x:'even' if x%2==0 else 'odd').mean()
data1 data2
even -0.009290 -0.307481
odd 0.173076 0.752621 [2 rows x 2 columns]

<br />

根据索引级别分组

当根据高级别索引来分组的时候,参数就不再是 by=None 了,而要换成 level=None,值可以是索引级别的编号或名称:

lang:python
>>> index = pd.MultiIndex.from_arrays([['even','odd','even','odd','even'],
[0,1,2,3,4]],names=['a','b'])
>>> df.index = index
>>> df.groupby(level='a').mean()
data1 data2
a
even -0.009290 -0.307481
odd 0.173076 0.752621 [2 rows x 2 columns]
>>> df.groupby(level=0).mean()
data1 data2
a
even -0.009290 -0.307481
odd 0.173076 0.752621 [2 rows x 2 columns]

<br />

数据聚合(Aggregation)


数据聚合,指的是任何能够从数组产生标量值的数据转换过程。你也可以简单地将其理解为统计计算,如 mean(), sum(), max() 等。

数据聚合本身与分组并没有直接关系,在任何一列(行)或全部列(行)上都可以进行。不过当这种运算被应用在分组数据上的时候,结果可能会变得更有意义。

对于 GroupBy 对象可以应用的聚合运算包括:

  • 已经内置的方法,如 sum(), mean() 等
  • Series 的方法,如 quantile() 等
  • 自定义的聚合函数,通过传入 GroupBy.aggregate() 或 GroupBy.agg() 来实现

其中自定义函数的参数应当为一个数组类型,即 GroupBy 对象迭代出的元组的第二个元素。如

lang:python
>>> df.groupby('key1')['data1','data2'].agg(lambda arr:arr.max()-arr.min())
data1 data2
key1
a 2.508309 2.334477
b 0.107973 0.203492 [2 rows x 2 columns]

但其实自定义函数的效率很慢,远不如 GroupBy 对象已经优化过的内建方法,这些方法包括: <br />

<table style="font-size:14px"> <tr> <td>############</td> <td>**</td> </tr> <tr> <td>count</td> <td>分组中非 NA 值得数量</td> </tr> <tr> <td>sum</td> <td>非 NA 值的和</td> </tr> <tr> <td>mean</td> <td>非 NA 值的平均值</td> </tr> <tr> <td>median</td> <td>非 NA 值的算数中位数</td> </tr> <tr> <td>std, var</td> <td>无偏(分母为 n-1)标准差和方差</td> </tr> <tr> <td>min, max</td> <td>非 NA 值的最小值和最大值</td> </tr> <tr> <td>prod</td> <td>非 NA 值的积</td> </tr> <tr> <td>first, last</td> <td>第一个和最后一个非 NA 值</td> </tr> </table> <br />

面向列的多函数应用

前面的例子中,我们每次都只调用一个聚合方法。对于多函数应用,我们可以分两种情况讨论:

第一种是相同列应用多个函数从而得到多个结果的情况,这时只需给 agg() 传入一个函数列表即可:

lang:python
>>> df.groupby('key1')['data1','data2'].agg(['min','max'])
data1 data2
min max min max
key1
a -1.586040 0.922269 -1.312042 1.022435
b 0.527926 0.635899 0.279316 0.482807 [2 rows x 4 columns]

这里一个技巧是,对上节中那些统计方法,可以将方法名以字符串的形式传入 agg()。另外,如果你不喜欢列的命名方式,或你使用的干脆是 lambda 匿名函数,你可以把函数参数替换成(name,function)的元组格式,这样结果集中的列就不再以函数名命名而是以你给出的 name 为准。

第二种是对不同列应用不同函数的情况,这时需要传给 agg() 一个从列名映射到函数名的字典:

lang:python
>>> df.groupby('key1').agg({'data1':'min','data2':'max'})
data1 data2
key1
a -1.586040 1.022435
b 0.527926 0.482807 [2 rows x 2 columns]

这里要注意的是,就不要再在 GroupBy 对象上进行索引操作啦,你的字典参数已经做了响应的列选取工作。 <br />

分组级运算和转换


聚合只是分组运算的一种,更多种类的分组运算可以通过 .transform() 和 apply() 方法实现。 <br />

transform

前面进行聚合运算的时候,得到的结果是一个以分组名为 index 的结果对象。如果我们想使用原数组的 index 的话,就需要进行 merge 转换。transform(func, *args, **kwargs) 方法简化了这个过程,它会把 func 参数应用到所有分组,然后把结果放置到原数组的 index 上(如果结果是一个标量,就进行广播):

lang:python
>>> df
data1 data2 key1 key2
a b
even 0 0.922269 0.110285 a one
odd 1 -0.181773 1.022435 a two
even 2 0.635899 0.279316 b one
odd 3 0.527926 0.482807 b two
even 4 -1.586040 -1.312042 a one [5 rows x 4 columns]
>>> df.groupby('key1').transform('mean')
data1 data2
a b
even 0 -0.281848 -0.059774
odd 1 -0.281848 -0.059774
even 2 0.581912 0.381061
odd 3 0.581912 0.381061
even 4 -0.281848 -0.059774 [5 rows x 2 columns]

<br />

apply

apply(func, *args, **kwargs) 会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试用pd.concat() 把结果组合起来。func 的返回值可以是 pandas 对象或标量,并且数组对象的大小不限。

lang:python
>>> df
data1 data2 key1 key2
0 0.721150 -0.359337 a one
1 -1.727197 1.539508 a two
2 -0.339751 0.171379 b one
3 -0.291888 -1.000769 b two
4 -0.127029 0.506162 a one [5 rows x 4 columns]
>>> def foo(df,n=12):
return pd.DataFrame(np.arange(n).reshape(3,4)) >>> df.groupby('key1').apply(foo)
0 1 2 3
key1
a 0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
b 0 0 1 2 3
1 4 5 6 7
2 8 9 10 11 [6 rows x 4 columns]

这是一个毫无意义的例子,因为传给 apply 的 func 参数没有对 df 做任何处理,直接返回了一个(3,4)的数组。而实际上,这样一个毫无意义的例子恰好说明了 apply 方法的通用性——你可以返回任意的结果。多数时候,限制 apply 发挥的其实是用户的脑洞。 <br />

透视表和交叉表

DataFrame 对象有一个 .pivot_table(data, values=None, rows=None, cols=None, aggfunc='mean', fill_value=None, margins=False, dropna=True) 方法可以用来制作透视表,同时 pd.pivot_table() 也是一个顶层函数。

  • data 参数相当于 self,这里将其命名为 data 也许是为了与顶级函数版本的 pivot_table 保持一致。
  • values 参数可以是一个以列名为元素的列表,用于指定想要聚合的数据,不给出的话默认使用全部数据。
  • rows 参数用于指定行分组键
  • cols 参数用于指定列分组键
  • aggfunc 参数用于指定聚合函数,默认为均值(mean)
  • margins 参数是小计(Total)功能的开关,设为 True 后结果集中会出现名为 “ALL” 的行和列

例:

lang:python
>>> df
A B C D
0 foo one small 1
1 foo one large 2
2 foo one large 2
3 foo two small 3
4 foo two small 3
5 bar one large 4
6 bar one small 5
7 bar two small 6
8 bar two large 7 >>> table = pivot_table(df, values='D', rows=['A', 'B'],
... cols=['C'], aggfunc=np.sum)
>>> table
small large
foo one 1 4
two 6 NaN
bar one 5 4
two 6 7

<br /> 交叉表(cross-tabulation,crosstab)是一种用于计算分组频数的特殊透视表。

crosstab(rows, cols, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, dropna=True)

lang:python
>>> a
array([foo, foo, foo, foo, bar, bar,
bar, bar, foo, foo, foo], dtype=object)
>>> b
array([one, one, one, two, one, one,
one, two, two, two, one], dtype=object)
>>> c
array([dull, dull, shiny, dull, dull, shiny,
shiny, dull, shiny, shiny, shiny], dtype=object) >>> crosstab(a, [b, c], rownames=['a'], colnames=['b', 'c'])
b one two
c dull shiny dull shiny
a
bar 1 2 1 0
foo 2 2 1 2

python数据分组运算的更多相关文章

  1. (数据科学学习手札99)掌握pandas中的时序数据分组运算

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们在使用pandas分析处理时间序列数据 ...

  2. 《利用python进行数据分析》读书笔记--第九章 数据聚合与分组运算(一)

    http://www.cnblogs.com/batteryhp/p/5046450.html 对数据进行分组并对各组应用一个函数,是数据分析的重要环节.数据准备好之后,通常的任务就是计算分组统计或生 ...

  3. 利用python进行数据分析之数据聚合和分组运算

    对数据集进行分组并对各分组应用函数是数据分析中的重要环节. group by技术 pandas对象中的数据会根据你所提供的一个或多个键被拆分为多组,拆分操作是在对象的特定轴上执行的,然后将一个函数应用 ...

  4. Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

    Python 数据分析(二) 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识 第1节 groupby 技术 第2节 数据聚合 第3节 分组级运算和转换 第4 ...

  5. 《python for data analysis》第九章,数据聚合与分组运算

    # -*- coding:utf-8 -*-# <python for data analysis>第九章# 数据聚合与分组运算import pandas as pdimport nump ...

  6. Python数据聚合和分组运算(1)-GroupBy Mechanics

    前言 Python的pandas包提供的数据聚合与分组运算功能很强大,也很灵活.<Python for Data Analysis>这本书第9章详细的介绍了这方面的用法,但是有些细节不常用 ...

  7. Python之数据聚合与分组运算

    Python之数据聚合与分组运算 1. 关系型数据库方便对数据进行连接.过滤.转换和聚合. 2. Hadley Wickham创建了用于表示分组运算术语"split-apply-combin ...

  8. 利用Python进行数据分析-Pandas(第六部分-数据聚合与分组运算)

    对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),通常是数据分析工作中的重要环节.在将数据集加载.融合.准备好之后,通常是计算分组统计或生成透视表.pandas提供了一个灵活高效的group ...

  9. python中pandas数据分析基础3(数据索引、数据分组与分组运算、数据离散化、数据合并)

    //2019.07.19/20 python中pandas数据分析基础(数据重塑与轴向转化.数据分组与分组运算.离散化处理.多数据文件合并操作) 3.1 数据重塑与轴向转换1.层次化索引使得一个轴上拥 ...

随机推荐

  1. Nearest Common Ancestors(poj 1330)

    题意:给定一棵树,询问两个节点的最近公共祖先. 输入:第一行T,表示测试组数. 每组测试数据包含一个n,表示节点数目,下面n-1行是连接的边,最后一行是询问 输出:共T行,代表每组的测试结果 /* 倍 ...

  2. Codeforces961F. k-substrings

    $n \leq 1000000$的字符串,对每一个子串$i$~$n-i+1$,求他最长的一个既是前缀又是后缀的子串. 这题要求的东西具有“对称性”,不充分利用难以解决.这里的“对称性”不仅指询问是对称 ...

  3. ADO:防止更新的数据含有单引号而出错

    原文发布时间为:2008-08-01 -- 来源于本人的百度文章 [由搬家工具导入] public void Update( string au_lname, string zip,string au ...

  4. Codeforces 713C Sonya and Problem Wihtout a Legend(DP)

    题目链接   Sonya and Problem Wihtout a Legend 题意  给定一个长度为n的序列,你可以对每个元素进行$+1$或$-1$的操作,每次操作代价为$1$. 求把原序列变成 ...

  5. HDU 5893 List wants to travel(树链剖分+线段树)

    题目链接 HDU5893 $2016$年$ICPC$沈阳网络赛的$B$题.这道题其和 BZOJ2243 基本一样 那道题我也写了题解 点这里 两道题的区别就是$BZOJ$这题是点的权值,这道题是边权. ...

  6. HDU 5636 Shortest Path(Floyd)

    题目链接  HDU5636 n个点,其中编号相邻的两个点之间都有一条长度为1的边,然后除此之外还有3条长度为1的边. m个询问,每次询问求两个点之前的最短路. 我们把这三条边的6个点两两算最短路, 然 ...

  7. Idea Failed to read artifact descriptor for xx:jar:unknown

    网上的解决方案: 根据网上说明添加了maven命令clean compile install -Dmaven.test.skip=true,与我遇到的问题不同 有的方法猜测可以通过,但是没时间测试了 ...

  8. jfinal使用idea启动 访问报404 action not found

    公司一个项目,在eclipse里面启动正常,换到idea里面启动后,启动没有报错,但是访问的时候会提示404 action not found. 百度了很多种解决方法 都没有解决. 今天脑子一转,想到 ...

  9. Spring MVC集成Spring Data Reids和Spring Session实现Session共享出现:No bean named 'springSessionRepositoryFilter' available

    出现这个问题:No bean named 'springSessionRepositoryFilter' available的的原因: 1.Spring MVC加载了多个配置文件导致的,并不是版本问题 ...

  10. vueSSR渲染原理

    优点:利于搜索引擎,解决白屏问题,因为正常情况下在index.html文件中只有一个简单的标签,没有内容,不利于爬虫搜索 场景:交互少,数据多,例如新闻,博客,论坛类等 原理:相当于服务端前面加了一层 ...