Почему конкатенация DataFrames экспоненциально медленнее? - программирование
Подтвердить что ты не робот

Почему конкатенация DataFrames экспоненциально медленнее?

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

Чтобы не обрабатывать все мои данные с помощью этой функции сразу (что выходит из памяти и приводит к сбою 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!

4b9b3361

Ответ 1

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

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 Кадры данных размера 1. Поэтому при построении таким образом super_x требуется O(N) копии.

Ответ 2

Каждый раз, когда вы объединяетесь, вы возвращаете копию данных.

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

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.