Почему производительность pandas dataframe to_dict(«записи») плохая по сравнению с другой наивной реализацией?

#python #pandas #performance #dataframe

Вопрос:

Pandas to_dict(«записи»), по-видимому, имеет гораздо более низкую производительность по сравнению с наивной реализацией. Ниже приведен фрагмент кода моей реализации:

 def fast_to_dict_records(df):
    data = df.values.tolist()
    columns = df.columns.tolist() 
    return [
        dict(zip(columns, datum))
        for datum in data
    ]
 

Чтобы сравнить производительность, попробуйте приведенный ниже фрагмент кода:

 import pandas as pd
import numpy as np

df_test = pd.DataFrame(
    np.random.normal(size=(10000, 300)),
    columns=range(300)
)

%timeit df_test.to_dict('records')
%timeit fast_to_dict_records(df_test)
 

И результаты являются:

 2.21 s ± 71.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
293 ms ± 15.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
 

А именно, моя реализация на ~7,5 быстрее, чем собственная реализация pandas. Кроме того, должно быть легко убедиться, что два метода дают один и тот же результат. Я также протестировал производительность на разных размерах фреймов данных, и, похоже, моя реализация постоянно превосходит свою аналогию (хотя величина может отличаться).

Мне любопытно, не упускаю ли я здесь чего-нибудь? Я просто не уверен, что производительность собственной реализации pandas, которая, по моему впечатлению, была вполне конкурентоспособной, может быть настолько превзойдена не такой сложной альтернативой…

Комментарии:

1. вы могли бы взглянуть на исходный код, чтобы увидеть, в чем разница

2. На мой взгляд, причина в том, что нативная реализация работает над структурами pandas, и они тяжелы в обработке, вместо этого вы преобразуете за одну операцию все данные в собственные типы python, и с этого момента другие операции выполняются очень быстро. попробуйте преобразовать каждую строку фрейма данных, я ожидаю, что это будет лучше соответствовать собственной производительности.

3. Приятное открытие! Я думаю, что мы можем открыть билет на GitHub, чтобы исправить эту проблему с производительностью.

Ответ №1:

TL;DR: Pandas в основном написан на чистом Python, как и ваша реализация, хотя для ускорения вычислений в нем часто используются векторизованные вызовы Numpy. К сожалению, здесь это не так. В результате реализация Pandas неэффективна. Ваша реализация выполняется быстрее, но требует больше памяти.


Углубленное изучение:

Вы можете найти реализацию to_list здесь. Он выполняет итерацию по данным, используя itertuples внутренние данные (см. Здесь его код). Полученный (слегка упрощенный) код Панды на дату 12 марта 2021 года выглядит следующим образом:

 def maybe_box_native(value: Scalar) -> Scalar:
    if is_datetime_or_timedelta_dtype(value): # branch never taken here
        value = maybe_box_datetimelike(value)
    elif is_float(value):                     # branch always taken here
        value = float(value)                  # slow manual conversion for EACH values!
    elif is_integer(value):
        value = int(value)
    elif is_bool(value):
        value = bool(value)
    return value

def pandas_to_list(df):
    # From itertuples:
    fields = list(df.columns)
    arrays = [df.iloc[:, k] for k in range(len(df.columns))]
    tmpRes = zip(*arrays)

    # From to_list:
    columns = df.columns.tolist()
    rows = (dict(zip(columns, row)) for row in tmpRes)
    return [dict((k, maybe_box_native(v)) for k, v in row.items()) for row in rows]
 

Ваша реализация генерирует большой временный список в памяти, используя to_list в то время как Pandas внутренне работает с генераторами Python. Этот список не должен быть проблемой на практике в большинстве простых случаев, так dict как в конечном итоге он должен быть намного больше.

Однако to_list (в вашей реализации) также эффективно преобразует типы Numpy, используя внутренние векторизованные вызовы Numpy, в то время как Панды используют очень медленный подход. Действительно, Панды проверяют и преобразуют все значения одно за другим, используя maybe_box_native функцию pure Python и медленно, если/иначе… Поэтому неудивительно, что реализация Pandas происходит медленнее. При этом обратите внимание, что ваш код может вести себя по-разному с датами.

Текущая реализация Pandas неэффективна, и ее явно можно улучшить в будущем (возможно, не требуя значительно большего объема памяти).

Комментарии:

1. Действительно. После проверки реализации pandas я вижу, что она выполняет больше циклов. Спасибо!