Zauważyłem bardzo słabą wydajność podczas używania iterrows od pand.
Czy jest to coś, czego doświadczają inni? Czy jest to specyficzne dla iterrows i czy należy unikać tej funkcji dla danych o określonym rozmiarze (pracuję z 2-3 milionami wierszy)?
Ta dyskusja na GitHub doprowadziła mnie do przekonania, że jest to spowodowane mieszaniem dtypów w ramce danych, jednak prosty przykład poniżej pokazuje, że istnieje nawet przy użyciu jednego dtype (float64). Na moim komputerze trwa to 36 sekund:
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
Dlaczego wektoryzowane operacje, takie jak stosowane, są znacznie szybsze? Wyobrażam sobie, że musi tam zachodzić pewna iteracja wiersz po wierszu.
Nie mogę wymyślić, jak nie używać iterows w moim przypadku (to zachowam na przyszłe pytanie). Dlatego byłbym wdzięczny za wysłuchanie, gdybyś konsekwentnie był w stanie uniknąć tej iteracji. Obliczenia wykonuję na podstawie danych w oddzielnych ramkach danych. Dziękuję Ci!
--- Edycja: uproszczona wersja tego, co chcę uruchomić, została dodana poniżej ---
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]
źródło
apply
NIE jest wektoryzowany.iterrows
jest jeszcze gorszy, ponieważ wszystko pakuje (z tym „różnicą perf”apply
). Powinieneś używać tylkoiterrows
w bardzo niewielu sytuacjach. IMHO nigdy. Pokaż, z czym tak naprawdę robisziterrows
.DatetimeIndex
doTimestamps
(został zaimplementowany w przestrzeni Pythona) i został znacznie ulepszony w pliku master.Odpowiedzi:
Ogólnie
iterrows
powinno być używane tylko w bardzo, bardzo szczególnych przypadkach. Oto ogólna kolejność wykonywania różnych operacji: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)
Korzystanie z niestandardowej procedury Cythona jest zwykle zbyt skomplikowane, więc na razie pomińmy to.
1) Wektoryzacja jest ZAWSZE, ZAWSZE pierwszym i najlepszym wyborem. Istnieje jednak niewielki zbiór przypadków (zwykle obejmujących nawrót), których nie można wektoryzować w oczywisty sposób. Co więcej, w niewielkim stopniu
DataFrame
szybsze może być użycie innych metod.3)
apply
zwykle może być obsługiwane przez iterator w przestrzeni Cython. Jest to obsługiwane wewnętrznie przez pandy, choć zależy to od tego, co dzieje się wewnątrzapply
wyrażenia. Na przykład,df.apply(lambda x: np.sum(x))
zostanie wykonany dość szybko, choć oczywiściedf.sum(1)
jest jeszcze lepszy. Jednak coś podobnegodf.apply(lambda x: x['b'] + 1)
zostanie wykonane w przestrzeni Pythona, a co za tym idzie, będzie znacznie wolniejsze.4)
itertuples
nie opakowuje danych w plikSeries
. Po prostu zwraca dane w postaci krotek.5)
iterrows
CZY pakuje dane do plikuSeries
. Jeśli naprawdę tego nie potrzebujesz, użyj innej metody.6) Aktualizacja pustej ramki po jednym wierszu na raz. Widziałem, jak ta metoda jest zbyt często używana. Jest to zdecydowanie najwolniejszy. Prawdopodobnie jest to powszechne miejsce (i dość szybkie w przypadku niektórych struktur Pythona), ale a
DataFrame
wykonuje sporą liczbę kontroli indeksowania, więc aktualizacja wiersza na raz będzie zawsze bardzo powolna. Znacznie lepiej jest tworzyć nowe struktury iconcat
.źródło
itertuples
jest szybszy niżapply
:(pd.DataFrame.apply
jest często wolniejszy niżitertuples
. Dodatkowo warto rozważyć zdania listowe,map
słabo nazwanenp.vectorize
inumba
(w przypadkowej kolejności) dla obliczeń niewektorowalnych , np. Zobacz tę odpowiedź .Operacje wektorowe w Numpy i pandas są znacznie szybsze niż operacje skalarne w waniliowym Pythonie z kilku powodów:
Wyszukiwanie typów amortyzowanych : Python jest językiem z typami dynamicznymi, więc każdy element tablicy ma narzut w czasie wykonywania. Jednak Numpy (a tym samym pandy) wykonują obliczenia w języku C (często przez Cython). Typ tablicy jest określany tylko na początku iteracji; sama ta oszczędność jest jedną z największych korzyści.
Lepsze buforowanie : Iterowanie po tablicy C jest przyjazne dla pamięci podręcznej, a zatem bardzo szybkie. Pandy DataFrame to „tabela zorientowana na kolumny”, co oznacza, że każda kolumna jest w rzeczywistości tylko tablicą. Więc natywne akcje, które możesz wykonać na DataFrame (takie jak sumowanie wszystkich elementów w kolumnie) będą miały kilka chybień w pamięci podręcznej.
Więcej możliwości równoległości : Prosta tablica C może być obsługiwana za pomocą instrukcji SIMD. Niektóre części Numpy włączają SIMD, w zależności od procesora i procesu instalacji. Korzyści z równoległości nie będą tak dramatyczne, jak statyczne pisanie i lepsze buforowanie, ale nadal są solidną wygraną.
Morał z tej historii: użyj operacji na wektorach w Numpy i pandach. Są szybsze niż operacje skalarne w Pythonie z tego prostego powodu, że te operacje są dokładnie tym, co programista C i tak napisałby ręcznie. (Tyle że pojęcie tablicy jest znacznie łatwiejsze do odczytania niż jawne pętle z osadzonymi instrukcjami SIMD).
źródło
Oto sposób rozwiązania problemu. To wszystko jest wektoryzowane.
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
źródło
Inną opcją jest użycie
to_records()
, które jest szybsze niż obaitertuples
iiterrows
.Ale w twoim przypadku jest dużo miejsca na inne rodzaje ulepszeń.
Oto moja ostateczna zoptymalizowana wersja
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'))
Test porównawczy:
-- 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
Pełny kod:
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)
Ostateczna wersja jest prawie 10x szybsza niż oryginalny kod. Strategia jest następująca:
groupby
aby uniknąć wielokrotnego porównywania wartości.to_records
do uzyskiwania dostępu do surowych obiektów numpy.records.źródło
Tak, Pandas itertuples () jest szybszy niż iterrows (). możesz skorzystać z dokumentacji: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html
„Aby zachować dtypes podczas iteracji po wierszach, lepiej jest użyć itertuples (), która zwraca nazwane krotności wartości i jest generalnie szybsza niż iterrows”.
źródło
Szczegóły w tym filmie
Reper
źródło