Pandas 2.2 中文官方教程和指南(二十)
按组分组:分割-应用-合并
通过“按组”我们指的是涉及以下一个或多个步骤的过程:
- 根据某些标准将数据分成组。
- 应用一个函数到每个组独立地。
- 合并结果到数据结构中。
在这些中,分割步骤是最直接的。在应用步骤中,我们可能希望执行以下操作之一:
- 聚合:为每个组计算摘要统计信息(或统计信息)。一些例子:
- 计算组的总和或均值。
- 计算组大小/计数。
- 转换:执行一些组特定的计算并返回一个类似索引的对象。一些例子:
- 在组内标准化数据(zscore)。
- 使用从每个组派生的值填充组内的 NAs。
- 过滤:根据按组计算的结果为 True 或 False 来丢弃一些组。一些例子:
- 丢弃属于只有少数成员的组的数据。
- 根据组总和或均值筛选数据。
这些操作中的许多是在 GroupBy 对象上定义的。这些操作类似于聚合 API、窗口 API 和重采样 API 的操作。
可能某个操作不属于这些类别之一,或是它们的某种组合。在这种情况下,可能可以使用 GroupBy 的apply
方法来计算操作。该方法将检查应用步骤的结果,并尝试将它们合理地组合成单个结果,如果它不适合上述三个类别之一。
注意
使用内置的 GroupBy 操作将一个操作分成多个步骤,比使用带有用户定义的 Python 函数的apply
方法更有效。
GroupBy 这个名字对于那些使用过基于 SQL 的工具(或itertools
)的人应该很熟悉,你可以编写类似以下代码:
SELECT Column1, Column2, mean(Column3), sum(Column4) FROM SomeTable GROUP BY Column1, Column2
我们的目标是使像这样的操作自然且易于使用 pandas 表达。我们将讨论 GroupBy 功能的每���领域,然后提供一些非平凡的例子/用例。
查看食谱以获取一些高级策略。
将对象分成组
分组的抽象定义是提供标签到组名的映射。要创建一个 GroupBy 对象(稍后会详细介绍 GroupBy 对象),您可以执行以下操作:
In [1]: speeds = pd.DataFrame( ...: [ ...: ("bird", "Falconiformes", 389.0), ...: ("bird", "Psittaciformes", 24.0), ...: ("mammal", "Carnivora", 80.2), ...: ("mammal", "Primates", np.nan), ...: ("mammal", "Carnivora", 58), ...: ], ...: index=["falcon", "parrot", "lion", "monkey", "leopard"], ...: columns=("class", "order", "max_speed"), ...: ) ...: In [2]: speeds Out[2]: class order max_speed falcon bird Falconiformes 389.0 parrot bird Psittaciformes 24.0 lion mammal Carnivora 80.2 monkey mammal Primates NaN leopard mammal Carnivora 58.0 In [3]: grouped = speeds.groupby("class") In [4]: grouped = speeds.groupby(["class", "order"])
映射可以以多种不同的方式指定:
- 一个 Python 函数,用于对每个索引标签进行调用。
- 与索引长度相同的列表或 NumPy 数组。
- 一个字典或
Series
,提供标签 -> 组名
的映射。 - 对于
DataFrame
对象,需要一个字符串,指示要用于分组的列名或索引级别名称。 - 上述任何一种事物的列表。
我们将分组对象统称为键。例如,考虑以下DataFrame
:
注意
传递给groupby
的字符串可以是列名,也可以是索引级别。如果一个字符串同时匹配列名和索引级别名称,将引发ValueError
。
In [5]: df = pd.DataFrame( ...: { ...: "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], ...: "B": ["one", "one", "two", "three", "two", "two", "one", "three"], ...: "C": np.random.randn(8), ...: "D": np.random.randn(8), ...: } ...: ) ...: In [6]: df Out[6]: A B C D 0 foo one 0.469112 -0.861849 1 bar one -0.282863 -2.104569 2 foo two -1.509059 -0.494929 3 bar three -1.135632 1.071804 4 foo two 1.212112 0.721555 5 bar two -0.173215 -0.706771 6 foo one 0.119209 -1.039575 7 foo three -1.044236 0.271860
在 DataFrame 上,通过调用groupby()
我们可以获得一个 GroupBy 对象。此方法返回一个pandas.api.typing.DataFrameGroupBy
实例。我们可以自然地按照A
或B
列,或两者进行分组:
In [7]: grouped = df.groupby("A") In [8]: grouped = df.groupby("B") In [9]: grouped = df.groupby(["A", "B"])
注意
df.groupby('A')
只是df.groupby(df['A'])
的语法糖。
如果我们还在列A
和B
上有一个 MultiIndex,我们可以按照除指定列之外的所有列进行分组:
In [10]: df2 = df.set_index(["A", "B"]) In [11]: grouped = df2.groupby(level=df2.index.names.difference(["B"])) In [12]: grouped.sum() Out[12]: C D A bar -1.591710 -1.739537 foo -0.752861 -1.402938
上述 GroupBy 将根据其索引(行)拆分 DataFrame。要按列拆分,请先进行转置:
In [13]: def get_letter_type(letter): ....: if letter.lower() in 'aeiou': ....: return 'vowel' ....: else: ....: return 'consonant' ....: In [14]: grouped = df.T.groupby(get_letter_type)
pandas Index
对象支持重复值。如果在 groupby 操作中使用非唯一索引作为组键,则相同索引值的所有值将被视为一个组,因此聚合函数的输出将仅包含唯一索引值:
In [15]: index = [1, 2, 3, 1, 2, 3] In [16]: s = pd.Series([1, 2, 3, 10, 20, 30], index=index) In [17]: s Out[17]: 1 1 2 2 3 3 1 10 2 20 3 30 dtype: int64 In [18]: grouped = s.groupby(level=0) In [19]: grouped.first() Out[19]: 1 1 2 2 3 3 dtype: int64 In [20]: grouped.last() Out[20]: 1 10 2 20 3 30 dtype: int64 In [21]: grouped.sum() Out[21]: 1 11 2 22 3 33 dtype: int64
注意,直到需要为止才会发生分割。创建 GroupBy 对象仅验证您是否传递了有效的映射。
注意
许多种复杂的数据操作可以用 GroupBy 操作来表达(尽管不能保证是最有效的实现)。您可以通过标签映射函数进行创造性的操作。
GroupBy 排序
默认情况下,在groupby
操作期间对组键进行排序。但是,您可以传递sort=False
以实现潜在的加速。使用sort=False
时,组键之间的顺序遵循键在原始数据框中出现的顺序:
In [22]: df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]}) In [23]: df2.groupby(["X"]).sum() Out[23]: Y X A 7 B 3 In [24]: df2.groupby(["X"], sort=False).sum() Out[24]: Y X B 3 A 7
注意,groupby
将保留每个组内排序的观测顺序。例如,下面通过groupby()
创建的组按照它们在原始DataFrame
中出现的顺序排列:
In [25]: df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]}) In [26]: df3.groupby("X").get_group("A") Out[26]: X Y 0 A 1 2 A 3 In [27]: df3.groupby(["X"]).get_group(("B",)) Out[27]: X Y 1 B 4 3 B 2
GroupBy dropna
默认情况下,在groupby
操作期间,将排除NA
值作为组键。但是,如果您想要在组键中包含NA
值,可以传递dropna=False
来实现。
In [28]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]] In [29]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"]) In [30]: df_dropna Out[30]: a b c 0 1 2.0 3 1 1 NaN 4 2 2 1.0 3 3 1 2.0 2
# Default ``dropna`` is set to True, which will exclude NaNs in keys In [31]: df_dropna.groupby(by=["b"], dropna=True).sum() Out[31]: a c b 1.0 2 3 2.0 2 5 # In order to allow NaN in keys, set ``dropna`` to False In [32]: df_dropna.groupby(by=["b"], dropna=False).sum() Out[32]: a c b 1.0 2 3 2.0 2 5 NaN 1 4
dropna
参数的默认设置是True
,这意味着NA
不包括在组键中。 ### GroupBy 对象属性
groups
属性是一个字典,其键是计算出的唯一组,相应的值是属于每个组的轴标签。在上面的示例中,我们有:
In [33]: df.groupby("A").groups Out[33]: {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]} In [34]: df.T.groupby(get_letter_type).groups Out[34]: {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
对 GroupBy 对象调用标准的 Python len
函数将返回组的数量,这与groups
字典的长度相同:
In [35]: grouped = df.groupby(["A", "B"]) In [36]: grouped.groups Out[36]: {('bar', 'one'): [1], ('bar', 'three'): [3], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'three'): [7], ('foo', 'two'): [2, 4]} In [37]: len(grouped) Out[37]: 6
GroupBy
将为列名、GroupBy 操作和其他属性提供制表完成:
In [38]: n = 10 In [39]: weight = np.random.normal(166, 20, size=n) In [40]: height = np.random.normal(60, 10, size=n) In [41]: time = pd.date_range("1/1/2000", periods=n) In [42]: gender = np.random.choice(["male", "female"], size=n) In [43]: df = pd.DataFrame( ....: {"height": height, "weight": weight, "gender": gender}, index=time ....: ) ....: In [44]: df Out[44]: height weight gender 2000-01-01 42.849980 157.500553 male 2000-01-02 49.607315 177.340407 male 2000-01-03 56.293531 171.524640 male 2000-01-04 48.421077 144.251986 female 2000-01-05 46.556882 152.526206 male 2000-01-06 68.448851 168.272968 female 2000-01-07 70.757698 136.431469 male 2000-01-08 58.909500 176.499753 female 2000-01-09 76.435631 174.094104 female 2000-01-10 45.306120 177.540920 male In [45]: gb = df.groupby("gender")
In [46]: gb.<TAB> # noqa: E225, E999 gb.agg gb.boxplot gb.cummin gb.describe gb.filter gb.get_group gb.height gb.last gb.median gb.ngroups gb.plot gb.rank gb.std gb.transform gb.aggregate gb.count gb.cumprod gb.dtype gb.first gb.groups gb.hist gb.max gb.min gb.nth gb.prod gb.resample gb.sum gb.var gb.apply gb.cummax gb.cumsum gb.fillna gb.gender gb.head gb.indices gb.mean gb.name gb.ohlc gb.quantile gb.size gb.tail gb.weight ```### 带有多重索引的 GroupBy 使用 层次化索引数据,按层次结构的一个级别进行分组非常自然。 让我们创建一个具有两级 `MultiIndex` 的 Series。 ```py In [47]: arrays = [ ....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ....: ["one", "two", "one", "two", "one", "two", "one", "two"], ....: ] ....: In [48]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) In [49]: s = pd.Series(np.random.randn(8), index=index) In [50]: s Out[50]: first second bar one -0.919854 two -0.042379 baz one 1.247642 two -0.009920 foo one 0.290213 two 0.495767 qux one 0.362949 two 1.548106 dtype: float64
然后,我们可以按 s
中的一个级别进行分组。
In [51]: grouped = s.groupby(level=0) In [52]: grouped.sum() Out[52]: first bar -0.962232 baz 1.237723 foo 0.785980 qux 1.911055 dtype: float64
如果 MultiIndex 指定了名称,则可以直接将这些名称传递而不是级别号:
In [53]: s.groupby(level="second").sum() Out[53]: second one 0.980950 two 1.991575 dtype: float64
支持具有多级别的分组。
In [54]: arrays = [ ....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ....: ["doo", "doo", "bee", "bee", "bop", "bop", "bop", "bop"], ....: ["one", "two", "one", "two", "one", "two", "one", "two"], ....: ] ....: In [55]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second", "third"]) In [56]: s = pd.Series(np.random.randn(8), index=index) In [57]: s Out[57]: first second third bar doo one -1.131345 two -0.089329 baz bee one 0.337863 two -0.945867 foo bop one -0.932132 two 1.956030 qux bop one 0.017587 two -0.016692 dtype: float64 In [58]: s.groupby(level=["first", "second"]).sum() Out[58]: first second bar doo -1.220674 baz bee -0.608004 foo bop 1.023898 qux bop 0.000895 dtype: float64
索引级别名称可以作为键提供。
In [59]: s.groupby(["first", "second"]).sum() Out[59]: first second bar doo -1.220674 baz bee -0.608004 foo bop 1.023898 qux bop 0.000895 dtype: float64
更多关于 sum
函数和聚合的信息稍后。
使用 Index 级别和列分组的 DataFrame
可以通过列和索引级别的组合对 DataFrame 进行分组。您可以同时指定列名和索引名,或者使用 Grouper
。
让我们首先创建一个带有 MultiIndex 的 DataFrame:
In [60]: arrays = [ ....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ....: ["one", "two", "one", "two", "one", "two", "one", "two"], ....: ] ....: In [61]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) In [62]: df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index) In [63]: df Out[63]: A B first second bar one 1 0 two 1 1 baz one 1 2 two 1 3 foo one 2 4 two 2 5 qux one 3 6 two 3 7
然后我们将 df
按 second
索引级别和 A
列分组。
In [64]: df.groupby([pd.Grouper(level=1), "A"]).sum() Out[64]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
索引级别也可以通过名称指定。
In [65]: df.groupby([pd.Grouper(level="second"), "A"]).sum() Out[65]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
索引级别名称可以直接作为键传递给 groupby
。
In [66]: df.groupby(["second", "A"]).sum() Out[66]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
在 GroupBy 中的 DataFrame 列选择
从 DataFrame 创建 GroupBy 对象后,您可能希望针对每列执行不同的操作。因此,通过在 GroupBy 对象上使用 []
,方式类似于从 DataFrame 获取列的方式,您可以执行以下操作:
In [67]: df = pd.DataFrame( ....: { ....: "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], ....: "B": ["one", "one", "two", "three", "two", "two", "one", "three"], ....: "C": np.random.randn(8), ....: "D": np.random.randn(8), ....: } ....: ) ....: In [68]: df Out[68]: A B C D 0 foo one -0.575247 1.346061 1 bar one 0.254161 1.511763 2 foo two -1.143704 1.627081 3 bar three 0.215897 -0.990582 4 foo two 1.193555 -0.441652 5 bar two -0.077118 1.211526 6 foo one -0.408530 0.268520 7 foo three -0.862495 0.024580 In [69]: grouped = df.groupby(["A"]) In [70]: grouped_C = grouped["C"] In [71]: grouped_D = grouped["D"]
这主要是对另一种方式的语法糖,后者要冗长得多:
In [72]: df["C"].groupby(df["A"]) Out[72]: <pandas.core.groupby.generic.SeriesGroupBy object at 0x7ff2cef1c730>
此外,该方法避免了重新计算从传递的键派生的内部分组信息。
如果您想要对它们进行操作,还可以包括分组列。
In [73]: grouped[["A", "B"]].sum() Out[73]: A B A bar barbarbar onethreetwo foo foofoofoofoofoo onetwotwoonethree ```## 遍历分组 拥有 GroupBy 对象后,通过分组的数据进行迭代是非常自然的,并且功能类似于 [`itertools.groupby()`](https://docs.python.org/3/library/itertools.html#itertools.groupby "(在 Python v3.12 中)"): ```py In [74]: grouped = df.groupby('A') In [75]: for name, group in grouped: ....: print(name) ....: print(group) ....: bar A B C D 1 bar one 0.254161 1.511763 3 bar three 0.215897 -0.990582 5 bar two -0.077118 1.211526 foo A B C D 0 foo one -0.575247 1.346061 2 foo two -1.143704 1.627081 4 foo two 1.193555 -0.441652 6 foo one -0.408530 0.268520 7 foo three -0.862495 0.024580
在按多个键进行分组的情况下,组名将是一个元组:
In [76]: for name, group in df.groupby(['A', 'B']): ....: print(name) ....: print(group) ....: ('bar', 'one') A B C D 1 bar one 0.254161 1.511763 ('bar', 'three') A B C D 3 bar three 0.215897 -0.990582 ('bar', 'two') A B C D 5 bar two -0.077118 1.211526 ('foo', 'one') A B C D 0 foo one -0.575247 1.346061 6 foo one -0.408530 0.268520 ('foo', 'three') A B C D 7 foo three -0.862495 0.02458 ('foo', 'two') A B C D 2 foo two -1.143704 1.627081 4 foo two 1.193555 -0.441652
参见 遍历分组。
选择一个组
可以使用 DataFrameGroupBy.get_group()
选择单个组:
In [77]: grouped.get_group("bar") Out[77]: A B C D 1 bar one 0.254161 1.511763 3 bar three 0.215897 -0.990582 5 bar two -0.077118 1.211526
或者针对在多列上分组的对象:
In [78]: df.groupby(["A", "B"]).get_group(("bar", "one")) Out[78]: A B C D 1 bar one 0.254161 1.511763
聚合
聚合是一种减少分组对象维度的 GroupBy 操作。聚合的结果是,或者至少被视为,每列在一个组中的标量值。例如,生成一组值中每列的总和。
In [79]: animals = pd.DataFrame( ....: { ....: "kind": ["cat", "dog", "cat", "dog"], ....: "height": [9.1, 6.0, 9.5, 34.0], ....: "weight": [7.9, 7.5, 9.9, 198.0], ....: } ....: ) ....: In [80]: animals Out[80]: kind height weight 0 cat 9.1 7.9 1 dog 6.0 7.5 2 cat 9.5 9.9 3 dog 34.0 198.0 In [81]: animals.groupby("kind").sum() Out[81]: height weight kind cat 18.6 17.8 dog 40.0 205.5
在结果中,默认情况下,组的键出现在索引中。通过传递 as_index=False
,可以将它们包含在列中。
In [82]: animals.groupby("kind", as_index=False).sum() Out[82]: kind height weight 0 cat 18.6 17.8 1 dog 40.0 205.5
内置聚合方法
许多常见的聚合内置到 GroupBy 对象中作为方法。在下面列出的方法中,带有 *
的方法 没有 一个高效的、GroupBy 特定的实现。
方法 | 描述 |
any() |
计算组中是否有任何真值 |
all() |
计算组中所有值是否都为真值 |
count() |
计算组中非 NA 值的数量 |
cov() * |
计算组的协方差 |
first() |
计算每个组中首次出现的值 |
idxmax() |
计算每个组中最大值的索引 |
idxmin() |
计算每个组中最小值的索引 |
last() |
计算每个组中最后出现的值 |
max() |
计算每个组的最大值 |
mean() |
计算每个组的平均值 |
median() |
计算每个组的中位数 |
min() |
计算每个组的最小值 |
nunique() |
计算每个组中唯一值的数量 |
prod() |
计算每个组中值的乘积 |
quantile() |
计算每个组中值的给定分位数 |
sem() |
计算每个组中值的均值标准误差 |
size() |
计算每个组中值的数量 |
skew() * |
计算每个组中值的偏度 |
std() |
计算每个组中值的标准偏差 |
sum() |
计算每个组中值的总和 |
var() |
计算每个组中值的方差 |
一些示例:
In [83]: df.groupby("A")[["C", "D"]].max() Out[83]: C D A bar 0.254161 1.511763 foo 1.193555 1.627081 In [84]: df.groupby(["A", "B"]).mean() Out[84]: C D A B bar one 0.254161 1.511763 three 0.215897 -0.990582 two -0.077118 1.211526 foo one -0.491888 0.807291 three -0.862495 0.024580 two 0.024925 0.592714
另一个聚合示例是计算每个组的大小。这包含在 GroupBy 中作为size
方法。它返回一个 Series,其索引由组名组成,值是每个组的大小。
In [85]: grouped = df.groupby(["A", "B"]) In [86]: grouped.size() Out[86]: A B bar one 1 three 1 two 1 foo one 2 three 1 two 2 dtype: int64
虽然DataFrameGroupBy.describe()
方法本身不是一个约简函数,但它可以方便地生成关于每个组的摘要统计信息的集合。
In [87]: grouped.describe() Out[87]: C ... D count mean std ... 50% 75% max A B ... bar one 1.0 0.254161 NaN ... 1.511763 1.511763 1.511763 three 1.0 0.215897 NaN ... -0.990582 -0.990582 -0.990582 two 1.0 -0.077118 NaN ... 1.211526 1.211526 1.211526 foo one 2.0 -0.491888 0.117887 ... 0.807291 1.076676 1.346061 three 1.0 -0.862495 NaN ... 0.024580 0.024580 0.024580 two 2.0 0.024925 1.652692 ... 0.592714 1.109898 1.627081 [6 rows x 16 columns]
另一个聚合示例是计算每个组的唯一值数量。这类似于DataFrameGroupBy.value_counts()
函数,不同之处在于它只计算唯一值的数量。
In [88]: ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]] In [89]: df4 = pd.DataFrame(ll, columns=["A", "B"]) In [90]: df4 Out[90]: A B 0 foo 1 1 foo 2 2 foo 2 3 bar 1 4 bar 1 In [91]: df4.groupby("A")["B"].nunique() Out[91]: A bar 1 foo 2 Name: B, dtype: int64
注意
聚合函数不会在as_index=True
(默认情况下)时返回您正在聚合的组作为命名列,分组的列将是返回对象的索引。
传递as_index=False
将 返回您正在聚合的分组作为命名列,而不管它们在输入中是命名索引还是列。### aggregate()
方法
注意
aggregate()
方法可以接受许多不同类型的输入。本节详细介绍了使用字符串别名进行各种 GroupBy 方法的其他输入详细信息,请参见下文各节。
任何 pandas 实现的缩减方法都可以作为字符串传递给aggregate()
。鼓励用户使用简写形式agg
。它将像调用相应的方法一样运行。
In [92]: grouped = df.groupby("A") In [93]: grouped[["C", "D"]].aggregate("sum") Out[93]: C D A bar 0.392940 1.732707 foo -1.796421 2.824590 In [94]: grouped = df.groupby(["A", "B"]) In [95]: grouped.agg("sum") Out[95]: C D A B bar one 0.254161 1.511763 three 0.215897 -0.990582 two -0.077118 1.211526 foo one -0.983776 1.614581 three -0.862495 0.024580 two 0.049851 1.185429
聚合的结果将使用组名作为新索引。在多个键的情况下,默认情况下结果是一个 MultiIndex。如上所述,这可以通过使用as_index
选项进行更改:
In [96]: grouped = df.groupby(["A", "B"], as_index=False) In [97]: grouped.agg("sum") Out[97]: A B C D 0 bar one 0.254161 1.511763 1 bar three 0.215897 -0.990582 2 bar two -0.077118 1.211526 3 foo one -0.983776 1.614581 4 foo three -0.862495 0.024580 5 foo two 0.049851 1.185429 In [98]: df.groupby("A", as_index=False)[["C", "D"]].agg("sum") Out[98]: A C D 0 bar 0.392940 1.732707 1 foo -1.796421 2.824590
请注意,您可以使用DataFrame.reset_index()
DataFrame 函数来达到与列名存储在结果MultiIndex
中相同的结果,尽管这将多做一次复制。
In [99]: df.groupby(["A", "B"]).agg("sum").reset_index() Out[99]: A B C D 0 bar one 0.254161 1.511763 1 bar three 0.215897 -0.990582 2 bar two -0.077118 1.211526 3 foo one -0.983776 1.614581 4 foo three -0.862495 0.024580 5 foo two 0.049851 1.185429 ```### 使用自定义函数进行聚合 用户还可以为自定义聚合提供自己的用户定义函数(UDFs)。 警告 使用 UDF 进行聚合时,UDF 不应更改提供的`Series`。有关更多信息,请参见使用用户定义函数(UDF)方法进行突变。 注意 使用 UDF 进行聚合通常不如在 GroupBy 上使用 pandas 内置方法高效。考虑将复杂操作分解为一系列使用内置方法的操作链。 ```py In [100]: animals Out[100]: kind height weight 0 cat 9.1 7.9 1 dog 6.0 7.5 2 cat 9.5 9.9 3 dog 34.0 198.0 In [101]: animals.groupby("kind")[["height"]].agg(lambda x: set(x)) Out[101]: height kind cat {9.1, 9.5} dog {34.0, 6.0}
结果的 dtype 将反映聚合函数的 dtype。如果不同组的结果具有不同的 dtype,则将以与DataFrame
构造相同的方式确定通用 dtype。
In [102]: animals.groupby("kind")[["height"]].agg(lambda x: x.astype(int).sum()) Out[102]: height kind cat 18 dog 40 ```### 同时应用多个函数 在分组的`Series`上,你可以传递一个函数列表或字典给`SeriesGroupBy.agg()`,输出一个 DataFrame: ```py In [103]: grouped = df.groupby("A") In [104]: grouped["C"].agg(["sum", "mean", "std"]) Out[104]: sum mean std A bar 0.392940 0.130980 0.181231 foo -1.796421 -0.359284 0.912265
在分组的DataFrame
上,你可以传递一个函数列表给DataFrameGroupBy.agg()
以聚合每一列,这将产生一个带有分层列索引的聚合结果:
In [105]: grouped[["C", "D"]].agg(["sum", "mean", "std"]) Out[105]: C D sum mean std sum mean std A bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330 foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
结果的聚合将以函数本身命名。如果需要重命名,则可以为Series
添加一个链接操作,像这样:
In [106]: ( .....: grouped["C"] .....: .agg(["sum", "mean", "std"]) .....: .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"}) .....: ) .....: Out[106]: foo bar baz A bar 0.392940 0.130980 0.181231 foo -1.796421 -0.359284 0.912265
对于分组的DataFrame
,您可以以类似的方式重命名:
In [107]: ( .....: grouped[["C", "D"]].agg(["sum", "mean", "std"]).rename( .....: columns={"sum": "foo", "mean": "bar", "std": "baz"} .....: ) .....: ) .....: Out[107]: C D foo bar baz foo bar baz A bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330 foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
注意
通常情况下,输出列名应该是唯一的,但 pandas 允许你将相同函数(或两个同名函数)应用于同一列。
In [108]: grouped["C"].agg(["sum", "sum"]) Out[108]: sum sum A bar 0.392940 0.392940 foo -1.796421 -1.796421
pandas 也允许你提供多个 lambda 函数。在这种情况下,pandas 会篡改(无名)lambda 函数的名称,对每个后续 lambda 函数追加 _
。
In [109]: grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()]) Out[109]: <lambda_0> <lambda_1> A bar 0.331279 0.084917 foo 2.337259 -0.215962 ```### 命名聚合 为了支持具有对输出列名的控制的特定列聚合,pandas 接受在 `DataFrameGroupBy.agg()` 和 `SeriesGroupBy.agg()` 中的特殊语法,称为“命名聚合”,其中 + 关键字是 *输出* 列名 + 这些值是元组,第一个元素是要选择的列,第二个元素是要应用于该列的聚合函数。pandas 提供了带有字段 `['column', 'aggfunc']` 的 `NamedAgg` 命名元组,以使参数更清晰。通常情况下,聚合可以是可调用对象或字符串别名。 ```py In [110]: animals Out[110]: kind height weight 0 cat 9.1 7.9 1 dog 6.0 7.5 2 cat 9.5 9.9 3 dog 34.0 198.0 In [111]: animals.groupby("kind").agg( .....: min_height=pd.NamedAgg(column="height", aggfunc="min"), .....: max_height=pd.NamedAgg(column="height", aggfunc="max"), .....: average_weight=pd.NamedAgg(column="weight", aggfunc="mean"), .....: ) .....: Out[111]: min_height max_height average_weight kind cat 9.1 9.5 8.90 dog 6.0 34.0 102.75
NamedAgg
就是一个 namedtuple
。普通元组也是允许的。
In [112]: animals.groupby("kind").agg( .....: min_height=("height", "min"), .....: max_height=("height", "max"), .....: average_weight=("weight", "mean"), .....: ) .....: Out[112]: min_height max_height average_weight kind cat 9.1 9.5 8.90 dog 6.0 34.0 102.75
如果你想要的列名不是有效的 Python 关键字,构造一个字典并解包关键字参数
In [113]: animals.groupby("kind").agg( .....: **{ .....: "total weight": pd.NamedAgg(column="weight", aggfunc="sum") .....: } .....: ) .....: Out[113]: total weight kind cat 17.8 dog 205.5
在使用命名聚合时,额外的关键字参数不会传递给聚合函数;只有 (column, aggfunc)
对应的键值对应该作为 **kwargs
传递。如果你的聚合函数需要额外的参数,可以使用 functools.partial()
部分应用它们。
命名聚合对于 Series 分组聚合也是有效的。在这种情况下,没有列选择,所以值只是函数。
In [114]: animals.groupby("kind").height.agg( .....: min_height="min", .....: max_height="max", .....: ) .....: Out[114]: min_height max_height kind cat 9.1 9.5 dog 6.0 34.0
Pandas 2.2 中文官方教程和指南(二十·一)(2)https://developer.aliyun.com/article/1508817