前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据科学 IPython 笔记本 7.11 聚合和分组

数据科学 IPython 笔记本 7.11 聚合和分组

作者头像
ApacheCN_飞龙
发布2022-06-03 17:23:03
3.6K0
发布2022-06-03 17:23:03
举报
文章被收录于专栏:信数据得永生

7.11 聚合和分组

原文:Aggregation and Grouping 译者:飞龙 协议:CC BY-NC-SA 4.0 本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。

大数据分析的必要部分是有效的总结:计算聚合,如sum()mean()median()min()max(),其中单个数字提供了大数据集的潜在本质的见解。在本节中,我们将探讨 Pandas 中的聚合,从类似于我们在 NumPy 数组中看到的简单操作,到基于groupby概念的更复杂的操作。

为方便起见,我们将使用display魔术函数,和我们在前面部分中看到的相同:

代码语言:javascript
复制
import numpy as np
import pandas as pd

class display(object):
    """Display HTML representation of multiple objects"""
    template = """<div style="float: left; padding: 10px;">
    <p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
    </div>"""
    def __init__(self, *args):
        self.args = args
        
    def _repr_html_(self):
        return '\n'.join(self.template.format(a, eval(a)._repr_html_())
                         for a in self.args)
    
    def __repr__(self):
        return '\n\n'.join(a + '\n' + repr(eval(a))
                           for a in self.args)

行星数据

在这里,我们将使用行星数据集,可通过 Seaborn 软件包获得(参见“可视化与 Seaborn”)。它提供了天文学家在其他恒星周围发现的行星信息(称为太阳系外行星或简称系外行星)。可以使用简单的 Seaborn 命令下载它:

代码语言:javascript
复制
import seaborn as sns
planets = sns.load_dataset('planets')
planets.shape

# (1035, 6)

planets.head()

method

number

orbital_period

mass

distance

year

0

Radial Velocity

1

269.300

7.10

77.40

2006

1

Radial Velocity

1

874.774

2.21

56.95

2008

2

Radial Velocity

1

763.000

2.60

19.84

2011

3

Radial Velocity

1

326.030

19.40

110.62

2007

4

Radial Velocity

1

516.220

10.50

119.47

2009

这包含了截至 2014 年发现的 1,000 多颗系外行星的一些细节。

Pandas 中的简单聚合

之前,我们研究了一些可用于 NumPy 数组的数据聚合(“聚合:最小,最大和之间的任何东西”)。与一维 NumPy 数组一样,对于 Pandas Series,聚合返回单个值:

代码语言:javascript
复制
rng = np.random.RandomState(42)
ser = pd.Series(rng.rand(5))
ser

'''
0    0.374540
1    0.950714
2    0.731994
3    0.598658
4    0.156019
dtype: float64
'''

ser.sum()

# 2.8119254917081569

ser.mean()

# 0.56238509834163142

对于DataFrame,默认情况下聚合返回每列的结果:

代码语言:javascript
复制
df = pd.DataFrame({'A': rng.rand(5),
                   'B': rng.rand(5)})
df

A

B

0

0.155995

0.020584

1

0.058084

0.969910

2

0.866176

0.832443

3

0.601115

0.212339

4

0.708073

0.181825

代码语言:javascript
复制
df.mean()

'''
A    0.477888
B    0.443420
dtype: float64
'''

通过指定axis参数,你可以按行聚合:

代码语言:javascript
复制
df.mean(axis='columns')

'''
0    0.088290
1    0.513997
2    0.849309
3    0.406727
4    0.444949
dtype: float64
'''

Pandas SeriesDataFrame包含“聚合:最小,最大和之间的任何东西”中提到的所有常见聚合;另外,还有一个方便的方法describe(),它为每列计算几个常见聚合并返回结果。

让我们在行星数据上使用它,现在删除带有缺失值的行:

代码语言:javascript
复制
planets.dropna().describe()

number

orbital_period

mass

distance

year

count

498.00000

498.000000

498.000000

498.000000

498.000000

mean

1.73494

835.778671

2.509320

52.068213

2007.377510

std

1.17572

1469.128259

3.636274

46.596041

4.167284

min

1.00000

1.328300

0.003600

1.350000

1989.000000

25%

1.00000

38.272250

0.212500

24.497500

2005.000000

50%

1.00000

357.000000

1.245000

39.940000

2009.000000

75%

2.00000

999.600000

2.867500

59.332500

2011.000000

max

6.00000

17337.500000

25.000000

354.000000

2014.000000

这可以是开始了解数据集的整体属性的有用方法。 例如,我们在year列中看到,虽然早在 1989 年就发现了系外行星,但是一半的已知系外行星直到 2010 年或之后才发现了。这主要得益于开普勒任务,这是一种专门设计的太空望远镜,用于寻找其他恒星周围的遮蔽行星。

下表总结了其他一些内置的 Pandas 聚合:

聚合

描述

count()

项目总数

first(), last()

第一个和最后一个项目

mean(), median()

均值和中值

min(), max()

最小和最大值

std(), var()

标准差和方差

mad()

平均绝对偏差

prod()

所有项目的积

sum()

所有项目的和

这些都是DataFrameSeries对象的方法。

然而,要深入探索数据,简单的聚合通常是不够的。数据汇总的下一级是groupby操作,它允许你快速有效地计算数据子集的聚合。

分组:分割,应用和组合

简单的聚合可以为你提供数据集的风格,但我们通常更愿意在某些标签或索引上有条件地聚合:这是在所谓的groupby操作中实现的。名称group by来自 SQL 数据库语言中的一个命令,但使用 Rstats 的作者 Hadley Wickham 创造的术语:分割(split),应用(apply)和组合(combine)来思考它,可能更有启发性。

分割,应用和组合

这是分割-应用-组合操作的规则示例,其中“应用”是汇总聚合,如下图所示:

这清楚地表明groupby完成了什么:

  • “分割”步骤涉及根据指定键的值打破和分组DataFrame
  • “应用”步骤涉及计算单个组内的某些函数,通常是聚合,转换或过滤。
  • “组合”步骤将这些操作的结果合并到输出数组中。

虽然这肯定可以使用前面介绍的掩码,聚合和合并命令的某种组合来手动完成,但一个重要的认识是,中间的分割不需要显式实例化。相反,GroupBy可以(经常)只遍历单次数据来执行此操作,在此过程中更新每个组的总和,均值,计数,最小值或其他聚合。GroupBy的强大之处在于,它抽象了这些步骤:用户不需要考虑计算如何在背后完成,而是考虑整个操作。

作为一个具体的例子,让我们看看,将 Pandas 用于此图中所示的计算。我们首先创建输入DataFrame

代码语言:javascript
复制
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'data': range(6)}, columns=['key', 'data'])
df

key

data

0

A

0

1

B

1

2

C

2

3

A

3

4

B

4

5

C

5

最基本的分割-应用-组合操作可以使用DataFramegroupby()方法计算,传递所需键列的名称:

代码语言:javascript
复制
df.groupby('key')

# <pandas.core.groupby.DataFrameGroupBy object at 0x117272160>

请注意,返回值不是一组DataFrame,而是一个DataFrameGroupBy对象。这个对象就是神奇之处:你可以把它想象成DataFrame的特殊视图,它做好了准备来深入挖掘分组,但在应用聚合之前不会进行实际计算。这种“惰性求值”方式意味着,可以以对用户几乎透明的方式,非常有效地实现常见聚合。

为了产生结果,我们可以将聚合应用于这个DataFrameGroupBy对象,该对象将执行适当的应用/组合步骤来产生所需的结果:

代码语言:javascript
复制
df.groupby('key').sum()

data

key

A

3

B

5

C

7

`sum()方法只是这里的一种可能性; 你可以应用几乎任何常见的 Pandas 或 NumPy 聚合函数,以及几乎任何有效的DataFrame``操作,我们将在下面的讨论中看到。

GroupBy对象

GroupBy对象是一个非常灵活的抽象。在许多方面,你可以简单地将它视为DataFrame的集合,它可以解决困难的问题。让我们看一些使用行星数据的例子。

也许由GroupBy提供的最重要的操作是聚合,过滤,转换和应用。 我们将在“聚合,过滤,转换,应用”中,更全面地讨论这些内容,但在此之前,我们将介绍一些其他功能,它们可以与基本的GroupBy操作配合使用。

列索引

`GroupBy对象支持列索引,方式与DataFrame相同,并返回修改后的GroupBy``对象。例如:

代码语言:javascript
复制
planets.groupby('method')

# <pandas.core.groupby.DataFrameGroupBy object at 0x1172727b8>

planets.groupby('method')['orbital_period']

# <pandas.core.groupby.SeriesGroupBy object at 0x117272da0>

在这里,我们通过列名的引用,从原始的DataFrame组中选择了一个特定的Series组。与GroupBy对象一样,在我们调用对象上的聚合之前,不会进行任何计算:

代码语言:javascript
复制
planets.groupby('method')['orbital_period'].median()

'''
method
Astrometry                         631.180000
Eclipse Timing Variations         4343.500000
Imaging                          27500.000000
Microlensing                      3300.000000
Orbital Brightness Modulation        0.342887
Pulsar Timing                       66.541900
Pulsation Timing Variations       1170.000000
Radial Velocity                    360.200000
Transit                              5.714932
Transit Timing Variations           57.011000
Name: orbital_period, dtype: float64
'''

这给出了每种方法对其敏感的,轨道周期(以天为单位)的一般尺度的概念。

分组上的迭代

GroupBy对象支持分组上的直接迭代,将每个组作为SeriesDataFrame返回:

代码语言:javascript
复制
for (method, group) in planets.groupby('method'):
    print("{0:30s} shape={1}".format(method, group.shape))
    
'''
Astrometry                     shape=(2, 6)
Eclipse Timing Variations      shape=(9, 6)
Imaging                        shape=(38, 6)
Microlensing                   shape=(23, 6)
Orbital Brightness Modulation  shape=(3, 6)
Pulsar Timing                  shape=(5, 6)
Pulsation Timing Variations    shape=(1, 6)
Radial Velocity                shape=(553, 6)
Transit                        shape=(397, 6)
Transit Timing Variations      shape=(4, 6)
'''

这对于手动执行某些操作非常有用,尽管使用内置的apply函数通常要快得多,我们之后将讨论这个函数。

分发方法

通过一些 Python 类魔术,任何未由GroupBy对象显式实现的方法都将被传递给分组,并在它上面调用,无论它们是DataFrame还是Series对象。例如,你可以使用DataFramedescribe()方法,来执行一组聚合,它们描述数据中的每个分组:

代码语言:javascript
复制
planets.groupby('method')['year'].describe().unstack()

count

mean

std

min

25%

50%

75%

max

method

Astrometry

2.0

2011.500000

2.121320

2010.0

2010.75

2011.5

2012.25

2013.0

Eclipse Timing Variations

9.0

2010.000000

1.414214

2008.0

2009.00

2010.0

2011.00

2012.0

Imaging

38.0

2009.131579

2.781901

2004.0

2008.00

2009.0

2011.00

2013.0

Microlensing

23.0

2009.782609

2.859697

2004.0

2008.00

2010.0

2012.00

2013.0

Orbital Brightness Modulation

3.0

2011.666667

1.154701

2011.0

2011.00

2011.0

2012.00

2013.0

Pulsar Timing

5.0

1998.400000

8.384510

1992.0

1992.00

1994.0

2003.00

2011.0

Pulsation Timing Variations

1.0

2007.000000

NaN

2007.0

2007.00

2007.0

2007.00

2007.0

Radial Velocity

553.0

2007.518987

4.249052

1989.0

2005.00

2009.0

2011.00

2014.0

Transit

397.0

2011.236776

2.077867

2002.0

2010.00

2012.0

2013.00

2014.0

Transit Timing Variations

4.0

2012.500000

1.290994

2011.0

2011.75

2012.5

2013.25

2014.0

查看此表格有助于我们更好地理解数据:例如,绝大多数行星都是通过径向速度(Radial Velocity)和 Transit Method 发现的,尽管后者在过去十年中变得普遍(由于新的,更精确的望远镜)。最新的方法似乎是 Transit Timing Variation 和 Orbital Brightness Modulation,它们直到 2011 年才被用于发现新的行星。

这只是分发方法的一个例子。请注意,它们被应用于每个单独的分组,然后在```GroupBy中组合并返回结果。同样,任何有效的DataFrameSeries方法都可以用在相应的GroupBy``对象上,这允许一些非常灵活和强大的操作!

聚合,过滤,转换,应用

前面的讨论主要关注组合操作的聚合,但还有更多选择。特别是GroupBy对象有aggregate()filter()transform()apply()方法,在组合分组数据之前,它们有效实现各种实用操作。

出于以下小节的目的,我们将使用这个DataFrame

代码语言:javascript
复制
rng = np.random.RandomState(0)
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'data1': range(6),
                   'data2': rng.randint(0, 10, 6)},
                   columns = ['key', 'data1', 'data2'])
df

key

data1

data2

0

A

0

5

1

B

1

0

2

C

2

3

3

A

3

3

4

B

4

7

5

C

5

9

聚合

我们现在熟悉GroupBy聚合与`sum()median()等,但aggregate()``方法允许更多的灵活性。它可以接受字符串,函数或其列表,并一次计算所有聚合。这是一个结合所有这些的快速示例:

代码语言:javascript
复制
df.groupby('key').aggregate(['min', np.median, max])

data1

data2

min

median

key

A

0

1.5

B

1

2.5

C

2

3.5

另一个有用的方案是传递字典,将列名称映射到要应用于该列的操作:

代码语言:javascript
复制
df.groupby('key').aggregate({'data1': 'min',
                             'data2': 'max'})

data1

data2

key

A

0

5

B

1

7

C

2

9

过滤

过滤操作允许你根据分组的属性来删除数据。 例如,我们可能希望保留标准差大于某个临界值的所有分组:

代码语言:javascript
复制
def filter_func(x):
    return x['data2'].std() > 4

display('df', "df.groupby('key').std()", "df.groupby('key').filter(filter_func)")

df

key

data1

data2

0

A

0

5

1

B

1

0

2

C

2

3

3

A

3

3

4

B

4

7

5

C

5

9

df.groupby('key').std()

data1

data2

key

A

2.12132

1.414214

B

2.12132

4.949747

C

2.12132

4.242641

df.groupby('key').filter(filter_func)

key

data1

data2

1

B

1

0

2

C

2

3

4

B

4

7

5

C

5

9

filter函数应返回一个布尔值,指定组是否通过过滤。 这里因为组 A 没有大于 4 的标准差,所以从结果中删除它。

转换

虽然聚合必须返回数据的简化版本,但转换可以返回完整数据的某些重新组合的转换版本。对于这种变换,输出与输入的形状相同。一个常见的例子是通过减去分组均值来使数据居中:

代码语言:javascript
复制
df.groupby('key').transform(lambda x: x - x.mean())

data1

data2

0

-1.5

1.0

1

-1.5

-3.5

2

-1.5

-3.0

3

1.5

-1.0

4

1.5

3.5

5

1.5

3.0

apply()方法

apply()方法允许你将任意函数应用于分组结果。该函数应该接受DataFrame,并返回一个 Pandas 对象(例如,DataFrameSeries)或一个标量;组合操作将根据返回的输出类型进行调整。

例如,这里是一个apply(),它按照第二列的总和将第一列标准化:

代码语言:javascript
复制
def norm_by_data2(x):
    # x 是分组值的数据帧
    x['data1'] /= x['data2'].sum()
    return x

display('df', "df.groupby('key').apply(norm_by_data2)")

df

key

data1

data2

0

A

0

5

1

B

1

0

2

C

2

3

3

A

3

3

4

B

4

7

5

C

5

9

df.groupby('key').apply(norm_by_data2)

key

data1

data2

0

A

0.000000

5

1

B

0.142857

0

2

C

0.166667

3

3

A

0.375000

3

4

B

0.571429

7

5

C

0.416667

9

GroupBy中的apply()非常灵活:唯一的规则是,函数接受一个DataFrame并返回一个 Pandas 对象或标量;在中间做什么取决于你!

指定分割键

在之前介绍的简单示例中,我们将DataFrame拆分为单个列名。这只是定义分组的众多选项之一,我们将在此处介绍分组规则的其他选项。

提供分组键的列表,数组,系列或索引

键可以是任何序列或列表,其长度匹配DataFrame的长度。例如:

代码语言:javascript
复制
L = [0, 1, 0, 1, 2, 0]
display('df', 'df.groupby(L).sum()')

df

key

data1

data2

0

A

0

5

1

B

1

0

2

C

2

3

3

A

3

3

4

B

4

7

5

C

5

9

df.groupby(L).sum()

data1

data2

0

7

17

1

4

3

2

4

7

当然,这意味着还有另一种更冗长的方式来完成之前的df.groupby('key')

代码语言:javascript
复制
display('df', "df.groupby(df['key']).sum()")

df

key

data1

data2

0

A

0

5

1

B

1

0

2

C

2

3

3

A

3

3

4

B

4

7

5

C

5

9

df.groupby(df['key']).sum()

data1

data2

key

A

3

8

B

5

7

C

7

12

将索引映射到分组的字典或序列

另一种方法是提供将索引值映射到分组键的字典:

代码语言:javascript
复制
df2 = df.set_index('key')
mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}
display('df2', 'df2.groupby(mapping).sum()')

df2

data1

data2

key

A

0

5

B

1

0

C

2

3

A

3

3

B

4

7

C

5

9

df2.groupby(mapping).sum()

data1

data2

consonant

12

19

vowel

3

8

任何 Python 函数

与映射类似,你可以传递任何接受索引值并输出分组的 Python 函数:

代码语言:javascript
复制
display('df2', 'df2.groupby(str.lower).mean()')

df2

data1

data2

key

A

0

5

B

1

0

C

2

3

A

3

3

B

4

7

C

5

9

df2.groupby(str.lower).mean()

data1

data2

a

1.5

4.0

b

2.5

3.5

c

3.5

6.0

有效键的列表

此外,可以组合任何前面选择的键,来在多重索引上分组:

代码语言:javascript
复制
df2.groupby([str.lower, mapping]).mean()

data1

data2

a

vowel

1.5

4.0

b

consonant

2.5

3.5

c

consonant

3.5

6.0

分组示例

作为一个例子,在几行 Python 代码中,我们可以将所有这些放在一起,并通过methoddecade计算发现的行星:

代码语言:javascript
复制
decade = 10 * (planets['year'] // 10)
decade = decade.astype(str) + 's'
decade.name = 'decade'
planets.groupby(['method', decade])['number'].sum().unstack().fillna(0)

decade

1980s

1990s

2000s

2010s

method

Astrometry

0.0

0.0

0.0

2.0

Eclipse Timing Variations

0.0

0.0

5.0

10.0

Imaging

0.0

0.0

29.0

21.0

Microlensing

0.0

0.0

12.0

15.0

Orbital Brightness Modulation

0.0

0.0

0.0

5.0

Pulsar Timing

0.0

9.0

1.0

1.0

Pulsation Timing Variations

0.0

0.0

1.0

0.0

Radial Velocity

1.0

52.0

475.0

424.0

Transit

0.0

0.0

64.0

712.0

Transit Timing Variations

0.0

0.0

0.0

9.0

这展示了在查看真实数据集时,结合我们讨论过的许多操作的强大威力。我们立即大致了解,过去几十年内行星何时以及如何被发现!

在这里,我建议深入研究这几行代码,并评估各个步骤,来确保你准确了解它们对结果的作用。 这当然是一个有点复杂的例子,但理解这些部分将为你提供,探索自己的数据的类似方法。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 7.11 聚合和分组
    • 行星数据
      • Pandas 中的简单聚合
        • 分组:分割,应用和组合
          • 分割,应用和组合
          • GroupBy对象
          • 聚合,过滤,转换,应用
          • 指定分割键
          • 分组示例
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档