#python #pandas #dataframe #performance #pandas-groupby
Вопрос:
Я только недавно столкнулся с этим странным поведением Панд groupby
.
У меня есть этот фрейм данных:
>>> 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
>>>
И я хочу в groupby
колонку a
и sum
в колонку b
.
Обычно 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
>>>
Вы можете видеть, что по какой-то причине, groupby
с as_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_index
быть быстрее, чем as_index=False
.
Если это так, почему as_index=False
бы также просто не использовать reset_index
?
Комментарии:
1. это будет отличная возможность для вас взглянуть на исходный код и, возможно, отправить PR, чтобы исправить это. питание с открытым исходным кодом!
2. @sammywemmy Правда, разберусь в этом!
3. @sammywemmy Это, честно говоря, для меня интересный вопрос 😛
Ответ №1:
as_index=True
является значением по умолчанию, так как группер использует внутренний индекс и сбрасывает его, если as_index
установлено значение False
:
if not self.as_index:
self._insert_inaxis_grouper_inplace(result)
Разница во времени между True
/ False
на самом деле минимальна, вы должны использовать больший кадр данных для проверки скорости.
Здесь на 600 тысяч строк:
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
, ну, панды многое делают внутри, я только предполагаю, поскольку код сложный, но, вероятно, существует множество проверок, которые делают больше, чем просто сброс индекса.
Комментарии:
1. Круто, но было бы неплохо узнать, почему это
reset_index
быстрее.2. Только записка… Где ты провел эту черту? какая строка 🙂
3. Добавлен пример
_insert_inaxis_grouper_inplace
функции.
Ответ №2:
Назначьте ответу @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()
Хотя я не совсем уверен в настройке, но я ожидаю, что все вышеперечисленное сработает…
Тогда, вероятно, это было бы быстрее.