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

96

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

Это то, что переживают другие? Это специфично для 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]
KieranPC
источник
7
applyНЕ векторизуется. iterrowsеще хуже, поскольку он упаковывает все (то есть перфоманс apply). Вы должны использовать его только iterrowsв очень редких ситуациях. ИМХО никогда. Покажите, что вы на самом деле делаете iterrows.
Джефф
2
Проблема, с которой вы связались вместо этого, связана с упаковкой a DatetimeIndexв Timestamps(была реализована в пространстве Python), и это было значительно улучшено в master.
Джефф
1
См. Этот вопрос для более полного обсуждения: github.com/pydata/pandas/issues/7194 .
Джефф
Ссылка на конкретный вопрос (этот останется общим): stackoverflow.com/questions/24875096/…
KieranPC
Пожалуйста, не рекомендуем использовать iterrows (). Это явный стимул к худшему антипаттерну в истории панд.
cs95 09

Ответы:

188

Как правило, 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) Векторизация - ВСЕГДА, ВСЕГДА первый и лучший выбор. Однако есть небольшой набор случаев (обычно связанных с повторением), которые нельзя векторизовать очевидными способами. Кроме того, на небольшомDataFrame может быть быстрее использовать другие методы.

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

4) itertuplesне помещает данные вSeries . Он просто возвращает данные в виде кортежей.

5) iterrowsЗАКРЫВАЕТ данные вSeries . Если вам это действительно не нужно, используйте другой метод.

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

Джефф
источник
1
Да, я использовал номер 6 (и 5). Мне нужно кое-чему научиться. Это кажется очевидным выбором для относительного новичка.
KieranPC
3
По моему опыту, разница между 3, 4 и 5 ограничена в зависимости от варианта использования.
IanS 09
8
Я пробовал проверить время выполнения в этом ноутбуке . Как-то itertuplesбыстрее, чем apply:(
Dimgold 06
1
pd.DataFrame.applyчасто медленнее, чем itertuples. Кроме того, стоит рассмотреть понимание списков, mapплохо названных np.vectorizeи numba(без определенного порядка) для невекторизуемых вычислений, например, см. Этот ответ .
jpp
2
@ Джефф, из любопытства, почему ты не добавил сюда понимание списка? Хотя это правда, что они не обрабатывают выравнивание индекса или отсутствующие данные (если вы не используете функцию с try-catch), они подходят для многих случаев использования (строковые / регулярные выражения), где методы pandas не векторизованы ( в прямом смысле этого слова) реализации. Как вы думаете, стоит ли упомянуть, что LC - это более быстрая альтернатива pandas с меньшими издержками и многими строковыми функциями pandas?
cs95
17

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

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

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

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

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

Chrisaycock
источник
11

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

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
Джефф
источник
Очень четкий ответ, спасибо. Я попытаюсь выполнить слияние, но у меня есть сомнения, так как тогда у меня будет 5 миллиардов строк (2,5 миллиона * 2000). Чтобы сохранить этот Q в целом, я создал конкретный Q. Я был бы рад увидеть альтернативу, чтобы избежать этой гигантской таблицы, если вы ее знаете: здесь: stackoverflow.com/questions/24875096/…
KieranPC
1
это не создает декартово произведение - это сжатое пространство и довольно эффективно использует память. то, что вы делаете, - очень стандартная проблема. попробуй. (у вашего связанного вопроса очень похожий soln)
Джефф
7

Другой вариант - использовать 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, пока не соберете все данные.
Пиво Полор
источник
0

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

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

Вандана Шарма
источник