Pandas GroupBy:Python 数据分组指南

举报
Yuchuan 发表于 2021/12/22 14:05:20 2021/12/22
【摘要】 在本教程中,您已经介绍了大量关于 的基础知识.groupby(),包括其设计、API 以及如何将方法链接在一起以在适合您目的的输出中获取数据。 你已经学到: 如何在真实数据上使用 Pandas GroupBy 操作 如何拆分申请,结合业务工作的链,以及如何可以分解成步骤 如何根据意图和结果将 Pandas GroupBy 的方法放入不同的类别 .groupby()您可以在一个教程中涵盖更多内

目录

无论您是刚开始使用Pandas并想掌握其核心设施之一,还是希望填补您对 的理解中的一些空白.groupby(),本教程都将帮助您从头开始分解和可视化Pandas GroupBy操作完成。

本教程旨在补充官方文档,您将在其中看到独立的、一口大小的示例。但是,在这里,您将关注三个使用真实世界数据集的更复杂的演练。

在本教程中,您将介绍:

  • 如何在真实数据上使用 Pandas GroupBy 操作
  • 如何拆分申请,结合业务工作的链
  • 如何分解拆分申请,结合链入步骤
  • 如何根据意图和结果将 Pandas GroupBy 对象的方法放入不同的类别

本教程假设您对 Pandas 本身有一些经验,包括如何将 CSV 文件作为 Pandas 对象读取到内存中read_csv()。如果您需要复习,请查看使用 PandasPandas读取 CSV :如何读取和写入文件

Housekeeping

本教程中的所有代码都是使用 Pandas 0.25.0在CPython 3.7.2 shell 中生成的。在继续之前,请确保您在新的虚拟环境中拥有最新版本的 Pandas :

$ python -m venv pandas-gb-tut
$ source ./pandas-gb-tut/bin/activate
$ python -m pip install pandas

此处的示例还使用了一些经过调整的 Pandas选项以获得更友好的输出:

import pandas as pd

# Use 3 decimal places in output display
pd.set_option("display.precision", 3)

# Don't wrap repr(DataFrame) across additional lines
pd.set_option("display.expand_frame_repr", False)

# Set max rows displayed in output to 25
pd.set_option("display.max_rows", 25)

您可以将这些添加到启动文件中,以便在每次启动解释器时自动设置它们。

在本教程中,您将关注三个数据集:

  1. 美国国会的数据集包含了国会的历史成员公共信息和说明的几个基本功能.groupby()
  2. 空气质量数据集包含周期性气体传感器读数。这将允许您使用浮点数和时间序列数据。
  3. 新闻聚合数据集,其持有数十万篇新闻文章的元数据。您将使用字符串并进行基于 groupby 的文本处理。

下载后.zip,您可以将其解压缩到当前目录:

$ unzip -q -d groupby-data groupby-data.zip

-d选项可让您将内容提取到新文件夹中:

./
│
└── groupby-data/
    │
    ├── legislators-historical.csv
    ├── airqual.csv
    └── news.csv

设置完成后,您就可以开始了!

示例 1:国会数据集

您将通过剖析国会历史成员的数据集直接进入事物。您可以阅读CSV文件转换成 pandasDataFrameread_csv()

import pandas as pd

dtypes = {
    "first_name": "category",
    "gender": "category",
    "type": "category",
    "state": "category",
    "party": "category",
}
df = pd.read_csv(
    "groupby-data/legislators-historical.csv",
    dtype=dtypes,
    usecols=list(dtypes) + ["birthday", "last_name"],
    parse_dates=["birthday"]
)

该数据集包含成员的名字和姓氏、出生日期、性别、类型("rep"众议院或"sen"参议院)、美国州和政党。您可以使用df.tail()查看数据集的最后几行:

>>>
>>> df.tail()
      last_name first_name   birthday gender type state       party
11970   Garrett     Thomas 1972-03-27      M  rep    VA  Republican
11971    Handel      Karen 1962-04-18      F  rep    GA  Republican
11972     Jones     Brenda 1959-10-24      F  rep    MI    Democrat
11973    Marino        Tom 1952-08-15      M  rep    PA  Republican
11974     Jones     Walter 1943-02-10      M  rep    NC  Republican

DataFrame使用分类dtypes空间效率

>>>
>>> df.dtypes
last_name             object
first_name          category
birthday      datetime64[ns]
gender              category
type                category
state               category
party               category
dtype: object

您可以看到数据集的大多数列都具有 type category,这减少了您机器上的内存负载。

The “Hello, World!” Pandas GroupBy

现在您已经熟悉了数据集,您将从“Hello, World!”开始。用于 Pandas GroupBy 操作。在数据集的整个历史中,每个州的国会议员人数是多少?在SQL 中,您可以通过以下SELECT语句找到此答案:

SELECT state, count(name)
FROM df
GROUP BY state
ORDER BY state;

这是 Pandas 中的近似值:

>>>
>>> n_by_state = df.groupby("state")["last_name"].count()
>>> n_by_state.head(10)
state
AK     16
AL    206
AR    117
AS      2
AZ     48
CA    361
CO     90
CT    240
DC      2
DE     97
Name: last_name, dtype: int64

您调用.groupby()并传递要分组的列的名称,即"state". 然后,您使用["last_name"]指定要对其执行实际聚合的列。

您可以传递的不仅仅是单个列名.groupby()作为第一个参数。您还可以指定以下任何一项:

  • list多个列名的A
  • Adict或熊猫Series
  • 一个NumPy 数组或 Pandas Index,或一个类似数组的可迭代对象

下面是一个在两列上联合分组的示例,它查找按州然后按性别细分的国会议员人数:

>>>
>>> df.groupby(["state", "gender"])["last_name"].count()
state  gender
AK     M          16
AL     F           3
       M         203
AR     F           5
       M         112
                ...
WI     M         196
WV     F           1
       M         119
WY     F           2
       M          38
Name: last_name, Length: 104, dtype: int64

类似的SQL 查询如下所示:

SELECT state, gender, count(name)
FROM df
GROUP BY state, gender
ORDER BY state, gender;

正如您接下来将看到的,.groupby()可比较的SQL语句是近亲,但它们的功能通常并不相同。

注意:这里 Pandas GroupBy 与 SQL 的比较还有一个微小的区别:在 Pandas 版本中,一些州只显示一种性别。在我们开发本教程时,我们在 Pandas 源代码中遇到了一个小而棘手的错误,它不能observed很好地处理某些类型数据的参数。从不畏惧!在这种特殊情况下有一些解决方法

Pandas GroupBy 与 SQL

现在是介绍 Pandas GroupBy 操作和上述 SQL 查询之间的一个显着区别的好时机。SQL 查询的结果集包含三列:

  1. state
  2. gender
  3. count

在 Pandas 版本中,分组列默认被推入MultiIndex结果中Series

>>>
>>> n_by_state_gender = df.groupby(["state", "gender"])["last_name"].count()
>>> type(n_by_state_gender)
<class 'pandas.core.series.Series'>
>>> n_by_state_gender.index[:5]
MultiIndex([('AK', 'M'),
            ('AL', 'F'),
            ('AL', 'M'),
            ('AR', 'F'),
            ('AR', 'M')],
           names=['state', 'gender'])

要更接近地模拟 SQL 结果并将分组列推回到结果中的列中,您可以使用as_index=False

>>>
>>> df.groupby(["state", "gender"], as_index=False)["last_name"].count()
    state gender  last_name
0      AK      F        NaN
1      AK      M       16.0
2      AL      F        3.0
3      AL      M      203.0
4      AR      F        5.0
..    ...    ...        ...
111    WI      M      196.0
112    WV      F        1.0
113    WV      M      119.0
114    WY      F        2.0
115    WY      M       38.0

[116 rows x 3 columns]

这将生成DataFrame带有三列和 a 的 a RangeIndex,而不是Series带有 a 的 a MultiIndex。简而言之,使用as_index=False将使您的结果更接近于类似操作的默认 SQL 输出。

注意:在 中df.groupby(["state", "gender"])["last_name"].count(),您也可以使用.size()代替.count(),因为您知道没有NaN姓氏。使用.count()排除NaN值,而.size()包括一切,NaN或不。

另请注意,上面的 SQL 查询显式使用ORDER BY,而.groupby()没有使用。那是因为.groupby()默认情况下是通过它的 parameter 来做到这一点的sortTrue除非你另有说明:

>>>
>>> # Don't sort results by the sort keys
>>> df.groupby("state", sort=False)["last_name"].count()
state
DE      97
VA     432
SC     251
MD     305
PA    1053
      ...
AK      16
PI      13
VI       4
GU       4
AS       2
Name: last_name, Length: 58, dtype: int64

接下来,您将深入了解.groupby()实际生成的对象。

Pandas GroupBy 的工作原理

在深入了解细节之前,先退后一步看看.groupby()自己:

>>>
>>> by_state = df.groupby("state")
>>> print(by_state)
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x107293278>

那是什么DataFrameGroupBy东西?它.__str__()没有给你太多关于它实际是什么或它是如何工作的信息。一个DataFrameGroupBy物体很难缠住你的头的原因是它本质上是懒惰的。除非您这么说,否则它不会真正执行任何操作来产生有用的结果。

经常一起使用的一个术语.groupby()split-apply-combine。这是指一连串的三个步骤:

  1. 将表拆分为组
  2. 对每个较小的表应用一些操作
  3. 合并结果

检查可能很困难,df.groupby("state")因为在您对结果对象执行某些操作之前,它几乎不执行任何这些操作。同样,Pandas GroupBy 对象是lazy。它实际上会延迟拆分-应用-组合过程的每个部分,直到您对其调用方法为止。

那么,如果您看不到其中任何一个单独发生,您如何在心理上将拆分、应用和组合阶段分开?检查 Pandas GroupBy 对象并查看拆分操作的一种有用方法是对其进行迭代。这是在DataFrame 中实现DataFrameGroupBy.__iter__()并生成( group , ) 对的迭代器DataFrame

>>>
>>> for state, frame in by_state:
...     print(f"First 2 entries for {state!r}")
...     print("------------------------")
...     print(frame.head(2), end="\n\n")
...
First 2 entries for 'AK'
------------------------
     last_name first_name   birthday gender type state        party
6619    Waskey      Frank 1875-04-20      M  rep    AK     Democrat
6647      Cale     Thomas 1848-09-17      M  rep    AK  Independent

First 2 entries for 'AL'
------------------------
    last_name first_name   birthday gender type state       party
912   Crowell       John 1780-09-18      M  rep    AL  Republican
991    Walker       John 1783-08-12      M  sen    AL  Republican

如果您正在处理具有挑战性的聚合问题,那么对 Pandas GroupBy 对象进行迭代可能是可视化split-apply-combine的拆分部分的好方法。

还有一些其他方法和属性可让您查看各个组及其拆分。该.groups属性会给你一个{group name: group label}成对的字典。例如,by_state是以dict状态为键的。这是"PA"键的值:

>>>
>>> by_state.groups["PA"]
Int64Index([    4,    19,    21,    27,    38,    57,    69,    76,    84,
               88,
            ...
            11842, 11866, 11875, 11877, 11887, 11891, 11932, 11945, 11959,
            11973],
           dtype='int64', length=1053)

每个值都是属于该特定组的行的索引位置序列。在上面的输出中,41921df状态等于“PA”的第一个索引。

您还可以将其.get_group()用作从单个组向下钻取到子表的方法:

>>>
>>> by_state.get_group("PA")
      last_name first_name   birthday gender type state                party
4        Clymer     George 1739-03-16      M  rep    PA                  NaN
19       Maclay    William 1737-07-20      M  sen    PA  Anti-Administration
21       Morris     Robert 1734-01-20      M  sen    PA   Pro-Administration
27      Wynkoop      Henry 1737-03-02      M  rep    PA                  NaN
38       Jacobs     Israel 1726-06-09      M  rep    PA                  NaN
...         ...        ...        ...    ...  ...   ...                  ...
11891     Brady     Robert 1945-04-07      M  rep    PA             Democrat
11932   Shuster       Bill 1961-01-10      M  rep    PA           Republican
11945   Rothfus      Keith 1962-04-25      M  rep    PA           Republican
11959  Costello       Ryan 1976-09-07      M  rep    PA           Republican
11973    Marino        Tom 1952-08-15      M  rep    PA           Republican

这实际上等同于使用.loc[]. 你可以用类似的东西得到相同的输出df.loc[df["state"] == "PA"]

注意:我使用通用术语Pandas GroupBy 对象来指代一个DataFrameGroupBy对象或一个SeriesGroupBy对象,它们之间有很多共同点。

还值得一提的是,通过为您传递的每个键构建一个类实例,它.groupby()确实完成了一些(但不是全部)拆分工作Grouping。但是,BaseGrouper持有这些分组的类的许多方法都是惰性调用的,而不是 at __init__(),并且许多方法还使用缓存属性设计。

接下来,应用部分呢?您可以将过程的这一步视为将相同的操作(或可调用)应用于拆分阶段生成的每个“子表”。(我不知道“子表”是不是技术术语,但我还没有找到更好的🤷‍♂️)

从 Pandas GroupBy 对象中by_state,您可以获取初始美国状态并DataFrame使用next(). 当您迭代 Pandas GroupBy 对象时,您将获得可以解压缩为两个变量的对:

>>>
>>> state, frame = next(iter(by_state))  # First tuple from iterator
>>> state
'AK'
>>> frame.head(3)
     last_name first_name   birthday gender type state        party
6619    Waskey      Frank 1875-04-20      M  rep    AK     Democrat
6647      Cale     Thomas 1848-09-17      M  rep    AK  Independent
7442   Grigsby     George 1874-12-02      M  rep    AK          NaN

现在,回想一下您最初的完整操作:

>>>
>>> df.groupby("state")["last_name"].count()
state
AK      16
AL     206
AR     117
AS       2
AZ      48
...

申请阶段,当应用到你的单,子集DataFrame,应该是这样的:

>>>
>>> frame["last_name"].count()  # Count for state == 'AK'
16

您可以看到结果 16 与AK组合结果中的值相匹配。

最后一步combine是最不言自明的。它只是简单地获取所有子表上所有应用操作的结果,并以直观的方式将它们组合在一起。

示例 2:空气质量数据集

空气质量数据集包含来自意大利的气体传感器元件每小时读数。缺失值在 CSV 文件中用-200表示。您可以使用read_csv()将两列组合成一个时间戳,同时使用其他列的子集:

import pandas as pd

df = pd.read_csv(
    "groupby-data/airqual.csv",
    parse_dates=[["Date", "Time"]],
    na_values=[-200],
    usecols=["Date", "Time", "CO(GT)", "T", "RH", "AH"]
).rename(
    columns={
        "CO(GT)": "co",
        "Date_Time": "tstamp",
        "T": "temp_c",
        "RH": "rel_hum",
        "AH": "abs_hum",
    }
).set_index("tstamp")

这会产生一个DataFrame带有 aDatetimeIndex和四float列的:

>>>
>>> df.head()
                      co  temp_c  rel_hum  abs_hum
tstamp
2004-03-10 18:00:00  2.6    13.6     48.9    0.758
2004-03-10 19:00:00  2.0    13.3     47.7    0.726
2004-03-10 20:00:00  2.2    11.9     54.0    0.750
2004-03-10 21:00:00  2.2    11.0     60.0    0.787
2004-03-10 22:00:00  1.6    11.2     59.6    0.789

此处,co是该小时的平均一氧化碳读数,而temp_crel_humabs_hum分别是该小时内的平均摄氏温度、相对湿度和绝对湿度。观察从 2004 年 3 月到 2005 年 4 月:

>>>
>>> df.index.min()
Timestamp('2004-03-10 18:00:00')
>>> df.index.max()
Timestamp('2005-04-04 14:00:00')

到目前为止,您已经通过将列的名称指定为 来对列进行分组str,例如df.groupby("state")。但是.groupby()比这更灵活!你会看到接下来如何。

对派生数组进行分组

早些时候你看到第一个参数 to.groupby()可以接受几个不同的参数:

  • 一列或列列表
  • Adict或熊猫Series
  • 一个 NumPy 数组或 Pandas Index,或一个类似数组的可迭代对象

您可以利用最后一个选项来按星期几进行分组。您可以使用索引.day_name()生成Index字符串的 Pandas 。以下是前十个观察结果:

>>>
>>> day_names = df.index.day_name()
>>> type(day_names)
<class 'pandas.core.indexes.base.Index'>
>>> day_names[:10]
Index(['Wednesday', 'Wednesday', 'Wednesday', 'Wednesday', 'Wednesday',
       'Wednesday', 'Thursday', 'Thursday', 'Thursday', 'Thursday'],
      dtype='object', name='tstamp')

然后,您可以获取此对象并将其用作.groupby()密钥。在Pandas 中day_names类似数组的。这是一个一维的标签序列。

注意:对于 PandasSeries而不是Index,您需要.dt访问器来访问像.day_name(). 如果ser是您的Series,那么您需要ser.dt.day_name()

现在,通过该对象.groupby()查找co一周中某天的平均一氧化碳 ( ) 读数:

>>>
>>> df.groupby(day_names)["co"].mean()
tstamp
Friday       2.543
Monday       2.017
Saturday     1.861
Sunday       1.438
Thursday     2.456
Tuesday      2.382
Wednesday    2.401
Name: co, dtype: float64

split-apply-combine 过程的行为与以前大致相同,只是这次拆分是在人工创建的列上完成的。此列不存在于 DataFrame 本身中,而是从中派生而来。

如果您不仅想按一周中的某天分组,还想按一天中的某个小时分组怎么办?那个结果应该有7 * 24 = 168观察。为此,您可以传递一个类似数组的对象列表。在这种情况下,您将传递 PandasInt64Index对象:

>>>
>>> hr = df.index.hour
>>> df.groupby([day_names, hr])["co"].mean().rename_axis(["dow", "hr"])
dow        hr
Friday     0     1.936
           1     1.609
           2     1.172
           3     0.887
           4     0.823
                 ...
Wednesday  19    4.147
           20    3.845
           21    2.898
           22    2.102
           23    1.938
Name: co, Length: 168, dtype: float64

这是另一个类似的案例,用于.cut()将温度值分入离散区间:

>>>
>>> bins = pd.cut(df["temp_c"], bins=3, labels=("cool", "warm", "hot"))
>>> df[["rel_hum", "abs_hum"]].groupby(bins).agg(["mean", "median"])
       rel_hum        abs_hum
          mean median    mean median
temp_c
cool    57.651   59.2   0.666  0.658
warm    49.383   49.3   1.183  1.145
hot     24.994   24.1   1.293  1.274

在这种情况下,bins实际上是一个Series

>>>
>>> type(bins)
<class 'pandas.core.series.Series'>
>>> bins.head()
tstamp
2004-03-10 18:00:00    cool
2004-03-10 19:00:00    cool
2004-03-10 20:00:00    cool
2004-03-10 21:00:00    cool
2004-03-10 22:00:00    cool
Name: temp_c, dtype: category
Categories (3, object): [cool < warm < hot]

无论是Series、NumPy 数组还是列表都无关紧要。什么是重要的是,bins仍然作为标签的序列,其中一个coolwarmhot。如果你真的想,那么你也可以使用一个Categorical数组甚至一个普通的list

  • 本机 Python 列表: df.groupby(bins.tolist())
  • Pandas 分类数组: df.groupby(bins.values)

如您所见,它.groupby()很聪明,可以处理许多不同的输入类型。这些中的任何一个都会产生相同的结果,因为它们都用作执行分组和拆分的标签序列。

重采样

您已按df星期几与df.groupby(day_names)["co"].mean(). 现在考虑一些不同的事情。如果您想按观察的年份和季度分组怎么办?这是实现这一目标的一种方法:

>>>
>>> # See an easier alternative below
>>> df.groupby([df.index.year, df.index.quarter])["co"].agg(
...     ["max", "min"]
... ).rename_axis(["year", "quarter"])
               max  min
year quarter
2004 1         8.1  0.3
     2         7.3  0.1
     3         7.5  0.1
     4        11.9  0.1
2005 1         8.7  0.1
     2         5.0  0.3

或者,这整个操作可以通过重采样来表示。重采样的用途之一是作为基于时间的 groupby。您需要做的就是传递一个频率字符串,例如"Q"for "quarterly",其余的由 Pandas 完成:

>>>
>>> df.resample("Q")["co"].agg(["max", "min"])
             max  min
tstamp
2004-03-31   8.1  0.3
2004-06-30   7.3  0.1
2004-09-30   7.5  0.1
2004-12-31  11.9  0.1
2005-03-31   8.7  0.1
2005-06-30   5.0  0.3

通常,当您使用时,.resample()您可以以更简洁的方式表达基于时间的分组操作。结果可能与更详细的.groupby()等价物略有不同,但您经常会发现它.resample()提供了您正在寻找的内容。

示例 3:新闻聚合器数据集

现在,您将使用第三个也是最后一个数据集,该数据集包含数十万条新闻文章的元数据并将它们分组到主题集群中:

import datetime as dt
import pandas as pd

def parse_millisecond_timestamp(ts: int) -> dt.datetime:
    """Convert ms since Unix epoch to UTC datetime instance."""
    return dt.datetime.fromtimestamp(ts / 1000, tz=dt.timezone.utc)

df = pd.read_csv(
    "groupby-data/news.csv",
    sep="\t",
    header=None,
    index_col=0,
    names=["title", "url", "outlet", "category", "cluster", "host", "tstamp"],
    parse_dates=["tstamp"],
    date_parser=parse_millisecond_timestamp,
    dtype={
        "outlet": "category",
        "category": "category",
        "cluster": "category",
        "host": "category",
    },
)

要使用正确的 将其读入内存dyptes,您需要一个辅助函数来解析时间戳列。这是因为它表示为自 Unix 纪元以来的毫秒数,而不是小数秒,这是惯例。与您之前所做的类似,您可以使用 Categoricaldtype对具有相对于列长度而言相对较少的唯一值的列进行有效编码。

数据集的每一行都包含标题、URL、发布网点名称和域,以及发布时间戳。cluster是文章所属主题集群的随机ID。category是新闻类别,包含以下选项:

  • b for business
  • t for science and technology
  • e for entertainment
  • m for health

这是第一行:

>>>
>>> df.iloc[0]
title       Fed official says wea...
url         http://www.latimes.co...
outlet             Los Angeles Times
category                           b
cluster     ddUyU0VZz0BRneMioxUPQ...
host                 www.latimes.com
tstamp      2014-03-10 16:52:50.6...
Name: 1, dtype: object

现在您已经了解了数据,您可以开始提出更复杂的问题。

在中使用 Lambda 函数 .groupby()

该数据集引发了更多可能涉及的问题。我会随机抛出一个有意义的问题:哪些媒体最常谈论美联储?为简单起见,我们假设这需要搜索区分大小写的"Fed". 请记住,这可能会产生一些误报,例如“联邦政府”之类的术语。

要按插座计算提及次数,您可以调用.groupby()插座,然后.apply()在每个组上调用一个函数:

>>>
>>> df.groupby("outlet", sort=False)["title"].apply(
...     lambda ser: ser.str.contains("Fed").sum()
... ).nlargest(10)
outlet
Reuters                         161
NASDAQ                          103
Businessweek                     93
Investing.com                    66
Wall Street Journal \(blog\)     61
MarketWatch                      56
Moneynews                        55
Bloomberg                        53
GlobalPost                       51
Economic Times                   44
Name: title, dtype: int64

让我们分解一下,因为有几个连续的方法调用。像以前一样,您可以通过tuple从 Pandas GroupBy 迭代器中取出第一个组来提取第一个组及其对应的 Pandas 对象:

>>>
>>> title, ser = next(iter(df.groupby("outlet", sort=False)["title"]))
>>> title
'Los Angeles Times'
>>> ser.head()
1       Fed official says weak data caused by weather,...
486            Stocks fall on discouraging news from Asia
1124    Clues to Genghis Khan's rise, written in the r...
1146    Elephants distinguish human voices by sex, age...
1237    Honda splits Acura into its own division to re...
Name: title, dtype: object

在这种情况下,ser是 PandasSeries而不是DataFrame. 那是因为您.groupby()使用["title"]. 这有效地从每个子表中选择了该单列。

接下来是.str.contains("Fed")。这将返回一个布尔值 SeriesTrue表示文章标题在搜索中注册匹配项。果然,第一行开始"Fed official says weak data caused by weather,..."并点亮为True

>>>
>>> ser.str.contains("Fed")
1          True
486       False
1124      False
1146      False
1237      False
          ...
421547    False
421584    False
421972    False
422226    False
422905    False
Name: title, Length: 1976, dtype: bool

下一步就是.sum()这样Series。由于bool在技术上只是一种特殊类型的int,你可以总结一个SeriesTrueFalse,就像您总结的序列10

>>>
>>> ser.str.contains("Fed").sum()
17

结果是提到的数量"Fed"洛杉矶时报的数据集。路透社、纳斯达克、商业周刊和其他公司都采用了相同的惯例。

提高性能 .groupby()

让我们再次回溯.groupby(...).apply(),看看为什么这种模式可能是次优的。要获得一些背景信息,请查看如何加速您的 Pandas 项目。可能发生的情况.apply()是它将有效地对每个组执行 Python 循环。虽然该.groupby(...).apply()模式可以提供一些灵活性,但它也可以阻止 Pandas 以其他方式使用其基于 Cython 的优化。

也就是说,每当您发现自己在考虑使用 时.apply(),问问自己是否有一种方法可以以矢量化的方式表达该操作。在这种情况下,您可以利用这一事实,即不仅.groupby()可以接受一个或多个列名,还可以接受许多类似数组的结构:

  • 一维 NumPy 数组
  • 一个列表
  • 熊猫SeriesIndex

另外请注意,.groupby()是一个有效的实例方法Series,不只是一个DataFrame,所以你基本上可以反分裂逻辑。考虑到这一点,您可以首先构造一个Series布尔值来指示标题是否包含"Fed"

>>>
>>> mentions_fed = df["title"].str.contains("Fed")
>>> type(mentions_fed)
<class 'pandas.core.series.Series'>

现在,.groupby()也是 的一种方法Series,因此您可以将一个分组Series到另一个:

>>>
>>> import numpy as np
>>> mentions_fed.groupby(
...     df["outlet"], sort=False
... ).sum().nlargest(10).astype(np.uintc)
outlet
Reuters                         161
NASDAQ                          103
Businessweek                     93
Investing.com                    66
Wall Street Journal \(blog\)     61
MarketWatch                      56
Moneynews                        55
Bloomberg                        53
GlobalPost                       51
Economic Times                   44
Name: title, dtype: uint32

两者Series不需要是同一DataFrame对象的列。它们只需要具有相同的形状:

>>>
>>> mentions_fed.shape
(422419,)
>>> df["outlet"].shape
(422419,)

最后,np.uintc如果您决定获得尽可能紧凑的结果,您可以将结果转换回无符号整数。这是将产生相同结果的两个版本的正面比较:

# Version 1: using `.apply()`
df.groupby("outlet", sort=False)["title"].apply(
    lambda ser: ser.str.contains("Fed").sum()
).nlargest(10)

# Version 2: using vectorization
mentions_fed.groupby(
    df["outlet"], sort=False
).sum().nlargest(10).astype(np.uintc)

在我的笔记本电脑上,版本 1 需要 4.01 秒,而版本 2 只需要 292 毫秒。对于几十万行,这是令人印象深刻的 14 倍 CPU 时间差异。考虑一下当您的数据集增长到几百万行时差异会变得多么显着!

注意:为简单起见,此示例忽略了数据中的一些细节。也就是说,搜索词"Fed"还可能会发现提及“联邦政府”之类的内容。

Series.str.contains()如果您想花哨并使用涉及否定前瞻的表达式,还可以将编译后的正则表达式作为参数。

您可能还想不仅计算提及的原始数量,还想计算提及相对于新闻媒体制作的所有文章的比例。

Pandas GroupBy:将它们放在一起

如果您调用dir()Pandas GroupBy 对象,那么您会在那里看到足够多的方法,让您头晕目眩!跟踪 Pandas GroupBy 对象的所有功能可能很困难。清除迷雾的一种方法是将不同的方法划分为它们的作用和行为方式。

从广义上讲,Pandas GroupBy 对象的方法分为几类:

  1. 聚合方法(也称为归约方法)将许多数据点“混入”到有关这些数据点的聚合统计信息中。一个例子是取 10 个数字的总和、平均值或中位数,其中结果只是一个数字。

  2. 过滤器方法会返回给您原始DataFrame. 这通常意味着.filter()根据有关该组及其子表的一些比较统计数据来删除整个组。在此定义下包含许多从每个组中排除特定行的方法也是有意义的。

  3. 转换方法返回DataFrame与原始具有相同形状和索引的 a,但具有不同的值。使用聚合和过滤方法,结果的DataFrame大小通常小于输入DataFrame。这不适用于转换,它转换单个值本身但保留原始 的形状DataFrame

  4. 元方法不太关心您调用的原始对象.groupby(),而更侧重于为您提供高级信息,例如组的数量和这些组的索引。

  5. 绘图方法模仿PandasSeries或绘图DataFrame的 API ,但通常将输出分成多个子图。

官方文档都有自己的这些类别的解释。在某种程度上,它们是可以解释的,并且本教程在对哪种方法适用于何处进行分类时可能会略有不同。

注意: Pandas 文档中还有另一个单独的表格,有自己的分类方案。选择最适合您且看起来最直观的!

您可以查看每个类别的更详细分类以及.groupby()属于它们的各种方法:

聚合方法和属性显示隐藏

聚合方法(也称为归约方法)将许多数据点“混入”到有关这些数据点的聚合统计信息中。一个例子是取 10 个数字的总和、平均值或中位数,其中结果只是一个数字。以下是一些聚合方法:

过滤方法和属性显示隐藏

过滤器方法会返回给您原始DataFrame. 这通常意味着.filter()根据有关该组及其子表的一些比较统计数据来删除整个组。在此定义下包含许多从每个组中排除特定行的方法也是有意义的。以下是一些过滤方法:

转换器方法和属性显示隐藏

转换方法返回DataFrame与原始具有相同形状和索引的 a,但具有不同的值。使用聚合和过滤方法,结果的DataFrame大小通常小于输入DataFrame。这不适用于转换,它转换单个值本身但保留原始 的形状DataFrame。以下是一些变压器方法:

元方法和属性显示隐藏

元方法不太关心您调用的原始对象.groupby(),而更侧重于为您提供高级信息,例如组的数量和这些组的索引。以下是一些元方法:

绘图方法显示隐藏

绘图方法模仿PandasSeries或绘图DataFrame的 API ,但通常将输出分成多个子图。以下是一些绘图方法:

什物显示隐藏

Pandas GroupBy 对象的一些方法不能很好地归入上述类别。这些方法通常产生中间对象,它是一个DataFrameSeries。例如,df.groupby(...).rolling(...)生成一个RollingGroupby对象,然后您可以在以下对象上调用聚合、过滤或转换方法:

  • .expanding()
  • .pipe()
  • .resample()
  • .rolling()

结论

在本教程中,您已经介绍了大量关于 的基础知识.groupby(),包括其设计、API 以及如何将方法链接在一起以在适合您目的的输出中获取数据。

你已经学到:

  • 如何在真实数据上使用 Pandas GroupBy 操作
  • 如何拆分申请,结合业务工作的链,以及如何可以分解成步骤
  • 如何根据意图和结果将 Pandas GroupBy 的方法放入不同的类别

.groupby()您可以在一个教程中涵盖更多内容。查看下面的资源并使用此处的示例数据集作为进一步探索的起点!

有关 Pandas GroupBy 的更多资源

Pandas 文档指南是对 Pandas 不同方面的用户友好的演练。以下是文档的某些部分,您可以查看以了解有关 Pandas GroupBy 的更多信息:

API 文档是对方法和对象的更全面的技术参考:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200