8

У меня есть функция, которая обрабатывает DataFrame, в основном для обработки данных в ковши, создает двоичную матрицу признаков в определенном столбце, используя pd.get_dummies(df[col]).Почему конкатенация DataFrames экспоненциально медленнее?

Чтобы избежать обработки всех моих данных, используя эту функцию сразу (которая выходит из памяти и вызывает IPython врезаться), я сломал большой DataFrame на куски с помощью:

chunks = (len(df)/10000) + 1 
df_list = np.array_split(df, chunks) 

pd.get_dummies(df) будет автоматически создавать новые столбцы на основе содержимого df[col], и они могут различаться для каждого df в df_list.

После обработки, я конкатенация DataFrames обратно вместе с помощью:

for i, df_chunk in enumerate(df_list): 
    print "chunk", i 
    [x, y] = preprocess_data(df_chunk) 
    super_x = pd.concat([super_x, x], axis=0) 
    super_y = pd.concat([super_y, y], axis=0) 
    print datetime.datetime.utcnow() 

Время обработки первого фрагмента вполне допустимо, однако, он растет на кусок! Это не связано с preprocess_data(df_chunk), так как нет никаких причин для его увеличения. Является ли это увеличением времени, возникающего в результате вызова pd.concat()?

Пожалуйста см войти ниже:

chunks 6 
chunk 0 
2016-04-08 00:22:17.728849 
chunk 1 
2016-04-08 00:22:42.387693 
chunk 2 
2016-04-08 00:23:43.124381 
chunk 3 
2016-04-08 00:25:30.249369 
chunk 4 
2016-04-08 00:28:11.922305 
chunk 5 
2016-04-08 00:32:00.357365 

Есть обходной путь, чтобы ускорить этот процесс? У меня 2900 кусков для обработки, поэтому любая помощь приветствуется!

Открыть любые другие предложения на Python!

ответ

8

Никогда не называть DataFrame.append или pd.concat внутри для петли. Это приводит к квадратичному копированию.

pd.concat возвращает новый DataFrame. Пространство должно быть выделено для нового DataFrame, а данные из старых DataFrames должны быть скопированы в новый DataFrame. Рассмотрим объем копирования, требуемую этой линии внутри for-loop (предполагается, что каждая x имеет размер 1):

super_x = pd.concat([super_x, x], axis=0) 

| iteration | size of old super_x | size of x | copying required | 
|   0 |     0 |   1 |    1 | 
|   1 |     1 |   1 |    2 | 
|   2 |     2 |   1 |    3 | 
|  ... |      |   |     | 
|  N-1 |     N-1 |   1 |    N | 

1 + 2 + 3 + ... + N = N(N-1)/2. Таким образом, требуется O(N**2) копий, необходимых для заполнения .

Теперь рассмотрим

super_x = [] 
for i, df_chunk in enumerate(df_list): 
    [x, y] = preprocess_data(df_chunk) 
    super_x.append(x) 
super_x = pd.concat(super_x, axis=0) 

прилагая к списку является O(1) операция и не требует копирования. Теперь есть один вызов pd.concat после завершения цикла. Этот призыв к pd.concat требует N копии должны быть сделаны, так как super_x содержит N DataFrames размера 1. Итак, когда построен таким образом, super_x требует O(N) копий.

+0

Привет, @unutbu, спасибо за подробное объяснение, это действительно объясняет теорию в деталях! – jfive

+0

Можно ли объединить 2900 блоков этой формы, таким образом (43717, 3261)? Теперь шаг обработки занимает всего 10 секунд. – jfive

4

Каждый раз, когда вы конкатенируете, вы возвращаете копию данных.

Вы хотите сохранить список своих кусков, а затем объединить все как последний шаг.

df_x = [] 
df_y = [] 
for i, df_chunk in enumerate(df_list): 
    print "chunk", i 
    [x, y] = preprocess_data(df_chunk) 
    df_x.append(x) 
    df_y.append(y) 

super_x = pd.concat(df_x, axis=0) 
del df_x # Free-up memory. 
super_y = pd.concat(df_y, axis=0) 
del df_y # Free-up memory. 
+0

Большое спасибо, это исправило проблему! – jfive