Vla*_*mir 5 python pandas pandas-groupby
如何实现自定义pandas.Grouper类?
换句话说 - 子类应该pandas.Grouper实现哪些方法/接口,以便它可以作为 的参数进行操作DataFrame.groupby?
作为一个简短的玩具示例:编写一个石斑鱼类,该类将根据某些特定字符串列的前 5 个符号对数据帧进行分组。事实上,使用现有方法可以轻松实现这一点:
df.groupby(df[column].str[:5])
Run Code Online (Sandbox Code Playgroud)
但我正在寻找的是实现一些NameGrouper类,以便通过以下方式完成操作:
df.groupby(NameGrouper(column))
Run Code Online (Sandbox Code Playgroud)
ps 关于针对特定需求定制分组的问题(例如Pandas:自定义分组功能、Pandas 自定义组聚合、Pandas groupby 自定义组)有很多问题,并使用标准 pandas 功能提供答案。但我对默认 pandas 功能的扩展感兴趣。
重要提示:在实践中,您永远不需要子类化
pandas.Grouper,我通常建议不要这样做。它的 API 是内部的,可能会在版本之间发生变化,恕不另行通知或警告。
注意:如果您需要在groupby函数中使用自定义逻辑,并且没有其他选项适合您,您可以随时回退到:
def custom_fn(df):
return series # a column of group indices
(
df
.assign(custom_groups=custom_fn)
.groupby("custom_groups")
...
)
Run Code Online (Sandbox Code Playgroud)
如果此时您仍在阅读,那么我假设您知道并接受这仅用于教育目的:)。在这种情况下,您可以pandas.Grouper通过重写一个方法来创建您自己的子类:_get_grouper。它接受一个NDFrame(又名df)和一个布尔值(validate)作为输入,并且 - 在 pandas v1.5.3 中 - 返回一个(None, Grouper, NDFrame)三元组。(这None是为了兼容性。)
例如,我们可以创建一个VowelGrouper,它根据给定字符串列内的元音数量对数据框进行分组:
import pandas as pd
from pandas.core.groupby.grouper import get_grouper
class VowelCounter(pd.Grouper):
"""Group by number of vowels.
"""
def _get_grouper(self, df:pd.DataFrame, validate: bool = True):
groups = df[self.key].map(lambda x: sum(map(x.count, "aeiou")))
groups.name = "vowel_count"
grouper, _, _ = get_grouper(
df,
groups,
axis=self.axis,
level=self.level,
sort=self.sort,
validate=validate,
dropna=self.dropna,
)
return None, grouper, df
Run Code Online (Sandbox Code Playgroud)
上面groups是一个哈希图,它将数据帧的索引映射到其组标签上。它几乎可以是任何可以理解为index_key->hash映射的东西,例如字典pd.Series(上面使用的)、可调用的、列名(str)等。有关支持哪些类型的哈希图的详细列表,我建议有一个查看 的pandas.core.groupby.grouper.get_grouper源代码(相当容易理解)。
get_grouper是另一个内部函数,它采用我们创建的哈希图并将其转换为pandas.core.groupby.ops.BaseGrouper. 是最终创建组成选择的对象的BaseGrouper对象。您可以在闲暇时探索这些。pandas.core.groupby.grouper.Groupinggroupby
要使用新创建的,我们只需像通常那样VowelGrouper将其传递给。Groupby给定一个数据框
import string
silly_df = pd.DataFrame().from_dict({
"integer":np.arange(10, 0, -1),
"char":list("abcdefghij"),
"fruit":["apple", "bunana"] * 5,
"random_string": np.random.choice(list(string.ascii_lowercase), 100).view(dtype="<U10")
})
Run Code Online (Sandbox Code Playgroud)
| 整数 | 字符 | 水果 | 随机字符串 | |
|---|---|---|---|---|
| 0 | 10 | A | 苹果 | 库吉苏特吉吉 |
| 1 | 9 | 乙 | 布纳纳 | 徐耀尧 |
| 2 | 8 | C | 苹果 | 回收qhvw |
| 3 | 7 | d | 布纳纳 | 乌马皮尔黑格 |
| 4 | 6 | e | 苹果 | yxjdfyqjmp |
| 5 | 5 | F | 布纳纳 | 蒂奥罗姆VJW |
| 6 | 4 | G | 苹果 | 杰布兹斯普斯德 |
| 7 | 3 | H | 布纳纳 | 乔克克斯利兹克 |
| 8 | 2 | 我 | 苹果 | 米夫兹科夫pnmt |
| 9 | 1 | j | 布纳纳 | 科克比克斯维奇乔 |
我们可以按fruit元音的数量对其进行分组random_string,并对其应用我们通常的聚合
(
silly_df
.groupby(["fruit", VowelCounter("random_string")])
.agg({"integer": "mean", "char": "sum"})
.reset_index()
)
Run Code Online (Sandbox Code Playgroud)
| 水果 | 元音计数 | 整数 | 字符 | |
|---|---|---|---|---|
| 0 | 苹果 | 0 | 8 | AE |
| 1 | 苹果 | 1 | 2 | 我 |
| 2 | 苹果 | 3 | 6 | CG |
| 3 | 布纳纳 | 1 | 6.33333 | BDH |
| 4 | 布纳纳 | 2 | 1 | j |
| 5 | 布纳纳 | 5 | 5 | F |