Pandas 2.2 中文官方教程和指南(二十·一)(1)https://developer.aliyun.com/article/1508816
对 DataFrame 列应用不同函数
通过将字典传递给 aggregate,你可以对 DataFrame 的列应用不同的聚合函数:
In [115]: grouped.agg({"C": "sum", "D": lambda x: np.std(x, ddof=1)}) Out[115]: C D A bar 0.392940 1.366330 foo -1.796421 0.884785
函数名称也可以是字符串。为了使字符串有效,它必须在 GroupBy 上实现:
In [116]: grouped.agg({"C": "sum", "D": "std"}) Out[116]: C D A bar 0.392940 1.366330 foo -1.796421 0.884785 ```## 转换 转换是一个 GroupBy 操作,其结果与被分组的对象索引相同。常见示例包括 `cumsum()` 和 `diff()`。 ```py In [117]: speeds Out[117]: 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 [118]: grouped = speeds.groupby("class")["max_speed"] In [119]: grouped.cumsum() Out[119]: falcon 389.0 parrot 413.0 lion 80.2 monkey NaN leopard 138.2 Name: max_speed, dtype: float64 In [120]: grouped.diff() Out[120]: falcon NaN parrot -365.0 lion NaN monkey NaN leopard NaN Name: max_speed, dtype: float64
与聚合不同,用于拆分原始对象的分组不包含在结果中。
注意
由于转换不包括用于拆分结果的分组,因此 DataFrame.groupby() 和 Series.groupby() 中的参数 as_index 和 sort 没有效果。
转换的常见用途是将结果添加回原始 DataFrame 中。
In [121]: result = speeds.copy() In [122]: result["cumsum"] = grouped.cumsum() In [123]: result["diff"] = grouped.diff() In [124]: result Out[124]: class order max_speed cumsum diff falcon bird Falconiformes 389.0 389.0 NaN parrot bird Psittaciformes 24.0 413.0 -365.0 lion mammal Carnivora 80.2 80.2 NaN monkey mammal Primates NaN NaN NaN leopard mammal Carnivora 58.0 138.2 NaN
内置转换方法
GroupBy 上的以下方法作为转换操作。
| 方法 | 描述 |
bfill() |
在每个组内向后填充 NA 值 |
cumcount() |
计算每个组内的累积计数 |
cummax() |
计算每个组内的累积最大值 |
cummin() |
计算每个组内的累积最小值 |
cumprod() |
计算每个组内的累积乘积 |
cumsum() |
计算每个组内的累积总和 |
diff() |
计算每个组内相邻值之间的差异 |
ffill() |
在每个组内填充 NA 值 |
pct_change() |
计算每个组内相邻值之间的百分比变化 |
rank() |
计算每个组内每个值的排名 |
shift() |
在每个组内上下移动值 |
此外,将任何内置聚合方法作为字符串传递给transform()(参见下一节)将在组内广播结果,产生转换后的结果。如果聚合方法有高效实现,这也将是高效的。
transform() 方法
与聚合方法类似,transform() 方法可以接受字符串别名,指向前一节中内置的转换方法。它也可以接受字符串别名,指向内置的聚合方法。当提供聚合方法时,结果将在组内广播。
In [125]: speeds Out[125]: 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 [126]: grouped = speeds.groupby("class")[["max_speed"]] In [127]: grouped.transform("cumsum") Out[127]: max_speed falcon 389.0 parrot 413.0 lion 80.2 monkey NaN leopard 138.2 In [128]: grouped.transform("sum") Out[128]: max_speed falcon 413.0 parrot 413.0 lion 138.2 monkey 138.2 leopard 138.2
除了字符串别名外,transform() 方法还可以接受用户定义的函数(UDFs)。UDF 必须:
- 返回的结果要么与组块的大小相同,要么可以广播到组块的大小(例如,标量,
grouped.transform(lambda x: x.iloc[-1]))。 - 在组块上逐列操作。使用 chunk.apply 对第一组块应用转换。
- 不要在组块上执行原位操作。组块应被视为不可变的,对组块的更改可能会产生意外结果。查看使用用户定义函数(UDF)方法进行变异获取更多信息。
- (可选)一次操作整个组块的所有列。如果支持此操作,将从第二块开始使用快速路径。
注意
通过提供 UDF 给 transform 进行转换通常比在 GroupBy 上使用内置方法性能较差。考虑将复杂操作拆分为一系列利用内置方法的操作。
本节中的所有示例都可以通过调用内置方法而不是使用 UDFs 来提高性能。查看下面的示例。
从版本 2.0.0 开始更改:当在分组的 DataFrame 上使用 .transform 并且转换函数返回一个 DataFrame 时,pandas 现在会将结果的索引与输入的索引对齐。您可以在转换函数内部调用 .to_numpy() 来避免对齐。
与聚合方法类似,结果的数据类型将反映转换函数的数据类型。如果不同组的结果具有不同的数据类型,则将以与 DataFrame 构造相同的方式确定公共数据类型。
假设我们希望在每个组内标准化数据:
In [129]: index = pd.date_range("10/1/1999", periods=1100) In [130]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index) In [131]: ts = ts.rolling(window=100, min_periods=100).mean().dropna() In [132]: ts.head() Out[132]: 2000-01-08 0.779333 2000-01-09 0.778852 2000-01-10 0.786476 2000-01-11 0.782797 2000-01-12 0.798110 Freq: D, dtype: float64 In [133]: ts.tail() Out[133]: 2002-09-30 0.660294 2002-10-01 0.631095 2002-10-02 0.673601 2002-10-03 0.709213 2002-10-04 0.719369 Freq: D, dtype: float64 In [134]: transformed = ts.groupby(lambda x: x.year).transform( .....: lambda x: (x - x.mean()) / x.std() .....: ) .....:
我们期望结果现在在每个组内具有均值为 0 和标准差为 1(浮点误差范围内),我们可以轻松检查:
# Original Data In [135]: grouped = ts.groupby(lambda x: x.year) In [136]: grouped.mean() Out[136]: 2000 0.442441 2001 0.526246 2002 0.459365 dtype: float64 In [137]: grouped.std() Out[137]: 2000 0.131752 2001 0.210945 2002 0.128753 dtype: float64 # Transformed Data In [138]: grouped_trans = transformed.groupby(lambda x: x.year) In [139]: grouped_trans.mean() Out[139]: 2000 -4.870756e-16 2001 -1.545187e-16 2002 4.136282e-16 dtype: float64 In [140]: grouped_trans.std() Out[140]: 2000 1.0 2001 1.0 2002 1.0 dtype: float64
我们还可以直观比较原始数据和转换后的数据集。
In [141]: compare = pd.DataFrame({"Original": ts, "Transformed": transformed}) In [142]: compare.plot() Out[142]: <Axes: >
具有较低维度输出的转换函数将被广播以匹配输入数组的形状。
In [143]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min()) Out[143]: 2000-01-08 0.623893 2000-01-09 0.623893 2000-01-10 0.623893 2000-01-11 0.623893 2000-01-12 0.623893 ... 2002-09-30 0.558275 2002-10-01 0.558275 2002-10-02 0.558275 2002-10-03 0.558275 2002-10-04 0.558275 Freq: D, Length: 1001, dtype: float64
另一个常见的数据转换是用组均值替换缺失数据。
In [144]: cols = ["A", "B", "C"] In [145]: values = np.random.randn(1000, 3) In [146]: values[np.random.randint(0, 1000, 100), 0] = np.nan In [147]: values[np.random.randint(0, 1000, 50), 1] = np.nan In [148]: values[np.random.randint(0, 1000, 200), 2] = np.nan In [149]: data_df = pd.DataFrame(values, columns=cols) In [150]: data_df Out[150]: A B C 0 1.539708 -1.166480 0.533026 1 1.302092 -0.505754 NaN 2 -0.371983 1.104803 -0.651520 3 -1.309622 1.118697 -1.161657 4 -1.924296 0.396437 0.812436 .. ... ... ... 995 -0.093110 0.683847 -0.774753 996 -0.185043 1.438572 NaN 997 -0.394469 -0.642343 0.011374 998 -1.174126 1.857148 NaN 999 0.234564 0.517098 0.393534 [1000 rows x 3 columns] In [151]: countries = np.array(["US", "UK", "GR", "JP"]) In [152]: key = countries[np.random.randint(0, 4, 1000)] In [153]: grouped = data_df.groupby(key) # Non-NA count in each group In [154]: grouped.count() Out[154]: A B C GR 209 217 189 JP 240 255 217 UK 216 231 193 US 239 250 217 In [155]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))
我们可以验证转换后数据中组均值未发生变化,并且转换后数据不包含任何缺失值。
In [156]: grouped_trans = transformed.groupby(key) In [157]: grouped.mean() # original group means Out[157]: A B C GR -0.098371 -0.015420 0.068053 JP 0.069025 0.023100 -0.077324 UK 0.034069 -0.052580 -0.116525 US 0.058664 -0.020399 0.028603 In [158]: grouped_trans.mean() # transformation did not change group means Out[158]: A B C GR -0.098371 -0.015420 0.068053 JP 0.069025 0.023100 -0.077324 UK 0.034069 -0.052580 -0.116525 US 0.058664 -0.020399 0.028603 In [159]: grouped.count() # original has some missing data points Out[159]: A B C GR 209 217 189 JP 240 255 217 UK 216 231 193 US 239 250 217 In [160]: grouped_trans.count() # counts after transformation Out[160]: A B C GR 228 228 228 JP 267 267 267 UK 247 247 247 US 258 258 258 In [161]: grouped_trans.size() # Verify non-NA count equals group size Out[161]: GR 228 JP 267 UK 247 US 258 dtype: int64
如上面的注意中所述,本节中的每个示例都可以使用内置方法更有效地计算。在下面的代码中,使用 UDF 的低效方法被注释掉,更快的替代方法出现在下面。
# result = ts.groupby(lambda x: x.year).transform( # lambda x: (x - x.mean()) / x.std() # ) In [162]: grouped = ts.groupby(lambda x: x.year) In [163]: result = (ts - grouped.transform("mean")) / grouped.transform("std") # result = ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min()) In [164]: grouped = ts.groupby(lambda x: x.year) In [165]: result = grouped.transform("max") - grouped.transform("min") # grouped = data_df.groupby(key) # result = grouped.transform(lambda x: x.fillna(x.mean())) In [166]: grouped = data_df.groupby(key) In [167]: result = data_df.fillna(grouped.transform("mean")) ```### 窗口和重采样操作 可以将 `resample()`、`expanding()` 和 `rolling()` 作为 groupby 的方法使用。 下面的示例将在列 B 的样本上应用 `rolling()` 方法,基于列 A 的组。 ```py In [168]: df_re = pd.DataFrame({"A": [1] * 10 + [5] * 10, "B": np.arange(20)}) In [169]: df_re Out[169]: A B 0 1 0 1 1 1 2 1 2 3 1 3 4 1 4 .. .. .. 15 5 15 16 5 16 17 5 17 18 5 18 19 5 19 [20 rows x 2 columns] In [170]: df_re.groupby("A").rolling(4).B.mean() Out[170]: A 1 0 NaN 1 NaN 2 NaN 3 1.5 4 2.5 ... 5 15 13.5 16 14.5 17 15.5 18 16.5 19 17.5 Name: B, Length: 20, dtype: float64
expanding() 方法将为每个特定组的所有成员累积给定操作(在示例中为 sum())。
In [171]: df_re.groupby("A").expanding().sum() Out[171]: B A 1 0 0.0 1 1.0 2 3.0 3 6.0 4 10.0 ... ... 5 15 75.0 16 91.0 17 108.0 18 126.0 19 145.0 [20 rows x 1 columns]
假设您想要在数据框的每个组中使用 resample() 方法获得每日频率,并希望使用 ffill() 方法填充缺失值。
In [172]: df_re = pd.DataFrame( .....: { .....: "date": pd.date_range(start="2016-01-01", periods=4, freq="W"), .....: "group": [1, 1, 2, 2], .....: "val": [5, 6, 7, 8], .....: } .....: ).set_index("date") .....: In [173]: df_re Out[173]: group val date 2016-01-03 1 5 2016-01-10 1 6 2016-01-17 2 7 2016-01-24 2 8 In [174]: df_re.groupby("group").resample("1D", include_groups=False).ffill() Out[174]: val group date 1 2016-01-03 5 2016-01-04 5 2016-01-05 5 2016-01-06 5 2016-01-07 5 ... ... 2 2016-01-20 7 2016-01-21 7 2016-01-22 7 2016-01-23 7 2016-01-24 8 [16 rows x 1 columns] ```## 过滤 过滤是一个 GroupBy 操作,它对原始分组对象进行子集化。它可以过滤掉整个组、部分组或两者。过滤返回调用对象的过滤版本,包括提供的分组列。在下面的示例中,`class` 包含在结果中。 ```py In [175]: speeds Out[175]: 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 [176]: speeds.groupby("class").nth(1) Out[176]: class order max_speed parrot bird Psittaciformes 24.0 monkey mammal Primates NaN
注意
与聚合不同,过滤不会将组键添加到结果的索引中。因此,传递 as_index=False 或 sort=True 不会影响这些方法。
过滤将尊重对 GroupBy 对象列的子集。
In [177]: speeds.groupby("class")[["order", "max_speed"]].nth(1) Out[177]: order max_speed parrot Psittaciformes 24.0 monkey Primates NaN
内置过滤
GroupBy 上的以下方法充当过滤。所有这些方法都有一个高效的、GroupBy 特定的实现。
| 方法 | 描述 |
head() |
选择每个组的前几行 |
nth() |
选择每个组的第 n 行 |
tail() |
选择每个组的底部行 |
用户还可以使用变换以及布尔索引在组内构建复杂的过滤。例如,假设我们有产品和其体积的组,并且希望将数据子集缩小到仅捕获每个组内总体积不超过 90%的最大产品。
In [178]: product_volumes = pd.DataFrame( .....: { .....: "group": list("xxxxyyy"), .....: "product": list("abcdefg"), .....: "volume": [10, 30, 20, 15, 40, 10, 20], .....: } .....: ) .....: In [179]: product_volumes Out[179]: group product volume 0 x a 10 1 x b 30 2 x c 20 3 x d 15 4 y e 40 5 y f 10 6 y g 20 # Sort by volume to select the largest products first In [180]: product_volumes = product_volumes.sort_values("volume", ascending=False) In [181]: grouped = product_volumes.groupby("group")["volume"] In [182]: cumpct = grouped.cumsum() / grouped.transform("sum") In [183]: cumpct Out[183]: 4 0.571429 1 0.400000 2 0.666667 6 0.857143 3 0.866667 0 1.000000 5 1.000000 Name: volume, dtype: float64 In [184]: significant_products = product_volumes[cumpct <= 0.9] In [185]: significant_products.sort_values(["group", "product"]) Out[185]: group product volume 1 x b 30 2 x c 20 3 x d 15 4 y e 40 6 y g 20
filter方法
注意
通过向filter提供用户定义函数(UDF)进行过滤通常比使用 GroupBy 上的内置方法性能较差。考虑将复杂操作分解为一系列利用内置方法的操作。
filter方法接受一个用户定义函数(UDF),当应用于整个组时,返回True或False。filter方法的结果是 UDF 返回True的组的子集。
假设我们只想取属于总和大于 2 的组的元素。
In [186]: sf = pd.Series([1, 1, 2, 3, 3, 3]) In [187]: sf.groupby(sf).filter(lambda x: x.sum() > 2) Out[187]: 3 3 4 3 5 3 dtype: int64
另一个有用的操作是过滤出仅属于只有几个成员的组的元素。
In [188]: dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")}) In [189]: dff.groupby("B").filter(lambda x: len(x) > 2) Out[189]: A B 2 2 b 3 3 b 4 4 b 5 5 b
或者,而不是删除有问题的组,我们可以返回一个类似索引对象,其中未通过过滤器的组填充为 NaN。
In [190]: dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False) Out[190]: A B 0 NaN NaN 1 NaN NaN 2 2.0 b 3 3.0 b 4 4.0 b 5 5.0 b 6 NaN NaN 7 NaN NaN
对于具有多列的数据框,过滤器应明确指定列作为过滤条件。
In [191]: dff["C"] = np.arange(8) In [192]: dff.groupby("B").filter(lambda x: len(x["C"]) > 2) Out[192]: A B C 2 2 b 2 3 3 b 3 4 4 b 4 5 5 b 5 ```## 灵活的`apply` 对于分组数据上的一些操作可能不适合于聚合、转换或过滤类别。对于这些情况,可以使用`apply`函数。 警告 `apply`必须尝试从结果中推断出它应该充当规约器、转换器*或*过滤器,具体取决于传递给它的内容。因此,分组列可能包含在输出中,也可能不包含。虽然它试图智能猜测如何行事,但有时可能猜错。 注意 本节中的所有示例都可以更可靠、更高效地使用其他 pandas 功能计算。 ```py In [193]: df Out[193]: 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 [194]: grouped = df.groupby("A") # could also just call .describe() In [195]: grouped["C"].apply(lambda x: x.describe()) Out[195]: A bar count 3.000000 mean 0.130980 std 0.181231 min -0.077118 25% 0.069390 ... foo min -1.143704 25% -0.862495 50% -0.575247 75% -0.408530 max 1.193555 Name: C, Length: 16, dtype: float64
返回结果的维度也可能会改变:
In [196]: grouped = df.groupby('A')['C'] In [197]: def f(group): .....: return pd.DataFrame({'original': group, .....: 'demeaned': group - group.mean()}) .....: In [198]: grouped.apply(f) Out[198]: original demeaned A bar 1 0.254161 0.123181 3 0.215897 0.084917 5 -0.077118 -0.208098 foo 0 -0.575247 -0.215962 2 -1.143704 -0.784420 4 1.193555 1.552839 6 -0.408530 -0.049245 7 -0.862495 -0.503211
Series 上的apply可以对来自应用函数的返回值本身为系列的值进行操作,并可能将结果上转换为数据框:
In [199]: def f(x): .....: return pd.Series([x, x ** 2], index=["x", "x²"]) .....: In [200]: s = pd.Series(np.random.rand(5)) In [201]: s Out[201]: 0 0.582898 1 0.098352 2 0.001438 3 0.009420 4 0.815826 dtype: float64 In [202]: s.apply(f) Out[202]: x x² 0 0.582898 0.339770 1 0.098352 0.009673 2 0.001438 0.000002 3 0.009420 0.000089 4 0.815826 0.665572
与 aggregate()方法类似,结果的数据类型将反映应用函数的数据类型。如果不同组的结果具有不同的数据类型,则将以与DataFrame构造相同的方式确定公共数据类型。
使用group_keys控制分组列的放置
要控制分组列是否包含在索引中,可以使用默认为True的group_keys参数。比较
In [203]: df.groupby("A", group_keys=True).apply(lambda x: x, include_groups=False) Out[203]: B C D A bar 1 one 0.254161 1.511763 3 three 0.215897 -0.990582 5 two -0.077118 1.211526 foo 0 one -0.575247 1.346061 2 two -1.143704 1.627081 4 two 1.193555 -0.441652 6 one -0.408530 0.268520 7 three -0.862495 0.024580
与
In [204]: df.groupby("A", group_keys=False).apply(lambda x: x, include_groups=False) Out[204]: B C D 0 one -0.575247 1.346061 1 one 0.254161 1.511763 2 two -1.143704 1.627081 3 three 0.215897 -0.990582 4 two 1.193555 -0.441652 5 two -0.077118 1.211526 6 one -0.408530 0.268520 7 three -0.862495 0.024580
Numba 加速例程
版本 1.1 中的新功能。
如果已安装Numba作为可选依赖项,则transform和aggregate方法支持engine='numba'和engine_kwargs参数。有关参数的一般用法和性能考虑,请参阅使用 Numba 增强性能。
函数签名必须以values, index 完全开始,因为属于每个组的数据将传递给values,分组索引将传递给index。
警告
当使用engine='numba'时,内部不会有“回退”行为。分组数据和分组索引将作为 NumPy 数组传递给 JITed 用户定义的函数,不会尝试任何替代执行。
Pandas 2.2 中文官方教程和指南(二十·一)(3)https://developer.aliyun.com/article/1508818