【问题标题】:df.groupby(...).agg(set) produces different result compared to df.groupby(...).agg(lambda x: set(x))df.groupby(...).agg(set) 与 df.groupby(...).agg(lambda x: set(x)) 相比产生不同的结果
【发布时间】:2018-09-07 06:56:39
【问题描述】:

回答this question 结果发现df.groupby(...).agg(set)df.groupby(...).agg(lambda x: set(x)) 产生了不同的结果。

数据:

df = pd.DataFrame({
       'user_id': [1, 2, 3, 4, 1, 2, 3], 
       'class_type': ['Krav Maga', 'Yoga', 'Ju-jitsu', 'Krav Maga', 
                      'Ju-jitsu','Krav Maga', 'Karate'], 
       'instructor': ['Bob', 'Alice','Bob', 'Alice','Alice', 'Alice','Bob']})

演示:

In [36]: df.groupby('user_id').agg(lambda x: set(x))
Out[36]:
                    class_type    instructor
user_id
1        {Krav Maga, Ju-jitsu}  {Alice, Bob}
2            {Yoga, Krav Maga}       {Alice}
3           {Ju-jitsu, Karate}         {Bob}
4                  {Krav Maga}       {Alice}

In [37]: df.groupby('user_id').agg(set)
Out[37]:
                                class_type                         instructor
user_id
1        {user_id, class_type, instructor}  {user_id, class_type, instructor}
2        {user_id, class_type, instructor}  {user_id, class_type, instructor}
3        {user_id, class_type, instructor}  {user_id, class_type, instructor}
4        {user_id, class_type, instructor}  {user_id, class_type, instructor}

我希望这里有同样的行为 - 你知道我错过了什么吗?

【问题讨论】:

  • 我认为这是因为当你只传递set 时,这将调用对象上的可迭代对象,在这种情况下将是列,因此你会得到这个奇怪的结果。当您使用 lambda 执行此操作时,这将调用系列值上的 set ctor
  • 给我 10 分钟,我应该有一个明确的答案,我正在逐步浏览源代码
  • 到目前为止我的发现:.agg(set) 最终调用pd.core.groupby.NDFrameGroupBy._aggregate_generic,而.add(lambda x: set(x)) 最终调用pd.core.groupby._GroupBy._python_agg_general。这两个函数都可以用setlambda x: set(x)调用(即._aggregate_generic(set)/._aggregate_generic(lambda x: set(x))._python_agg_general(set)/._python_agg_general(lambda x: set(x))),每个函数在两种情况下产生相同的结果,但我还没有发现在哪里/为什么决定打电话给一个或另一个。
  • 我花了 2 个小时浏览 pandas 源代码。令人沮丧的不透明。很少有cmets。我将奖励一个通过源代码到达此答案底部的答案。

标签: python pandas pandas-groupby


【解决方案1】:

好的,这里发生的事情是 set 没有被处理,因为它不是 _aggregate 中的 is_list_like

elif is_list_like(arg) and arg not in compat.string_types:

source

这不是is_list_like,所以它在调用链上返回None,并在这一行结束:

results.append(colg.aggregate(a))

source

这会将TypeError 提升为TypeError: 'type' object is not iterable

然后引发:

if not len(results):
    raise ValueError("no results")

source

所以因为我们没有结果,所以我们最终调用_aggregate_generic

source

然后调用:

result[name] = self._try_cast(func(data, *args, **kwargs)

source

这最终会变成:

(Pdb) n
> c:\programdata\anaconda3\lib\site-packages\pandas\core\groupby.py(3779)_aggregate_generic()
-> return self._wrap_generic_output(result, obj)

(Pdb) result
{1: {'user_id', 'instructor', 'class_type'}, 2: {'user_id', 'instructor', 'class_type'}, 3: {'user_id', 'instructor', 'class_type'}, 4: {'user_id', 'instructor', 'class_type'}}

我正在运行一个稍微不同版本的 pandas,但等效的源代码行是 https://github.com/pandas-dev/pandas/blob/v0.22.0/pandas/core/groupby.py#L3779

所以本质上是因为set 不算作函数或可迭代对象,它只是折叠为调用系列可迭代对象上的 ctor,在本例中是列,您可以在此处看到相同的效果:

In [8]:

df.groupby('user_id').agg(lambda x: print(set(x.columns)))
{'class_type', 'instructor', 'user_id'}
{'class_type', 'instructor', 'user_id'}
{'class_type', 'instructor', 'user_id'}
{'class_type', 'instructor', 'user_id'}
Out[8]: 
        class_type instructor
user_id                      
1             None       None
2             None       None
3             None       None
4             None       None

但是当您使用 lambda 这是一个匿名函数时,它会按预期工作。

【讨论】:

  • @EdChum,几天后你就会收到赏金。 pandas 源中的一些 cmets 会很有帮助。感觉有点像旋转木马。
  • 很棒的工作。也许考虑用硬编码的标签/提交替换指向master 的前五个链接,所以它们在未来仍然有意义。我没有得到is_aggregator 部分,这似乎只适用于isinstance(arg, dict)?
  • 这是否意味着这是一个错误?还是有意但未记录的行为?
  • @jdehesa 啊好吧,我误解了,我认为is_aggregator 是用来处理这些值是否属于这些类型中的任何一种,如果是这样,通过构造一个orderedDict 来保持排序,我会更新我的链接到0.22.0
  • @jdehesa 我已经删除了第一部分,因为它与您指出的无关,我花了一个小时逐步使用pdb,然后才到达多汁的部分,然后重新拼凑调用堆栈,这就是为什么该位出现在我的答案的原始帖子中
【解决方案2】:

也许正如@Edchum 评论的agg 应用python 内置函数,将groupby 对象视为一个迷你数据框,而当传递定义的函数时,它将应用于每一列。说明这一点的一个例子是通过打印。

df.groupby('user_id').agg(print,end='\n\n')

 class_type instructor  user_id
0  Krav Maga        Bob        1
4   Ju-jitsu      Alice        1

  class_type instructor  user_id
1       Yoga      Alice        2
5  Krav Maga      Alice        2

  class_type instructor  user_id
2   Ju-jitsu        Bob        3
6     Karate        Bob        3


df.groupby('user_id').agg(lambda x : print(x,end='\n\n'))

0    Krav Maga
4     Ju-jitsu
Name: class_type, dtype: object

1         Yoga
5    Krav Maga
Name: class_type, dtype: object

2    Ju-jitsu
6      Karate
Name: class_type, dtype: object

3    Krav Maga
Name: class_type, dtype: object

...

希望这就是应用 set 给出上述结果的原因。

【讨论】:

  • 谢谢!你的演示很清楚!我会等待更多的替代答案,并会接受最好的答案。你已经有我对这个答案的投票了 ;-)
  • 哈哈@MaxU 我更好奇。我无法掌握源代码。有人可能会想出更有效的答案:)
猜你喜欢
  • 2013-02-25
  • 1970-01-01
  • 2019-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 1970-01-01
相关资源
最近更新 更多