Создание больших Pandas DataFrames: preallocation vs append vs concat

Меня смущает производительность в Pandas при построении большого блока данных данных куском. В Numpy мы (почти) всегда видим лучшую производительность, предварительно распределяя большой пустой массив и затем заполняя значения. Насколько я понимаю, это связано с тем, что Numpy захватывает всю память, которая ему нужна, вместо того, чтобы перераспределять память при каждой операции append .

В Pandas я, кажется, получаю лучшую производительность, используя df = df.append(temp) .

Вот пример времени. Далее следует определение класса Timer . Как вы видите, я вижу, что preallocating примерно в 10 раз медленнее, чем при использовании append ! Предварительное np.empty с np.empty значениями соответствующего типа dtype очень помогает, но метод append по-прежнему является самым быстрым.

 import numpy as np from numpy.random import rand import pandas as pd from timer import Timer # Some constants num_dfs = 10 # Number of random dataframes to generate n_rows = 2500 n_cols = 40 n_reps = 100 # Number of repetitions for timing # Generate a list of num_dfs dataframes of random values df_list = [pd.DataFrame(rand(n_rows*n_cols).reshape((n_rows, n_cols)), columns=np.arange(n_cols)) for i in np.arange(num_dfs)] ## # Define two methods of growing a large dataframe ## # Method 1 - append dataframes def method1(): out_df1 = pd.DataFrame(columns=np.arange(4)) for df in df_list: out_df1 = out_df1.append(df, ignore_index=True) return out_df1 def method2(): # # Create an empty dataframe that is big enough to hold all the dataframes in df_list out_df2 = pd.DataFrame(columns=np.arange(n_cols), index=np.arange(num_dfs*n_rows)) #EDIT_1: Set the dtypes of each column for ix, col in enumerate(out_df2.columns): out_df2[col] = out_df2[col].astype(df_list[0].dtypes[ix]) # Fill in the values for ix, df in enumerate(df_list): out_df2.iloc[ix*n_rows:(ix+1)*n_rows, :] = df.values return out_df2 # EDIT_2: # Method 3 - preallocate dataframe with np.empty data of appropriate type def method3(): # Create fake data array data = np.transpose(np.array([np.empty(n_rows*num_dfs, dtype=dt) for dt in df_list[0].dtypes])) # Create placeholder dataframe out_df3 = pd.DataFrame(data) # Fill in the real values for ix, df in enumerate(df_list): out_df3.iloc[ix*n_rows:(ix+1)*n_rows, :] = df.values return out_df3 ## # Time both methods ## # Time Method 1 times_1 = np.empty(n_reps) for i in np.arange(n_reps): with Timer() as t: df1 = method1() times_1[i] = t.secs print 'Total time for %d repetitions of Method 1: %f [sec]' % (n_reps, np.sum(times_1)) print 'Best time: %f' % (np.min(times_1)) print 'Mean time: %f' % (np.mean(times_1)) #>> Total time for 100 repetitions of Method 1: 2.928296 [sec] #>> Best time: 0.028532 #>> Mean time: 0.029283 # Time Method 2 times_2 = np.empty(n_reps) for i in np.arange(n_reps): with Timer() as t: df2 = method2() times_2[i] = t.secs print 'Total time for %d repetitions of Method 2: %f [sec]' % (n_reps, np.sum(times_2)) print 'Best time: %f' % (np.min(times_2)) print 'Mean time: %f' % (np.mean(times_2)) #>> Total time for 100 repetitions of Method 2: 32.143247 [sec] #>> Best time: 0.315075 #>> Mean time: 0.321432 # Time Method 3 times_3 = np.empty(n_reps) for i in np.arange(n_reps): with Timer() as t: df3 = method3() times_3[i] = t.secs print 'Total time for %d repetitions of Method 3: %f [sec]' % (n_reps, np.sum(times_3)) print 'Best time: %f' % (np.min(times_3)) print 'Mean time: %f' % (np.mean(times_3)) #>> Total time for 100 repetitions of Method 3: 6.577038 [sec] #>> Best time: 0.063437 #>> Mean time: 0.065770 

Я использую любезность Timer от Huy Nguyen:

 # credit: http://www.huyng.com/posts/python-performance-analysis/ import time class Timer(object): def __init__(self, verbose=False): self.verbose = verbose def __enter__(self): self.start = time.clock() return self def __exit__(self, *args): self.end = time.clock() self.secs = self.end - self.start self.msecs = self.secs * 1000 # millisecs if self.verbose: print 'elapsed time: %f ms' % self.msecs 

Если вы все еще следуете, у меня есть два вопроса:

1) Почему метод append быстрее? (ПРИМЕЧАНИЕ: для очень маленьких фреймов данных, т. n_rows = 40 , это на самом деле медленнее).

2) Каков наиболее эффективный способ создания большого блока данных из кусков? (В моем случае куски – это большие файлы csv).

Спасибо за вашу помощь!

EDIT_1: В моем проекте реального мира столбцы имеют разные типы. Поэтому я не могу использовать pd.DataFrame(.... dtype=some_type) чтобы улучшить производительность preallocation, по рекомендации BrenBarn. Параметр dtype заставляет все столбцы быть одним и тем же типом [Ref. выпуск 4464]

Я добавил несколько строк в method2() в моем коде, чтобы изменить столбцы по столбцам dtypes, чтобы они соответствовали входным фреймворкам данных. Эта операция является дорогостоящей и отрицает преимущества наличия соответствующих типов при записи блоков строк.

EDIT_2: попробуйте предварительно распределить фрейм данных с использованием массива-заполнителя np.empty(... dtyp=some_type) . Предложение Пер @ Джориса.

4 Solutions collect form web for “Создание больших Pandas DataFrames: preallocation vs append vs concat”

Ваш тест на самом деле слишком мал, чтобы показать реальную разницу. Добавляя, копирует EACH time, поэтому вы на самом деле выполняете копирование размера N памяти N * (N-1) раз. Это ужасно неэффективно по мере роста размера вашего фреймворка. Это, конечно, может не иметь значения в очень маленькой рамке. Но если у вас есть реальные размеры, это очень важно. Это специально указано в документах здесь , хотя это небольшое предупреждение.

 In [97]: df = DataFrame(np.random.randn(100000,20)) In [98]: df['B'] = 'foo' In [99]: df['C'] = pd.Timestamp('20130101') In [103]: df.info() <class 'pandas.core.frame.DataFrame'> Int64Index: 100000 entries, 0 to 99999 Data columns (total 22 columns): 0 100000 non-null float64 1 100000 non-null float64 2 100000 non-null float64 3 100000 non-null float64 4 100000 non-null float64 5 100000 non-null float64 6 100000 non-null float64 7 100000 non-null float64 8 100000 non-null float64 9 100000 non-null float64 10 100000 non-null float64 11 100000 non-null float64 12 100000 non-null float64 13 100000 non-null float64 14 100000 non-null float64 15 100000 non-null float64 16 100000 non-null float64 17 100000 non-null float64 18 100000 non-null float64 19 100000 non-null float64 B 100000 non-null object C 100000 non-null datetime64[ns] dtypes: datetime64[ns](1), float64(20), object(1) memory usage: 17.5+ MB 

Прикрепление

 In [85]: def f1(): ....: result = df ....: for i in range(9): ....: result = result.append(df) ....: return result ....: 

Concat

 In [86]: def f2(): ....: result = [] ....: for i in range(10): ....: result.append(df) ....: return pd.concat(result) ....: In [100]: f1().equals(f2()) Out[100]: True In [101]: %timeit f1() 1 loops, best of 3: 1.66 s per loop In [102]: %timeit f2() 1 loops, best of 3: 220 ms per loop 

Обратите внимание, что я даже не буду пытаться предварительно выделить. Это несколько сложно, особенно потому, что вы имеете дело с несколькими типами (например, вы можете создать гигантскую рамку и просто .loc и это сработает). Но pd.concat просто мертв, работает надежно и быстро.

И время ваших размеров сверху

 In [104]: df = DataFrame(np.random.randn(2500,40)) In [105]: %timeit f1() 10 loops, best of 3: 33.1 ms per loop In [106]: %timeit f2() 100 loops, best of 3: 4.23 ms per loop 

@Jeff, pd.concat побеждает на милю! Я pd.concat четвертый метод, используя pd.concat с num_dfs = 500 . Результаты недвусмысленно:

Определение method4() :

 # Method 4 - us pd.concat on df_list def method4(): return pd.concat(df_list, ignore_index=True) 

Результаты профилирования, используя тот же Timer в моем исходном вопросе:

 Total time for 100 repetitions of Method 1: 3679.334655 [sec] Best time: 35.570036 Mean time: 36.793347 Total time for 100 repetitions of Method 2: 1569.917425 [sec] Best time: 15.457102 Mean time: 15.699174 Total time for 100 repetitions of Method 3: 325.730455 [sec] Best time: 3.192702 Mean time: 3.257305 Total time for 100 repetitions of Method 4: 25.448473 [sec] Best time: 0.244309 Mean time: 0.254485 

Метод pd.concat в 13 раз быстрее, чем предварительное np.empty(... dtype) с помощью np.empty(... dtype) .

Вы не указали какие-либо данные или тип для out_df2 , поэтому он имеет тип «object». Это позволяет присваивать ему значения очень медленно. Укажите тип float64:

 out_df2 = pd.DataFrame(columns=np.arange(n_cols), index=np.arange(num_dfs*n_rows), dtype=np.float64) 

Вы увидите резкое ускорение. Когда я попробовал это, method2 с этим изменением примерно в два раза быстрее, чем method1 .

Ответ Джеффа правильный, но я нашел, что для моего типа данных другое решение работает лучше.

 def df_(): return pd.DataFrame(['foo']*np.random.randint(100)).transpose() k = 100 frames = [df_() for x in range(0, k)] def f1(): result = frames[0] for i in range(k-1): result = result.append(frames[i+1]) return result def f2(): result = [] for i in range(k): result.append(frames[i]) return pd.concat(result) def f3(): result = [] for i in range(k): result.append(frames[i]) n = 2 while len(result) > 1: _result = [] for i in range(0, len(result), n): _result.append(pd.concat(result[i:i+n])) result = _result return result[0] 

Мое числовые кадры – это одна строка и различной длины – нулевые записи должны иметь какое-то отношение к тому, почему f3 () преуспевает.

 In [33]: f1().equals(f2()) Out[33]: True In [34]: f1().equals(f3()) Out[34]: True In [35]: %timeit f1() 357 ms ± 192 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [36]: %timeit f2() 562 ms ± 68.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [37]: %timeit f3() 215 ms ± 58.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

Вышеприведенные результаты по-прежнему для k = 100, но при больших k это еще более значимо.

  • Экспорт таблицы LaTeX из pandas DataFrame
  • как заполнить недостающее значение при построении DataFrame?
  • Как удалить строку с определенной строкой с нулевым значением
  • Ряды фильтров Pandas
  • Вопросы проектирования Pandas для мультииндексированных Dataframes
  • Расширенное поперечное сечение с мультииндексами в пандах
  • Как удалить столбцы, которые имеют одинаковые значения во всех строках через панды или искробезопасный фрейм?
  • Pandas и HDF5, запрашивая таблицу, строку, содержащую символ '&'
  • Как удалить последнюю строку данных фрейма pandas
  • pandas не могут читать из большого объекта StringIO
  • усредняя каждые пять минут данные как один набор данных в кадре данных панд
  •  
    Interesting Posts for Van-Lav

    Как использовать encode (Python 3), чтобы исправить код не-ascii для импорта CSV в Pandas?

    Обработка пар значений из двух последовательностей в Clojure

    В какой ситуации должен использоваться встроенный модуль «operator» в python?

    Django 1.11. Контекст TypeError должен быть dict, а не Context

    запись на stdin, а также правильная распечатка результатов

    Переменная экземпляра Python как параметр по умолчанию

    Сетевой мост с использованием Scapy и Python

    Редактировать панды DataFrame с использованием индексов

    есть ли Java-эквивалент Python defaultdict?

    Сжатие нескольких сжатых потоков данных zlib в один поток эффективно

    Использовать монгольский сервер MongoDB для модульного тестирования

    Python PIP не показывает пакет pypi

    Насколько различен тип .__ setattr__ из объекта .__ setattr__?

    Подпроцесс Python.Popen связывается по конвейеру

    Почему я могу получить доступ к объекту во время его сообщения post_save, но не тогда, когда я запускаю код внутри этого сигнала, который вызывает его в другом процессе

    Python - лучший язык программирования в мире.