Haben Pandas Iterrows Leistungsprobleme?


92

Ich habe eine sehr schlechte Leistung bei der Verwendung von Iterrows von Pandas festgestellt.

Ist das etwas, was andere erleben? Ist es spezifisch für Iterrows und sollte diese Funktion für Daten einer bestimmten Größe vermieden werden (ich arbeite mit 2-3 Millionen Zeilen)?

Diese Diskussion auf GitHub hat mich zu der Annahme geführt, dass dies beim Mischen von dtypes im Datenrahmen verursacht wird. Das einfache Beispiel unten zeigt jedoch, dass es auch bei Verwendung eines dtype (float64) vorhanden ist. Dies dauert auf meinem Computer 36 Sekunden:

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

Warum lassen sich vektorisierte Operationen wie so viel schneller anwenden? Ich stelle mir vor, dass dort auch eine zeilenweise Iteration stattfinden muss.

Ich kann nicht herausfinden, wie ich in meinem Fall keine Iterrows verwenden soll (dies werde ich für eine zukünftige Frage speichern). Daher würde ich mich freuen, wenn Sie diese Iteration konsequent vermeiden konnten. Ich mache Berechnungen basierend auf Daten in separaten Datenrahmen. Danke dir!

--- Bearbeiten: Eine vereinfachte Version von dem, was ich ausführen möchte, wurde unten hinzugefügt ---

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
applywird NICHT vektorisiert. iterrowsist noch schlimmer, da es alles einpackt (mit dem sich die Leistung unterscheidet apply). Sie sollten nur iterrowsin sehr wenigen Situationen verwenden. IMHO nie. Zeigen Sie, womit Sie tatsächlich arbeiten iterrows.
Jeff

2
Das Problem, mit dem Sie stattdessen verlinkt haben, hat mit dem Boxen eines DatetimeIndexIn zu tun Timestamps(wurde im Python-Bereich implementiert), und dies wurde im Master erheblich verbessert.
Jeff


Link zu der spezifischen Frage (diese bleibt allgemein): stackoverflow.com/questions/24875096/…
KieranPC

Bitte empfehlen Sie nicht die Verwendung von iterrows (). Es ist ein eklatanter Wegbereiter für das schlimmste Anti-Muster in der Geschichte der Pandas.
CS95

Antworten:


179

Im Allgemeinen iterrowssollte nur in sehr, sehr spezifischen Fällen verwendet werden. Dies ist die allgemeine Rangfolge für die Ausführung verschiedener Operationen:

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)

Die Verwendung einer benutzerdefinierten Cython-Routine ist normalerweise zu kompliziert. Lassen Sie uns dies zunächst überspringen.

1) Vektorisierung ist IMMER die erste und beste Wahl. Es gibt jedoch eine kleine Anzahl von Fällen (die normalerweise eine Wiederholung beinhalten), die nicht auf offensichtliche Weise vektorisiert werden können. Darüber hinaus auf einem kleinenDataFrame schneller sein, andere Methoden zu verwenden.

3) kann apply normalerweise von einem Iterator im Cython-Raum verarbeitet werden. Dies wird intern von Pandas erledigt, obwohl es davon abhängt, was im applyAusdruck vor sich geht. Zum Beispiel df.apply(lambda x: np.sum(x))wird ziemlich schnell ausgeführt, obwohl es natürlich df.sum(1)noch besser ist. Jedoch so etwas wiedf.apply(lambda x: x['b'] + 1) wird jedoch im Python-Raum ausgeführt und ist folglich viel langsamer.

4) itertuplespackt die Daten nicht in aSeries . Es werden nur die Daten in Form von Tupeln zurückgegeben.

5) iterrowsBoxt die Daten in aSeries . Verwenden Sie eine andere Methode, es sei denn, Sie benötigen dies wirklich.

6) Aktualisieren eines leeren Frames zeilenweise. Ich habe gesehen, dass diese Methode viel zu oft angewendet wurde. Es ist bei weitem das langsamste. Es ist wahrscheinlich üblich (und für einige Python-Strukturen relativ schnell), aber a DataFrameführt eine angemessene Anzahl von Überprüfungen der Indizierung durch, sodass das Aktualisieren einer Zeile immer sehr langsam ist. Viel besser neue Strukturen zu schaffen und concat.


1
Ja, ich habe Nummer 6 (und 5) verwendet. Ich muss etwas lernen. Es scheint die offensichtliche Wahl für einen relativen Anfänger zu sein.
KieranPC

3
Nach meiner Erfahrung ist der Unterschied zwischen 3, 4 und 5 je nach Anwendungsfall begrenzt.
IanS

8
Ich habe versucht, die Laufzeiten in diesem Notizbuch zu überprüfen . Irgendwie itertuplesist schneller als apply:(
Dimgold

1
pd.DataFrame.applyist oft langsamer als itertuples. Darüber hinaus lohnt es sich, das Listenverständnis map, die schlecht benannten np.vectorizeund numba(in keiner bestimmten Reihenfolge) für nicht vektorisierbare Berechnungen zu berücksichtigen, z . B. diese Antwort .
jpp

2
@ Jeff, aus Neugier, warum hast du hier kein Listenverständnis hinzugefügt? Zwar verarbeiten sie keine Indexausrichtung oder fehlende Daten (es sei denn, Sie verwenden eine Funktion mit einem Try-Catch), sie eignen sich jedoch für viele Anwendungsfälle (String- / Regex-Inhalte), in denen Pandas-Methoden nicht vektorisiert wurden ( im wahrsten Sinne des Wortes) Implementierungen. Denken Sie, dass es erwähnenswert ist, dass LCs eine schnellere Alternative mit geringerem Overhead zu Pandas und vielen Pandas-String-Funktionen sind?
CS95

17

Vektoroperationen in Numpy und Pandas sind aus mehreren Gründen viel schneller als Skalaroperationen in Vanilla Python:

  • Amortisierte Typensuche : Python ist eine dynamisch typisierte Sprache, sodass für jedes Element in einem Array ein Laufzeit-Overhead anfällt. Numpy (und damit Pandas) führen jedoch Berechnungen in C durch (häufig über Cython). Der Typ des Arrays wird erst zu Beginn der Iteration bestimmt. Diese Einsparungen allein sind einer der größten Gewinne.

  • Besseres Caching : Das Iterieren über ein C-Array ist cachefreundlich und daher sehr schnell. Ein Pandas DataFrame ist eine "spaltenorientierte Tabelle", was bedeutet, dass jede Spalte wirklich nur ein Array ist. Die nativen Aktionen, die Sie für einen DataFrame ausführen können (z. B. das Summieren aller Elemente in einer Spalte), weisen daher nur wenige Cache-Fehler auf.

  • Mehr Möglichkeiten für Parallelität : Ein einfaches C-Array kann über SIMD-Anweisungen bearbeitet werden. Einige Teile von Numpy aktivieren SIMD, abhängig von Ihrer CPU und dem Installationsprozess. Die Vorteile der Parallelität sind nicht so dramatisch wie die statische Eingabe und das bessere Caching, aber sie sind immer noch ein solider Gewinn.

Moral der Geschichte: Verwenden Sie die Vektoroperationen in Numpy und Pandas. Sie sind schneller als skalare Operationen in Python, weil diese Operationen genau das sind, was ein C-Programmierer ohnehin von Hand geschrieben hätte. (Abgesehen davon, dass der Array-Begriff viel einfacher zu lesen ist als explizite Schleifen mit eingebetteten SIMD-Anweisungen.)


10

Hier ist der Weg, um Ihr Problem zu lösen. Dies ist alles vektorisiert.

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

Sehr klare Antwort danke. Ich werde versuchen, zusammenzuführen, aber ich habe Zweifel, da ich dann 5 Milliarden Zeilen (2,5 Millionen * 2000) haben werde. Um dieses Q allgemein zu halten, habe ich ein spezifisches Q erstellt. Ich würde mich freuen, eine Alternative zu finden, um diesen riesigen Tisch zu vermeiden, wenn Sie einen kennen: hier: stackoverflow.com/questions/24875096/…
KieranPC

1
Dadurch wird das kartesische Produkt nicht erstellt - es ist ein komprimierter Bereich und ziemlich speichereffizient. Was Sie tun, ist ein sehr normales Problem. Versuche es. (Ihre verknüpfte Frage hat eine sehr ähnliche Lösung)
Jeff

7

Eine andere Option ist die Verwendung to_records(), die schneller ist als beide itertuplesunditerrows .

Für Ihren Fall gibt es jedoch viel Raum für andere Arten von Verbesserungen.

Hier ist meine endgültige optimierte Version

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'))

Benchmark-Test:

-- 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

Vollständiger Code:

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)

Die endgültige Version ist fast 10x schneller als der ursprüngliche Code. Die Strategie lautet:

  1. Verwenden groupby diese , um einen wiederholten Vergleich von Werten zu vermeiden.
  2. Verwenden to_records diese Option, um auf rohe numpy.records-Objekte zuzugreifen.
  3. Arbeiten Sie nicht mit DataFrame, bis Sie alle Daten zusammengestellt haben.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.