Подтвердить что ты не робот

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

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

Это что-то, что испытывают другие? Является ли оно специфичным для 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]
4b9b3361

Ответ 1

Обычно 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 не itertuples данные в серию, а просто возвращает их как кортеж

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

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

Ответ 2

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

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

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

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

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

Ответ 3

Вот как это сделать. Все это векторизовано.

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

Ответ 4

Другой вариант - использовать 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 раз быстрее исходного кода. Стратегия такова:

  • Используйте groupby, чтобы избежать повторного сравнения значений.
  • Используйте to_records для доступа к объектам raw numrec.records.
  • Не работайте с DataFrame, пока не скомпилируете все данные.

Ответ 5

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

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