Есть ли у iterrows проблемы с производительностью?

Я заметил очень низкую производительность при использовании iterrows из панд.

Это то, что испытывают другие? Это специфично для iterrows и следует ли избегать этой функции для данных определенного размера (я работаю с 2-3 миллионами строк)?

Это обсуждение GitHub заставило меня поверить, что это вызвано смешиванием dtypes в кадре данных, однако простой пример, приведенный ниже, показывает, что он существует даже при использовании одного dtype (float64). Это займет 36 секунд на моей машине:

import pandas as pd
import numpy as np
import time

s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})

start = time.time()
i=0
for rowindex, row in dfa.iterrows():
    i+=1
end = time.time()
print end - start

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

Я не могу понять, как не использовать iterrows в моем случае (это я сохраню для будущего вопроса). Поэтому я был бы признателен, если бы вы последовательно смогли избежать этой итерации. Я делаю расчеты на основе данных в отдельных фреймах данных. Спасибо!

--- Изменить: упрощенная версия того, что я хочу запустить, была добавлена ​​ниже ---

import pandas as pd
import numpy as np

#%% Create the original tables
t1 = {'letter':['a','b'],
      'number1':[50,-10]}

t2 = {'letter':['a','a','b','b'],
      'number2':[0.2,0.5,0.1,0.4]}

table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)

#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])

#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():   
    t2info = table2[table2.letter == row['letter']].reset_index()
    table3.ix[row_index,] = optimize(t2info,row['number1'])

#%% Define optimization
def optimize(t2info, t1info):
    calculation = []
    for index, r in t2info.iterrows():
        calculation.append(r['number2']*t1info)
    maxrow = calculation.index(max(calculation))
    return t2info.ix[maxrow]

8 ответов

Решение

В общем-то, iterrows следует использовать только в очень специфических случаях. Это общий порядок приоритета для выполнения различных операций:

1) vectorization
2) using a custom cython routine
3) apply
    a) reductions that can be performed in cython
    b) iteration in python space
4) itertuples
5) iterrows
6) updating an empty frame (e.g. using loc one-row-at-a-time)

Использование пользовательской подпрограммы Cython обычно слишком сложно, поэтому давайте пока пропустим это.

1) Векторизация ВСЕГДА ВСЕГДА - первый и лучший выбор. Тем не менее, существует небольшой набор случаев, которые нельзя векторизовать очевидными способами (в основном с повторением). Кроме того, на небольшом кадре может быть быстрее сделать другие методы.

3) Применение может обычно выполняться итератором в пространстве Cython (это делается внутри панд) (это а).

Это зависит от того, что происходит внутри выражения применения. например df.apply(lambda x: np.sum(x)) будет выполнен довольно быстро (конечно, df.sum(1) еще лучше). Однако что-то вроде: df.apply(lambda x: x['b'] + 1) будет выполняться в пространстве Python, и, следовательно, медленнее.

4) itertuples не упаковывает данные в серию, просто возвращает их в виде кортежа

5) iterrows ДЕЛАЕТ упаковывать данные в Серию. Если вам это действительно не нужно, используйте другой метод.

6) обновление пустого фрейма по одной строке за раз. Я видел, что этот метод использовал слишком много. Это безусловно самый медленный. Это, вероятно, обычное место (и достаточно быстрое для некоторых структур python), но DataFrame выполняет достаточное количество проверок при индексировании, поэтому обновление строки всегда будет происходить очень медленно. Намного лучше создавать новые структуры и concat,

Векторные операции в Numpy и pandas намного быстрее, чем скалярные операции в vanilla Python по нескольким причинам:

Амортизированный поиск типа

Python - это язык с динамической типизацией, поэтому для каждого элемента в массиве накладные расходы. Тем не менее, Numpy (и, следовательно, панды) выполняют вычисления в C (часто через Cython). Тип массива определяется только в начале итерации; одни только эти сбережения - одна из самых больших побед.

Лучшее кеширование

Итерация по массиву C удобна для кэша и, следовательно, очень быстра. DataFrame pandas - это "таблица, ориентированная на столбцы", что означает, что каждый столбец на самом деле является просто массивом. Таким образом, собственные действия, которые вы можете выполнять в DataFrame (например, суммирование всех элементов в столбце), будут иметь несколько ошибок в кэше.

Больше возможностей для параллелизма

С простым массивом C можно работать с помощью SIMD-инструкций. Некоторые части Numpy включают SIMD, в зависимости от вашего процессора и процесса установки. Преимущества параллелизма не будут такими существенными, как статическая типизация и лучшее кеширование, но они все же являются надежной победой.

Мораль истории: используйте векторные операции в Numpy и Pandas. Они выполняются быстрее, чем скалярные операции в Python, по той простой причине, что эти операции - именно то, что программист C написал бы вручную в любом случае. (За исключением того, что понятие массива гораздо легче читать, чем явные циклы со встроенными SIMD-инструкциями.)

Вот способ решить вашу проблему. Это все векторизовано.

In [58]: df = table1.merge(table2,on='letter')

In [59]: df['calc'] = df['number1']*df['number2']

In [60]: df
Out[60]: 
  letter  number1  number2  calc
0      a       50      0.2    10
1      a       50      0.5    25
2      b      -10      0.1    -1
3      b      -10      0.4    -4

In [61]: df.groupby('letter')['calc'].max()
Out[61]: 
letter
a         25
b         -1
Name: calc, dtype: float64

In [62]: df.groupby('letter')['calc'].idxmax()
Out[62]: 
letter
a         1
b         2
Name: calc, dtype: int64

In [63]: df.loc[df.groupby('letter')['calc'].idxmax()]
Out[63]: 
  letter  number1  number2  calc
1      a       50      0.5    25
2      b      -10      0.1    -1

НЕ ИСПОЛЬЗУЙТЕ iterrows!

...Или же iteritems, или же itertuples. Серьезно, не надо. По возможности старайтесь векторизовать свой код . Если вы мне не верите, спросите Джеффа .

Я признаю, что есть законные варианты использования для итерации по DataFrame, но есть гораздо лучшие альтернативы для итерации, чем iter* семейные функции, а именно

Часто слишком много новичков, чтобы панды задавали вопросы, связанные с кодом, который имеет к нему какое-то отношение. Поскольку эти новые пользователи, вероятно, не знакомы с концепцией векторизации, они представляют код, решающий их проблему, как нечто, включающее циклы или другие итерационные процедуры. Не зная, как выполнять итерацию, они обычно приходят к этому вопросу и узнают все неправильные вещи.


Подтверждающие аргументы

На странице документации по итерации есть огромное красное окно с предупреждением, в котором говорится:

Итерация по объектам pandas обычно выполняется медленно. Во многих случаях повторение строк вручную не требуется [...].

Если это вас не убеждает, взгляните на сравнение производительности векторизованных и не векторизованных методов добавления двух столбцов «A + B», взятого из моего сообщения здесь .

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

Строка внизу измеряет функцию, написанную в numpandas, стиле Pandas, который сильно смешивается с NumPy, чтобы выжать максимальную производительность. Следует избегать написания кода numpandas, если вы не знаете, что делаете. Придерживайтесь API там, где можете (т. Е. Предпочитаете vec над vec_numpy).


В заключении

Всегда стремитесь к векторизации. Иногда, в зависимости от характера вашей проблемы или данных, это не всегда возможно, поэтому ищите лучшие итерационные процедуры, чем iterrows. Для этого почти никогда не существует законного варианта использования, кроме удобства при работе с чрезвычайно малым количеством строк, в противном случае будьте готовы к длительному ожиданию, пока ваш код может работать часами.

Просмотрите ссылки ниже, чтобы определить лучший метод / векторизованную процедуру для решения вашего кода.

Другой вариант заключается в использовании to_records(), который быстрее, чем оба itertuples а также iterrows,

Но для вашего случая есть много возможностей для других типов улучшений.

Вот моя последняя оптимизированная версия

def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    t2info = table2.to_records()
    for index, letter, n1 in table1.to_records():
        t2 = t2info[grouped.groups[letter].values]
        # np.multiply is in general faster than "x * y"
        maxrow = np.multiply(t2.number2, n1).argmax()
        # `[1:]`  removes the index column
        ret.append(t2[maxrow].tolist()[1:])
    global table3
    table3 = pd.DataFrame(ret, columns=('letter', 'number2'))

Контрольный тест:

-- iterrows() --
100 loops, best of 3: 12.7 ms per loop
  letter  number2
0      a      0.5
1      b      0.1
2      c      5.0
3      d      4.0

-- itertuple() --
100 loops, best of 3: 12.3 ms per loop

-- to_records() --
100 loops, best of 3: 7.29 ms per loop

-- Use group by --
100 loops, best of 3: 4.07 ms per loop
  letter  number2
1      a      0.5
2      b      0.1
4      c      5.0
5      d      4.0

-- Avoid multiplication --
1000 loops, best of 3: 1.39 ms per loop
  letter  number2
0      a      0.5
1      b      0.1
2      c      5.0
3      d      4.0

Полный код:

import pandas as pd
import numpy as np

#%% Create the original tables
t1 = {'letter':['a','b','c','d'],
      'number1':[50,-10,.5,3]}

t2 = {'letter':['a','a','b','b','c','d','c'],
      'number2':[0.2,0.5,0.1,0.4,5,4,1]}

table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)

#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index)


print('\n-- iterrows() --')

def optimize(t2info, t1info):
    calculation = []
    for index, r in t2info.iterrows():
        calculation.append(r['number2'] * t1info)
    maxrow_in_t2 = calculation.index(max(calculation))
    return t2info.loc[maxrow_in_t2]

#%% Iterate through filtering relevant data, optimizing, returning info
def iterthrough():
    for row_index, row in table1.iterrows():   
        t2info = table2[table2.letter == row['letter']].reset_index()
        table3.iloc[row_index,:] = optimize(t2info, row['number1'])

%timeit iterthrough()
print(table3)

print('\n-- itertuple() --')
def optimize(t2info, n1):
    calculation = []
    for index, letter, n2 in t2info.itertuples():
        calculation.append(n2 * n1)
    maxrow = calculation.index(max(calculation))
    return t2info.iloc[maxrow]

def iterthrough():
    for row_index, letter, n1 in table1.itertuples():   
        t2info = table2[table2.letter == letter]
        table3.iloc[row_index,:] = optimize(t2info, n1)

%timeit iterthrough()


print('\n-- to_records() --')
def optimize(t2info, n1):
    calculation = []
    for index, letter, n2 in t2info.to_records():
        calculation.append(n2 * n1)
    maxrow = calculation.index(max(calculation))
    return t2info.iloc[maxrow]

def iterthrough():
    for row_index, letter, n1 in table1.to_records():   
        t2info = table2[table2.letter == letter]
        table3.iloc[row_index,:] = optimize(t2info, n1)

%timeit iterthrough()

print('\n-- Use group by --')

def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    for index, letter, n1 in table1.to_records():
        t2 = table2.iloc[grouped.groups[letter]]
        calculation = t2.number2 * n1
        maxrow = calculation.argsort().iloc[-1]
        ret.append(t2.iloc[maxrow])
    global table3
    table3 = pd.DataFrame(ret)

%timeit iterthrough()
print(table3)

print('\n-- Even Faster --')
def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    t2info = table2.to_records()
    for index, letter, n1 in table1.to_records():
        t2 = t2info[grouped.groups[letter].values]
        maxrow = np.multiply(t2.number2, n1).argmax()
        # `[1:]`  removes the index column
        ret.append(t2[maxrow].tolist()[1:])
    global table3
    table3 = pd.DataFrame(ret, columns=('letter', 'number2'))

%timeit iterthrough()
print(table3)

Финальная версия почти в 10 раз быстрее оригинального кода. Стратегия такова:

  1. использование groupby избегать повторного сравнения значений.
  2. использование to_records для доступа к необработанным объектам numpy.records.
  3. Не работайте с DataFrame, пока не скомпилируете все данные.

Подробности в этом видео

Контрольный показатель

Если вам действительно нужно выполнить итерацию и получить доступ к полю строки по имени, просто сохраните имена столбцов в списке и преобразуйте фрейм данных в массив numpy:

      import pandas as pd
import numpy as np
import time

s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})
columns = list(dfa.columns)
dfa = dfa.values
start = time.time()
i=0
for row in dfa:
    blablabla = row[columns.index('s1')]
    i+=1
end = time.time()
print (end - start)

0,9485495090484619

Да, Pandas itertuples() работает быстрее, чем iterrows(). вы можете обратиться к документации: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html

"Чтобы сохранить dtypes во время итерации по строкам, лучше использовать itertuples (), которая возвращает именованные кортежи значений и, как правило, быстрее, чем iterrows".

Другие вопросы по тегам