Pandas GroupBy:Python 数据分组指南
【摘要】 在本教程中,您已经介绍了大量关于 的基础知识.groupby(),包括其设计、API 以及如何将方法链接在一起以在适合您目的的输出中获取数据。
你已经学到:
如何在真实数据上使用 Pandas GroupBy 操作
如何拆分申请,结合业务工作的链,以及如何可以分解成步骤
如何根据意图和结果将 Pandas GroupBy 的方法放入不同的类别
.groupby()您可以在一个教程中涵盖更多内
目录
无论您是刚开始使用Pandas并想掌握其核心设施之一,还是希望填补您对 的理解中的一些空白.groupby()
,本教程都将帮助您从头开始分解和可视化Pandas GroupBy操作完成。
本教程旨在补充官方文档,您将在其中看到独立的、一口大小的示例。但是,在这里,您将关注三个使用真实世界数据集的更复杂的演练。
在本教程中,您将介绍:
- 如何在真实数据上使用 Pandas GroupBy 操作
- 如何拆分申请,结合业务工作的链
- 如何分解拆分申请,结合链入步骤
- 如何根据意图和结果将 Pandas GroupBy 对象的方法放入不同的类别
本教程假设您对 Pandas 本身有一些经验,包括如何将 CSV 文件作为 Pandas 对象读取到内存中read_csv()
。如果您需要复习,请查看使用 Pandas和Pandas读取 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)
您可以将这些添加到启动文件中,以便在每次启动解释器时自动设置它们。
在本教程中,您将关注三个数据集:
- 在美国国会的数据集包含了国会的历史成员公共信息和说明的几个基本功能
.groupby()
。 - 的空气质量数据集包含周期性气体传感器读数。这将允许您使用浮点数和时间序列数据。
- 该新闻聚合数据集,其持有数十万篇新闻文章的元数据。您将使用字符串并进行基于 groupby 的文本处理。
下载后.zip
,您可以将其解压缩到当前目录:
$ unzip -q -d groupby-data groupby-data.zip
该-d
选项可让您将内容提取到新文件夹中:
./
│
└── groupby-data/
│
├── legislators-historical.csv
├── airqual.csv
└── news.csv
设置完成后,您就可以开始了!
示例 1:国会数据集
您将通过剖析国会历史成员的数据集直接进入事物。您可以阅读CSV文件转换成 pandasDataFrame
有read_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()
作为第一个参数。您还可以指定以下任何一项:
下面是一个在两列上联合分组的示例,它查找按州然后按性别细分的国会议员人数:
>>>
>>> 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 GroupBy 操作和上述 SQL 查询之间的一个显着区别的好时机。SQL 查询的结果集包含三列:
state
gender
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 来做到这一点的sort
,True
除非你另有说明:
>>>
>>> # 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。这是指一连串的三个步骤:
- 将表拆分为组
- 对每个较小的表应用一些操作
- 合并结果
检查可能很困难,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)
每个值都是属于该特定组的行的索引位置序列。在上面的输出中,4、19和21是df
状态等于“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_c
、rel_hum
和abs_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()
可以接受几个不同的参数:
- 一列或列列表
- A
dict
或熊猫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
仍然作为标签的序列,其中一个cool
,warm
或hot
。如果你真的想,那么你也可以使用一个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 businesst
for science and technologye
for entertainmentm
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")
。这将返回一个布尔值 Series
,True
表示文章标题在搜索中注册匹配项。果然,第一行开始"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
,你可以总结一个Series
的True
和False
,就像您总结的序列1
和0
:
>>>
>>> ser.str.contains("Fed").sum()
17
结果是提到的数量"Fed"
由洛杉矶时报的数据集。路透社、纳斯达克、商业周刊和其他公司都采用了相同的惯例。
提高性能 .groupby()
让我们再次回溯.groupby(...).apply()
,看看为什么这种模式可能是次优的。要获得一些背景信息,请查看如何加速您的 Pandas 项目。可能发生的情况.apply()
是它将有效地对每个组执行 Python 循环。虽然该.groupby(...).apply()
模式可以提供一些灵活性,但它也可以阻止 Pandas 以其他方式使用其基于 Cython 的优化。
也就是说,每当您发现自己在考虑使用 时.apply()
,问问自己是否有一种方法可以以矢量化的方式表达该操作。在这种情况下,您可以利用这一事实,即不仅.groupby()
可以接受一个或多个列名,还可以接受许多类似数组的结构:
- 一维 NumPy 数组
- 一个列表
- 熊猫
Series
或Index
另外请注意,.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 时间差异。考虑一下当您的数据集增长到几百万行时差异会变得多么显着!
Pandas GroupBy:将它们放在一起
如果您调用dir()
Pandas GroupBy 对象,那么您会在那里看到足够多的方法,让您头晕目眩!跟踪 Pandas GroupBy 对象的所有功能可能很困难。清除迷雾的一种方法是将不同的方法划分为它们的作用和行为方式。
从广义上讲,Pandas GroupBy 对象的方法分为几类:
-
聚合方法(也称为归约方法)将许多数据点“混入”到有关这些数据点的聚合统计信息中。一个例子是取 10 个数字的总和、平均值或中位数,其中结果只是一个数字。
-
过滤器方法会返回给您原始
DataFrame
. 这通常意味着.filter()
根据有关该组及其子表的一些比较统计数据来删除整个组。在此定义下包含许多从每个组中排除特定行的方法也是有意义的。 -
转换方法返回
DataFrame
与原始具有相同形状和索引的 a,但具有不同的值。使用聚合和过滤方法,结果的DataFrame
大小通常小于输入DataFrame
。这不适用于转换,它转换单个值本身但保留原始 的形状DataFrame
。 -
元方法不太关心您调用的原始对象
.groupby()
,而更侧重于为您提供高级信息,例如组的数量和这些组的索引。 -
绘图方法模仿Pandas
Series
或绘图DataFrame
的 API ,但通常将输出分成多个子图。
该官方文档都有自己的这些类别的解释。在某种程度上,它们是可以解释的,并且本教程在对哪种方法适用于何处进行分类时可能会略有不同。
注意: Pandas 文档中还有另一个单独的表格,有自己的分类方案。选择最适合您且看起来最直观的!
您可以查看每个类别的更详细分类以及.groupby()
属于它们的各种方法:
聚合方法和属性显示隐藏
聚合方法(也称为归约方法)将许多数据点“混入”到有关这些数据点的聚合统计信息中。一个例子是取 10 个数字的总和、平均值或中位数,其中结果只是一个数字。以下是一些聚合方法:
过滤方法和属性显示隐藏
转换器方法和属性显示隐藏
转换方法返回DataFrame
与原始具有相同形状和索引的 a,但具有不同的值。使用聚合和过滤方法,结果的DataFrame
大小通常小于输入DataFrame
。这不适用于转换,它转换单个值本身但保留原始 的形状DataFrame
。以下是一些变压器方法:
元方法和属性显示隐藏
元方法不太关心您调用的原始对象.groupby()
,而更侧重于为您提供高级信息,例如组的数量和这些组的索引。以下是一些元方法:
.__iter__()
.get_group()
.groups
.indices
.ndim
.ngroup()
.ngroups
.dtypes
绘图方法显示隐藏
绘图方法模仿PandasSeries
或绘图DataFrame
的 API ,但通常将输出分成多个子图。以下是一些绘图方法:
什物显示隐藏
Pandas GroupBy 对象的一些方法不能很好地归入上述类别。这些方法通常产生中间对象,它是不一个DataFrame
或Series
。例如,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)