【问题标题】:Pandas groupby apply vs transform with specific functions熊猫 groupby 应用与特定功能的变换
【发布时间】:2018-12-07 08:07:57
【问题描述】:

我不明白groupby + transform 操作可以接受哪些函数。通常,我最终只是猜测、测试、还原直到某些东西可行,但我觉得应该有一个系统的方法来确定解决方案是否可行。

这是一个最小的例子。首先让我们使用groupby + applyset

df = pd.DataFrame({'a': [1,2,3,1,2,3,3], 'b':[1,2,3,1,2,3,3], 'type':[1,0,1,0,1,0,1]})

g = df.groupby(['a', 'b'])['type'].apply(set)

print(g)

a  b
1  1    {0, 1}
2  2    {0, 1}
3  3    {0, 1}

这很好用,但我希望在原始数据框的新列中按组计算得到的 set。所以我尝试使用transform:

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
---> 23 df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

TypeError: int() argument must be a string, a bytes-like object or a number, not 'set'

这是我在 Pandas v0.19.0 中看到的错误。在 v0.23.0 中,我看到了 TypeError: 'set' type is unordered。当然,我可以映射一个专门定义的索引来实现我的结果:

g = df.groupby(['a', 'b'])['type'].apply(set)
df['g'] = df.set_index(['a', 'b']).index.map(g.get)

print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

但我认为transform 的好处是避免了这种显式映射。我哪里做错了?

【问题讨论】:

    标签: python pandas dataframe pandas-groupby


    【解决方案1】:

    转换的结果仅限于某些类型。 [例如不能是listsetSeries 等--这是不正确的,谢谢@RafaelC 的评论] 我不知道'认为这没有记录,但是在检查 groupby.pyseries.py 的源代码时,您会发现这些类型限制。

    来自groupbydocumentation

    transform 方法返回一个对象,该对象的索引与被分组的对象相同(相同大小)。变换函数必须:

    • 返回与组块大小相同或可广播到组块大小的结果(例如,标量,grouped.transform(lambda x: x.iloc[- 1]))。

    • 对组块逐列操作。使用 chunk.apply 将转换应用于第一个组块。

    • 不对组块执行就地操作。组块应该被视为不可变的,对组块的更改可能会产生意想不到的结果。例如使用fillna时,inplace必须为False(grouped.transform(lambda x: x.fillna(inplace=False)))。

    • (可选)对整个组块进行操作。如果支持,则使用从第二个块开始的快速路径。

    免责声明:我收到不同的错误(pandas 版本 0.23.1):

    df['g'] = df.groupby(['a', 'b'])['type'].transform(set)
    File "***/lib/python3.6/site-packages/pandas/core/groupby/groupby.py", line 3661, in transform
    s = klass(res, indexer)        s = klass(res, indexer)
    File "***/lib/python3.6/site-packages/pandas/core/series.py", line 242, in __init__
    "".format(data.__class__.__name__))
    TypeError: 'set' type is unordered
    

    更新

    将组转换为集合后,pandas 无法将其广播到Series,因为它是无序的(并且具有与组块不同的维度)。如果我们强制它进入一个列表,它将变成与组块相同的大小,并且我们每行只得到一个值。答案是把它包裹在某个容器中,这样对象的最终大小就会变成 1,然后pandas 就可以广播它了:

    df['g'] = df.groupby(['a', 'b'])['type'].transform(lambda x: np.array(set(x)))
    print(df)
    
       a  b  type       g
    0  1  1     1  {0, 1}
    1  2  2     0  {0, 1}
    2  3  3     1  {0, 1}
    3  1  1     0  {0, 1}
    4  2  2     1  {0, 1}
    5  3  3     0  {0, 1}
    6  3  3     1  {0, 1}
    

    为什么我选择np.array作为容器?因为series.py(第 205:206 行)无需进一步检查就通过了这种类型。所以我相信这种行为会在未来的版本中保留。

    【讨论】:

    • 奇怪的是,tuplelist 在 v0.23 上似乎可以正常工作,但不是我期望的结果。我想我们遇到的问题是broadcastable to the size of the group chunk 部分。 Pandas 似乎无法将集合/列表等“复制”给组的所有成员。
    • 我同意,这就是我强调这部分的原因。但由于它没有记录,并且代码正在从一个版本更改为另一个版本,我不建议在transform 中使用除标量、numpy 向量或字符串之外的任何返回值。
    • @igrinis 事实上,您可以使用listpd.Series进行转换
    • @RafaelC 你是对的。更新我的答案并支持你的答案。感谢您和 jpp 的有趣讨论。
    【解决方案2】:

    首先,我相信在使用这些函数时有一定的直觉空间,因为它们可能非常有意义。

    在您的第一个结果中,您实际上并没有尝试转换您的价值观,而是聚合它们(这将按照您的预期工作)。

    但是在进入代码时,transform 文档在说这方面很有启发性

    返回与组块大小相同或可广播到组块大小的结果。

    当你这样做时

    df.groupby(['a', 'b'])['type'].transform(some_func)
    

    您实际上是使用您的some_func 函数将每个组中的每个pd.Series 对象转换为一个新对象。但问题是,这个新对象应该具有与组相同的大小可以广播到块的大小。

    因此,如果您使用tuplelist 转换您的系列,您将基本上转换对象

    0    1
    1    2
    2    3
    dtype: int64
    

    进入

    [1,2,3]
    

    但请注意,这些值现在分配回到它们各自的索引,这就是为什么您在transform 操作中看不到任何差异的原因。具有来自pd.Series.iloc[0] 值的行现在将具有来自转换列表的[1,2,3][0] 值(同样适用于元组)等。请注意ordering大小 在这里很重要,因为否则你可能会弄乱你的组并且转换将无法工作(这正是set 在这种情况下不是一个合适的函数的原因)。


    引用文本的第二部分说“可广播到组块的大小”。

    这意味着您还可以将pd.Series 转换为可在所有行中使用的对象。例如

    df.groupby(['a', 'b'])['type'].transform(lambda k: 50)
    

    会起作用的。为什么?即使50 不可迭代,它是可广播的,通过在初始pd.Series 的所有位置重复使用此值。


    为什么你可以apply 使用 set?

    因为apply 方法在结果中没有size这个约束。它实际上有三种不同的结果类型,它会推断您是要扩展减少还是广播您的结果.请注意,您不能 reduce 进行变换*

    默认情况下(result_type=None),最终返回类型是从应用函数的返回类型推断出来的。 result_type : {‘expand’, ‘reduce’, ‘broadcast’, None},默认无 这些仅在axis=1(列)时起作用:

    1. ‘expand’:类似列表的结果将变成列。

    2. ‘reduce’:如果可能,返回一个系列,而不是像扩展列表一样 结果。这与“扩展”相反。

    3. ‘broadcast’:结果会被广播到DataFrame的原始形状,保留原始索引和列。

    【讨论】:

    • 谢谢。我忘了transform 作为一种方法可以单独使用(没有groupby)并且有特定的要求。话虽如此,根据我的经验,它主要与 groupby 一起使用,这就是为什么我错误地认为与groupby + apply 一起使用的任何东西都可以与transform 一起使用。
    猜你喜欢
    • 2013-02-28
    • 2018-08-12
    • 2017-05-19
    • 1970-01-01
    • 2019-12-19
    • 1970-01-01
    • 2019-04-12
    • 2022-07-05
    • 2021-07-16
    相关资源
    最近更新 更多