Чи є у індрів панд проблеми з продуктивністю?


96

Я помітив дуже низьку продуктивність при використанні стрілок із панд.

Це те, що переживають інші? Це специфічно для ітерацій і чи слід уникати цієї функції для даних певного розміру (я працюю з 2-3 мільйонами рядків)?

Ця дискусія на GitHub привела мене до думки, що це спричинено змішуванням dтипів у фреймі даних, однак простий приклад нижче показує, що він є навіть при використанні одного 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]

7
applyНЕ векторизується. iterrowsще гірше, оскільки він боксує все (що 'різниця з перфорацією apply). Ви повинні використовувати лише iterrowsв дуже незначних ситуаціях. ІМХО ніколи. Покажіть, чим ви насправді займаєтесь iterrows.
Джефф,

2
Проблема, до якої ви зв’язали, пов’язана з боксом a DatetimeIndexу Timestamps(було реалізовано в просторі python), і це було значно покращено в master.
Джефф

1
Дивіться це питання для більш повного обговорення: github.com/pydata/pandas/issues/7194 .
Джефф

Посилання на конкретне запитання (це один залишиться взагалі): stackoverflow.com/questions/24875096 / ...
KieranPC

Будь ласка, не рекомендуйте використовувати ітерації (). Це кричущий фактор найгіршого анти-шаблону в історії панд.
cs95

Відповіді:


186

Як правило, 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. Це обробляється внутрішньо пандами, хоча це залежить від того, що відбувається всередині applyвиразу. Наприклад, df.apply(lambda x: np.sum(x))буде виконано досить швидко, хоча, звичайно, df.sum(1)це навіть краще. Однак щось подібне df.apply(lambda x: x['b'] + 1)буде виконуватися в просторі Python, а отже, набагато повільніше.

4) itertuplesне розміщує дані в Series. Він просто повертає дані у вигляді кортежів.

5) iterrowsМОЖЕ вкласти дані в a Series. Якщо це вам дійсно не потрібно, використовуйте інший метод.

6) Оновлення порожнього кадру по одному рядку. Я бачив, що цей метод застосовується ШАЙБ занадто часто. Це на сьогоднішній день найповільніше. Це, мабуть, звичайне місце (і досить швидко для деяких структур python), але a DataFrameробить чималу кількість перевірок щодо індексації, тому оновлення рядка за раз буде завжди дуже повільним. Набагато краще створювати нові структури і concat.


1
Так, я використовував номер 6 (і 5). Мені потрібно навчитися. Це здається очевидним вибором для відносного новачка.
KieranPC

3
З мого досвіду, різниця між 3, 4 та 5 обмежена залежно від випадку використання.
IanS

8
Я намагався перевірити час роботи в цьому блокноті . Якось itertuplesшвидше, ніж apply:(
Dimgold

1
pd.DataFrame.applyчасто повільніше, ніж itertuples. Крім того, варто врахувати розуміння списків, mapпогано названих np.vectorizeта numba(без певного порядку) для невекторизованих розрахунків, наприклад, див. Цю відповідь .
jpp

2
@ Джефе, з цікавості, чому ти сюди не додав розуміння списку? Хоча це правда, що вони не обробляють вирівнювання індексу або відсутні дані (якщо ви не використовуєте функцію з функцією try-catch), вони добре підходять для багатьох випадків використання (рядок / регулярні вирази), де методи панд не мають векторизації ( у прямому розумінні цього слова). Як ви вважаєте, чи варто згадувати, що LC - це швидша, нижча накладна альтернатива застосуванню панд і багатьох рядкових функцій панд?
cs95

17

Векторні операції в Numpy і пандах набагато швидші, ніж скалярні операції у ванільному Python з кількох причин:

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

  • Краще кешування : Ітерація над масивом C зручна для кешування і, отже, дуже швидко. Pandas DataFrame - це "таблиця, орієнтована на стовпець", що означає, що кожен стовпець насправді є просто масивом. Отже, власні дії, які ви можете виконувати над DataFrame (як підсумовування всіх елементів у стовпці), матимуть кілька помилок кешу.

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

Мораль історії: використовуйте векторні операції в Numpy та pandas. Вони швидші за скалярні операції в Python з тієї простої причини, що ці операції якраз і написали б від руки програміст. (За винятком того, що поняття масиву набагато легше читати, ніж явні цикли із вбудованими інструкціями SIMD.)


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
це не створює декартового продукту - це стислий простір і досить ефективна пам’ять. те, що ви робите, є дуже стандартною проблемою. спробуй. (у вашого пов'язаного запитання дуже схоже рішення)
Джефф,

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 (), який повертає іменовані значення значень і, як правило, швидший, ніж iterrow."


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.