Pandas DataFrame-Leistung

74

Pandas ist wirklich großartig, aber ich bin wirklich überrascht, wie ineffizient es ist, Werte von einem Pandas.DataFrame abzurufen. Im folgenden Spielzeugbeispiel ist sogar die DataFrame.iloc-Methode mehr als 100-mal langsamer als ein Wörterbuch.

Die Frage: Ist die Lektion hier nur, dass Wörterbücher der bessere Weg sind, um nach Werten zu suchen? Ja, ich verstehe, dass genau dafür sie gemacht wurden. Aber ich frage mich nur, ob mir etwas an der Leistung der DataFrame-Suche fehlt.

Mir ist klar, dass diese Frage mehr "Nachdenken" als "Fragen" ist, aber ich werde eine Antwort akzeptieren, die Einblicke oder Perspektiven bietet. Vielen Dank.

import timeit

setup = '''
import numpy, pandas
df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
dictionary = df.to_dict()
'''

f = ['value = dictionary[5][5]', 'value = df.loc[5, 5]', 'value = df.iloc[5, 5]']

for func in f:
    print func
    print min(timeit.Timer(func, setup).repeat(3, 100000))

Wert = Wörterbuch [5] [5]

0,130625009537

value = df.loc [5, 5]

19.4681699276

Wert = df.iloc [5, 5]

17.2575249672

Owen
quelle

Antworten:

106

Ein Diktat gilt für einen DataFrame wie ein Fahrrad für ein Auto. Sie können 10 Fuß auf einem Fahrrad schneller treten, als Sie ein Auto starten, einen Gang einlegen usw. usw. Aber wenn Sie eine Meile fahren müssen, gewinnt das Auto.

Für bestimmte kleine, zielgerichtete Zwecke kann ein Diktat schneller sein. Und wenn das alles ist, was Sie brauchen, dann verwenden Sie auf jeden Fall ein Diktat! Wenn Sie jedoch die Leistung und den Luxus eines DataFrame benötigen / möchten, ist ein Diktat kein Ersatz. Es ist sinnlos, die Geschwindigkeit zu vergleichen, wenn die Datenstruktur Ihre Anforderungen nicht zuerst erfüllt.

Um beispielsweise konkreter zu sein, ist ein Diktat gut für den Zugriff auf Spalten, aber nicht so praktisch für den Zugriff auf Zeilen.

import timeit

setup = '''
import numpy, pandas
df = pandas.DataFrame(numpy.zeros(shape=[10, 1000]))
dictionary = df.to_dict()
'''

# f = ['value = dictionary[5][5]', 'value = df.loc[5, 5]', 'value = df.iloc[5, 5]']
f = ['value = [val[5] for col,val in dictionary.items()]', 'value = df.loc[5]', 'value = df.iloc[5]']

for func in f:
    print(func)
    print(min(timeit.Timer(func, setup).repeat(3, 100000)))

ergibt

value = [val[5] for col,val in dictionary.iteritems()]
25.5416321754
value = df.loc[5]
5.68071913719
value = df.iloc[5]
4.56006002426

Das Diktat von Listen ist also beim Abrufen von Zeilen fünfmal langsamer als df.iloc. Das Geschwindigkeitsdefizit wird größer, wenn die Anzahl der Spalten zunimmt. (Die Anzahl der Spalten entspricht der Anzahl der Fuß in der Fahrradanalogie. Je länger die Entfernung, desto bequemer wird das Auto ...)

Dies ist nur ein Beispiel dafür, wann ein Diktat von Listen weniger bequem / langsamer als ein DataFrame wäre.

Ein anderes Beispiel wäre, wenn Sie einen DatetimeIndex für die Zeilen haben und alle Zeilen zwischen bestimmten Daten auswählen möchten. Mit einem DataFrame können Sie verwenden

df.loc['2000-1-1':'2000-3-31']

Es gibt kein einfaches Analogon dafür, wenn Sie ein Listen-Diktat verwenden würden. Und die Python-Schleifen, die Sie zur Auswahl der richtigen Zeilen benötigen würden, wären im Vergleich zum DataFrame wieder sehr langsam.

unutbu
quelle
Antworten wie diese können zu den FAQ hinzugefügt werden, siehe hier: github.com/pydata/pandas/issues/3871
Jeff
3
Vielen Dank für die beiden wirklich aufschlussreichen Beispiele und auch für die Analogie, die ich als Radfahrer sehr schätze.
Owen
3
In Ihrem Beispiel werden df.to_dict()keine Spalten, sondern Diktate verwendet. Wenn dic = {x:df[x].values.tolist() for x in df}Sie verwenden, erhalten Sie (auf meinem Computer) eine 25-fache Verbesserung beim Zugriff auf Spalten und einen 1,5-fach schnelleren Zeilenzugriff. so Wörterbuch ist schneller
tal
20

Es scheint, dass der Leistungsunterschied jetzt viel geringer ist (0.21.1 - Ich habe vergessen, was die Version von Pandas im ursprünglichen Beispiel war). Nicht nur die Leistungslücke zwischen Wörterbuchzugriff und .locreduziert (von etwa 335-mal auf 126-mal langsamer), loc( iloc) ist jetzt weniger als zweimal langsamer als at( iat).

In [1]: import numpy, pandas
   ...:    ...: df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
   ...:    ...: dictionary = df.to_dict()
   ...: 

In [2]: %timeit value = dictionary[5][5]
85.5 ns ± 0.336 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: %timeit value = df.loc[5, 5]
10.8 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %timeit value = df.at[5, 5]
6.87 µs ± 64.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [5]: %timeit value = df.iloc[5, 5]
14.9 µs ± 114 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [6]: %timeit value = df.iat[5, 5]
9.89 µs ± 54.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: print(pandas.__version__)
0.21.1

---- Originalantwort unten ----

+1 für die Verwendung atoder iatfür skalare Operationen. Beispiel-Benchmark:

In [1]: import numpy, pandas
   ...: df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
   ...: dictionary = df.to_dict()

In [2]: %timeit value = dictionary[5][5]
The slowest run took 34.06 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 310 ns per loop

In [4]: %timeit value = df.loc[5, 5]
10000 loops, best of 3: 104 µs per loop

In [5]: %timeit value = df.at[5, 5]
The slowest run took 6.59 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 9.26 µs per loop

In [6]: %timeit value = df.iloc[5, 5]
10000 loops, best of 3: 98.8 µs per loop

In [7]: %timeit value = df.iat[5, 5]
The slowest run took 6.67 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 9.58 µs per loop

Es scheint, dass using at( iat) ungefähr zehnmal schneller ist als loc( iloc).

joon
quelle
6

Ich bin auf das gleiche Problem gestoßen. Sie können verwenden at, um zu verbessern.

"Da die Indizierung mit [] viele Fälle behandeln muss (Single-Label-Zugriff, Slicing, Boolesche Indizierung usw.), ist der Aufwand etwas höher, um herauszufinden, wonach Sie fragen. Wenn Sie nur möchten Der schnellste Weg, auf einen Skalarwert zuzugreifen, besteht darin, die Methoden atund zu iatverwenden, die in allen Datenstrukturen implementiert sind. "

siehe offizielle Referenz http://pandas.pydata.org/pandas-docs/stable/indexing.html Kapitel "Schnelles Abrufen und Einstellen von Skalarwerten"

user3566825
quelle
Dies ist eine gute Referenz, aber nicht so detailliert wie die obige Antwort.
BCR
1

Ich denke, der schnellste Weg, auf eine Zelle zuzugreifen, ist

df.get_value(row,column)
df.set_value(row,column,value) 

Beide sind schneller als (glaube ich)

df.iat(...) 
df.at(...)
Orvar Korvar
quelle
1
Es scheint atschneller zu sein - 4,68 µs ( at) gegenüber 5,98 µs ( get_values). Ist auch atviel flexibler, da Sie benannte Indizes verwenden können.
Joon
0

Beim Zugriff auf die Datenrahmenzeile ist ein anderes Phänomen aufgetreten. Testen Sie dieses einfache Beispiel auf einem Datenrahmen mit etwa 10.000.000 Zeilen. Wörterbuch rockt.

def testRow(go):
    go_dict = go.to_dict()
    times = 100000
    ot= time.time()
    for i in range(times):
        go.iloc[100,:]
    nt = time.time()
    print('for iloc {}'.format(nt-ot))
    ot= time.time()
    for i in range(times):
        go.loc[100,2]
    nt = time.time()
    print('for loc {}'.format(nt-ot))
    ot= time.time()
    for i in range(times):
        [val[100] for col,val in go_dict.iteritems()]
    nt = time.time()
    print('for dict {}'.format(nt-ot))
Amityaffliction
quelle