Меня смущает производительность в Pandas при создании большого фрагмента блока данных куском. В Numpy мы (почти) всегда видим лучшую производительность, предварительно распределяя большой пустой массив и затем заполняя значения. Насколько я понимаю, это связано с тем, что Numpy захватывает всю память, которая ему нужна, вместо того, чтобы перераспределять память при каждой операции append
.
В Pandas я, кажется, получаю лучшую производительность, используя шаблон df = df.append(temp)
.
Вот пример времени. Далее следует определение класса Timer
. Как вы видите, я обнаружил, что preallocating примерно в 10 раз медленнее, чем при использовании append
! Предварительное выделение кадра данных с 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. issue 4464]
Я добавил несколько строк в method2()
в свой код, чтобы изменить столбцы по столбцам dtypes для соответствия во входных данных. Эта операция является дорогостоящей и отрицает преимущества наличия соответствующих типов при записи блоков строк.
EDIT_2: попробуйте предварительно распределить фрейм с использованием массива-заполнителя np.empty(... dtyp=some_type)
. Per @Joris предложение.