2015-04-04 32 views
10

Ich suche nach Lösungen, um eine Funktion zu beschleunigen, die ich geschrieben habe, um einen Pandas-Datenrahmen zu durchlaufen und Spaltenwerte zwischen der aktuellen Zeile und der vorherigen Zeile zu vergleichen .Schnellste Möglichkeit zum Vergleichen von Zeilen und vorherigen Zeilen im Pandas-Datenframe mit Millionen von Zeilen

Als Beispiel ist dies eine vereinfachte Version von meinem Problem:

User Time     Col1 newcol1 newcol2 newcol3 newcol4 
0  1  6  [cat, dog, goat]  0  0  0  0 
1  1  6   [cat, sheep]  0  0  0  0 
2  1 12  [sheep, goat]  0  0  0  0 
3  2  3   [cat, lion]  0  0  0  0 
4  2  5 [fish, goat, lemur]  0  0  0  0 
5  3  9   [cat, dog]  0  0  0  0 
6  4  4   [dog, goat]  0  0  0  0 
7  4 11    [cat]  0  0  0  0 

Im Moment habe ich eine Funktion, die durch Schleifen und berechnet Werte für ‚newcol1‘ und ‚newcol2‘ basierend darauf, ob die ' User 'hat sich seit der vorherigen Zeile geändert und auch, ob der Unterschied in den Werten' Time 'größer als 1 ist. Es wird auch der erste Wert in den Arrays unter' Col1 'und' Col2 'und Aktualisierungen' newcol3 'und' newcol4 ', wenn sich diese Werte seit der letzten Zeile geändert haben.

Hier ist der Pseudo-Code für das, was ich zur Zeit mache (da ich das Problem vereinfacht habe ich nicht getestet, aber es ist ziemlich ähnlich zu dem, was ich in ipython Notebook tatsächlich tun):

def myJFunc(df): 
...  #initialize jnum counter 
...  jnum = 0; 
...  #loop through each row of dataframe (not including the first/zeroeth) 
...  for i in range(1,len(df)): 
...    #has user changed? 
...    if df.User.loc[i] == df.User.loc[i-1]: 
...      #has time increased by more than 1 (hour)? 
...      if abs(df.Time.loc[i]-df.Time.loc[i-1])>1: 
...        #update new columns 
...        df['newcol2'].loc[i-1] = 1; 
...        df['newcol1'].loc[i] = 1; 
...        #increase jnum 
...        jnum += 1; 
...      #has content changed? 
...      if df.Col1.loc[i][0] != df.Col1.loc[i-1][0]: 
...        #record this change 
...        df['newcol4'].loc[i-1] = [df.Col1.loc[i-1][0], df.Col2.loc[i][0]]; 
...    #different user? 
...    elif df.User.loc[i] != df.User.loc[i-1]: 
...      #update new columns 
...      df['newcol1'].loc[i] = 1; 
...      df['newcol2'].loc[i-1] = 1; 
...      #store jnum elsewhere (code not included here) and reset jnum 
...      jnum = 1; 

Ich muss jetzt diese Funktion auf mehrere Millionen Zeilen anwenden und es ist unglaublich langsam, so dass ich versuche, herauszufinden, der beste Weg, um es zu beschleunigen. Ich habe gehört, dass Cython die Geschwindigkeit von Funktionen erhöhen kann, aber ich habe keine Erfahrung damit (und ich bin neu sowohl Pandas und Pandas). Ist es möglich, zwei Zeilen eines Datenframes als Argumente an die Funktion zu übergeben und dann Cython zu verwenden, um sie zu beschleunigen, oder müssten neue Spalten mit "diff" -Werten erstellt werden, sodass die Funktion nur liest und in eine schreibt Zeile des Datenrahmens gleichzeitig, um von der Verwendung von Cython zu profitieren? Alle anderen Geschwindigkeitstricks würden sehr geschätzt werden!

(Was .loc verwenden, verglich ich .loc, .iloc und .ix und dieses war geringfügig schneller, so das ist der einzige Grund, warum ich das zur Zeit bin mit)

(Auch meine User Spalte in Realität ist Unicode nicht int, was für schnelle Vergleiche problematisch sein könnte)

+1

Mit einer Million Zeilen, warum nicht eine dedizierte Datenbank verwenden, mit der Python einfach Verbindungen wie MySQL oder SQLlite herstellen kann? Relationale Datenbanken können komplexe SQL-Abfragen mit if/then-Logik für den Zeilen-zu-Zeilen-Vergleich ausführen, die durch Indizes verbunden sind. Sie sind entworfen, um Millionen von Zeilen zu skalieren. Selbst ein Trigger kann so eingerichtet werden, dass bei jeder Benutzeränderung bestimmte Spalten aktualisiert werden können. – Parfait

Antwort

10

Ich dachte in die gleiche Richtung wie Andy, nur mit groupby hinzugefügt, und ich denke, das ist komplementär zu Andys Antwort. Hinzufügen von groupby wird nur den Effekt haben, ein NaN in die erste Zeile zu setzen, wenn Sie eine diff oder shift machen. (Beachten Sie, dass dies nicht der Versuch einer genauen Antwort, nur einige grundlegende Techniken zu skizzieren.)

df['time_diff'] = df.groupby('User')['Time'].diff() 

df['Col1_0'] = df['Col1'].apply(lambda x: x[0]) 

df['Col1_0_prev'] = df.groupby('User')['Col1_0'].shift() 

    User Time     Col1 time_diff Col1_0 Col1_0_prev 
0  1  6  [cat, dog, goat]  NaN cat   NaN 
1  1  6   [cat, sheep]   0 cat   cat 
2  1 12  [sheep, goat]   6 sheep   cat 
3  2  3   [cat, lion]  NaN cat   NaN 
4  2  5 [fish, goat, lemur]   2 fish   cat 
5  3  9   [cat, dog]  NaN cat   NaN 
6  4  4   [dog, goat]  NaN dog   NaN 
7  4 11    [cat]   7 cat   dog 

Als Followup zu Andys Punkt über Objekte zu speichern, beachten Sie, dass, was ich hier tat, war die erste zu extrahieren Element der Listenspalte (und fügen Sie auch eine verschobene Version hinzu). So muss man nur einmal eine teure Extraktion machen und danach kann man sich an Standard-Pandas halten.

+0

Vielen Dank beide (JohnE & @Andy), ich implementierte beide Lösungen, groupby und extrahieren das erste Element von Col1 waren besonders nützlich, jetzt dauert ~ 3 Minuten auf den gesamten Datensatz zu laufen - sehr glücklich! :) – AdO

0

In Ihrem Problem scheint es, als ob Sie paarweise durch die Reihe iterieren wollen. Das erste, was Sie tun können, ist so etwas wie dieses:

from itertools import tee, izip 
def pairwise(iterable): 
    "s -> (s0,s1), (s1,s2), (s2, s3), ..." 
    a, b = tee(iterable) 
    next(b, None) 
    return izip(a, b) 

for (idx1, row1), (idx2, row2) in pairwise(df.iterrows()): 
    # you stuff 

jedoch nicht row1 ändern können und Row2 direkt müssen Sie noch .loc oder .iloc mit den Indizes verwenden.

Wenn iterrows noch zu langsam ist, schlage ich vor, so etwas zu tun:

  • erstellen user_id Spalte von Ihnen Unicode Namen pd.unique (Benutzer) und der Abbildung des Namens mit einem Wörterbuch-IDs in Integer .

  • Erstellen Sie einen Delta-Datenrahmen: zu einem verschobenen Datenrahmen mit der Spalte user_id und time subtrahieren Sie den ursprünglichen Datenrahmen.

    df[[col1, ..]].shift() - df[[col1, ..]]) 
    

Wenn user_id> 0 ist, bedeutet dies, dass der Benutzer in zwei aufeinanderfolgenden Zeilen geändert. Die Zeitspalte kann direkt mit delta [delta ['time'> 1]] gefiltert werden. Mit diesem Delta-Datenrahmen erfassen Sie die Änderungen zeilenweise. Sie können eine Maske verwenden, um die Spalten, die Sie benötigen, von Ihrem ursprünglichen Datenrahmen zu aktualisieren.

8

Verwenden Sie Pandas (Konstrukte) und vektorisieren Sie Ihren Code, d. H. Verwenden Sie keine for-Schleifen, sondern verwenden Sie Pandas/numpy-Funktionen.

‚newcol1‘ und ‚newcol2‘ basierend darauf, ob die ‚User‘ seit der letzten Zeile geändert hat und auch, ob die Differenz in der diese ‚Time‘ Werte größer als 1.

Berechnen getrennt:

df['newcol1'] = df['User'].shift() == df['User'] 
df.ix[0, 'newcol1'] = True # possibly tweak the first row?? 

df['newcol1'] = (df['Time'].shift() - df['Time']).abs() > 1 

Es ist mir unklar, der Zweck der Col1, aber allgemeine python-Objekte in Spalten nicht gut skalieren (Sie nicht schnell Pfad verwenden können, und der Inhalt in Erinnerung verstreut). Die meiste Zeit kann man sonst bei der Verwendung von etwas weg ...


Cython ist die allerletzte Option, und nicht in 99% der Anwendungsfälle erforderlich, aber enhancing performance section of the docs für Spitzen sehen.

Verwandte Themen