【问题标题】:Why is `groupby` with `as_index=False` even slower than `groupby` with `reset_index`为什么使用 `as_index=False` 的 `groupby` 甚至比使用 `reset_index` 的 `groupby` 还要慢
【发布时间】:2021-11-18 05:15:24
【问题描述】:

我最近在groupby 中遇到了这种奇怪的 Pandas 行为。

我有这个数据框:

>>> df = pd.DataFrame({'a': [1, 2, 3, 1, 2, 3], 'b': [4, 5, 6, 7, 8, 9]})
>>> df
   a  b
0  1  4
1  2  5
2  3  6
3  1  7
4  2  8
5  3  9
>>> 

我想groupbyasumb

普通groupby 会将列作为索引,但as_index=False 不会:

>>> df.groupby('a')['b'].sum()
a
1    11
2    13
3    15
Name: b, dtype: int64
>>> 

但是当我给他们计时:

>>> timeit(lambda: df.groupby('a')['b'].sum(), number=1000)
0.5426476000000093
>>> timeit(lambda: df.groupby('a')['b'].sum(), number=10000)
4.912795499999902
>>> timeit(lambda: df.groupby('a', as_index=False)['b'].sum(), number=1000)
1.419923899999958
>>> timeit(lambda: df.groupby('a', as_index=False)['b'].sum(), number=10000)
11.907147600000144
>>> 

您可以看到,由于某种原因,groupbyas_index=False 慢了 2.75 倍!

不仅如此!比reset_index还要慢!

>>> timeit(lambda: df.groupby('a', as_index=False)['b'].sum(), number=1000)
1.419923899999958
>>> timeit(lambda: df.groupby('a', as_index=False)['b'].sum(), number=10000)
11.907147600000144
>>> timeit(lambda: df.groupby('a')['b'].sum().reset_index(), number=1000)
1.0641113000001496
>>> timeit(lambda: df.groupby('a')['b'].sum().reset_index(), number=10000)
10.01520289999985
>>> 

reset_index 显然也给出了与as_index=False 相同的输出:

>>> df.groupby('a')['b'].sum().reset_index()
   a   b
0  1  11
1  2  13
2  3  15
>>> 

as_index=False:

>>> df.groupby('a', as_index=False)['b'].sum()
   a   b
0  1  11
1  2  13
2  3  15
>>> 

我可以理解as_index=False 可能会更慢,但不会慢很多...另外,主要是我无法理解为什么reset_index 更快?这是一个额外的功能...

这是为什么? as_index的实现是什么?

我真的很惊讶,我什至认为as_index=False 很有可能比as_index=True 快,因为它不需要将列设置为索引。

但恰恰相反,它实际上是 as_index=True 的 2.75 倍...甚至 reset_indexas_index=False 快。

如果是这种情况,为什么as_index=False 不直接使用reset_index

【问题讨论】:

  • 这将是您查看源代码并可能提交 PR 以解决此问题的绝佳机会。开源的力量!
  • @sammywemmy 是的,会调查的!
  • @sammywemmy 老实说,这对我来说是一个有趣的问题:P

标签: python pandas dataframe performance pandas-groupby


【解决方案1】:

分配给@mozway 的答案,_insert_inaxis_grouper_inplace 函数是:

def _insert_inaxis_grouper_inplace(self, result: DataFrame) -> None:
    # zip in reverse so we can always insert at loc 0
    columns = result.columns
    for name, lev, in_axis in zip(
        reversed(self.grouper.names),
        reversed(self.grouper.get_group_levels()),
        reversed([grp.in_axis for grp in self.grouper.groupings]),
    ):
        # GH #28549
        # When using .apply(-), name will be in columns already
        if in_axis and name not in columns:
            result.insert(0, name, lev)

如您所见,它使用的是insert,而不是重置索引。

所以我怀疑它是否使用:

def _insert_inaxis_grouper_inplace(self, result: DataFrame) -> None:
    # zip in reverse so we can always insert at loc 0
    columns = result.columns
    for name, lev, in_axis in zip(
        reversed(self.grouper.names),
        reversed(self.grouper.get_group_levels()),
        reversed([grp.in_axis for grp in self.grouper.groupings]),
    ):
        # GH #28549
        # When using .apply(-), name will be in columns already
        if in_axis and name not in columns:
            result = result.reset_index()

虽然不完全确定设置,但我希望上面的工作......

那么它可能会更快。

【讨论】:

    【解决方案2】:

    as_index=True 是默认值,因为 grouper 在内部使用索引并在 as_index 设置为False 时将其重置:

    参见。 core.groupby.py source

            if not self.as_index:
                self._insert_inaxis_grouper_inplace(result)
    

    True/False之间的时间差其实很小,你应该使用更大的数据框来测试速度。

    这里有 600k 行:

    True:30.7 ms ± 3.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

    False:32.5 ms ± 2.66 ms per loop (mean ± std. dev. of 7 runs, 10 loops each

    因此,差异不是成比例的(慢 2 倍),而是固定的(慢 2.5 毫秒),这不是一个负担。

    现在至于为什么源不使用reset_index,熊猫在内部做了很多事情,我只是在这里猜测,因为代码很复杂,但可能有很多检查可以做更多的事情只是重置索引。

    【讨论】:

    • 很酷,但很高兴知道为什么reset_index 更快。
    • 只是注释...您在哪里对那条线进行了罚款?哪一行:)
    • 添加了_insert_inaxis_grouper_inplace函数的示例。
    猜你喜欢
    • 2019-01-22
    • 1970-01-01
    • 2017-05-05
    • 2017-05-07
    • 1970-01-01
    • 2012-03-28
    • 2022-11-23
    • 2019-08-02
    • 1970-01-01
    相关资源
    最近更新 更多