Gibt es in Pandas eine Möglichkeit, den vorherigen Zeilenwert in dataframe.apply zu verwenden, wenn der vorherige Wert auch in apply angewendet wird?

93

Ich habe folgenden Datenrahmen:

 Index_Date    A    B    C    D
 ===============================
 2015-01-31    10   10   Nan  10
 2015-02-01     2    3   Nan  22 
 2015-02-02    10   60   Nan  280
 2015-02-03    10   100   Nan  250

Benötigen:

 Index_Date    A    B    C    D
 ===============================
 2015-01-31    10   10   10   10
 2015-02-01     2    3   23   22
 2015-02-02    10   60   290  280
 2015-02-03    10   100  3000 250

Column Cwird abgeleitet 2015-01-31durch Nehmen valuevon D.

Dann muss ich das valuevon Cfür verwenden 2015-01-31und mit dem valuevon Aon multiplizieren 2015-02-01und hinzufügen B.

Ich habe versucht, eine applyund eine shiftVerwendung if elsevon dies gibt einen Schlüsselfehler.

Strg-Alt-Löschen
quelle
Warum unterscheiden sich Ihre letzten Zeilen in den Datenrahmen für Spalten Aund B?
Anton Protopopov
@ Antonio entschuldigt sich jetzt richtig.
Strg-Alt-Löschen
Was ist der Wert der nächsten Zeile in Spalte Aund Spalte D?
Jezrael
7
Das ist eine gute Frage. Ich habe einen ähnlichen Bedarf an einer vektorisierten Lösung. Es wäre schön, wenn Pandas eine Version bereitstellen würde, apply()in der die Benutzerfunktion im Rahmen ihrer Berechnung auf einen oder mehrere Werte aus der vorherigen Zeile zugreifen oder zumindest einen Wert zurückgeben kann, der dann bei der nächsten Iteration an sich selbst übergeben wird. Würde dies nicht einige Effizienzgewinne im Vergleich zu einer for-Schleife ermöglichen?
Bill
@ Bill, Sie könnten an dieser Antwort interessiert sein , die ich gerade hinzugefügt habe, numbaist hier oft eine gute Option.
Jpp

Antworten:

64

Erstellen Sie zunächst den abgeleiteten Wert:

df.loc[0, 'C'] = df.loc[0, 'D']

Durchlaufen Sie dann die verbleibenden Zeilen und füllen Sie die berechneten Werte aus:

for i in range(1, len(df)):
    df.loc[i, 'C'] = df.loc[i-1, 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']


  Index_Date   A   B    C    D
0 2015-01-31  10  10   10   10
1 2015-02-01   2   3   23   22
2 2015-02-02  10  60  290  280
Stefan
quelle
41
Gibt es in Pandas eine Funktion, um dies ohne die Schleife zu tun?
Strg-Alt-Löschen
1
Die iterative Natur der Berechnung, bei der die Eingaben von den Ergebnissen vorheriger Schritte abhängen, erschwert die Vektorisierung. Sie könnten vielleicht applyeine Funktion verwenden, die die gleiche Berechnung wie die Schleife ausführt, aber hinter den Kulissen wäre dies auch eine Schleife. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Stefan
Wenn ich diese Schleife verwende und auf einem zusammengeführten Datenrahmen berechne und eine Nan findet, funktioniert dies jedoch nur für die Zeile mit Nan. Es werden keine Fehler ausgegeben. Wenn ich eine fillNa versuche, erhalte ich AttributeError: 'numpy.float64' Objekt hat kein Attribut 'fillna' Gibt es eine Möglichkeit, die Zeile mit Nan zu überspringen oder Werte auf Null zu setzen?
Strg-Alt-Löschen
Meinen Sie fehlende Werte in anderen Spalten als C?
Stefan
Ja, Ihre Lösung ist in Ordnung. Ich stelle nur sicher, dass ich die Nans im Datenrahmen vor der Schleife fülle.
Strg-Alt-Löschen
41

Gegeben eine Spalte von Zahlen:

lst = []
cols = ['A']
for a in range(100, 105):
    lst.append([a])
df = pd.DataFrame(lst, columns=cols, index=range(5))
df

    A
0   100
1   101
2   102
3   103
4   104

Sie können die vorherige Zeile mit Shift referenzieren:

df['Change'] = df.A - df.A.shift(1)
df

    A   Change
0   100 NaN
1   101 1.0
2   102 1.0
3   103 1.0
4   104 1.0
kztd
quelle
9
Dies hilft in dieser Situation nicht, da der Wert aus der vorherigen Zeile am Anfang nicht bekannt ist. Es muss bei jeder Iteration berechnet und dann bei der nächsten Iteration verwendet werden.
Bill
6
Ich bin immer noch dankbar für diese Antwort, weil ich darüber gestolpert bin und nach einem Fall gesucht habe, in dem ich den Wert aus der vorherigen Zeile kenne. Also danke @kztd
Kevin Pauli
28

numba

Bei rekursiven Berechnungen, die nicht vektorisierbar sind numba, die JIT-Kompilierung verwenden und mit Objekten niedrigerer Ebene arbeiten, ergeben sich häufig große Leistungsverbesserungen. Sie müssen nur eine reguläre forSchleife definieren und den Dekorator verwenden @njitoder (für ältere Versionen) @jit(nopython=True):

Bei einem Datenrahmen mit angemessener Größe ergibt sich eine ~ 30-fache Leistungsverbesserung gegenüber einer regulären forSchleife:

from numba import jit

@jit(nopython=True)
def calculator_nb(a, b, d):
    res = np.empty(d.shape)
    res[0] = d[0]
    for i in range(1, res.shape[0]):
        res[i] = res[i-1] * a[i] + b[i]
    return res

df['C'] = calculator_nb(*df[list('ABD')].values.T)

n = 10**5
df = pd.concat([df]*n, ignore_index=True)

# benchmarking on Python 3.6.0, Pandas 0.19.2, NumPy 1.11.3, Numba 0.30.1
# calculator() is same as calculator_nb() but without @jit decorator
%timeit calculator_nb(*df[list('ABD')].values.T)  # 14.1 ms per loop
%timeit calculator(*df[list('ABD')].values.T)     # 444 ms per loop
jpp
quelle
1
Es ist wunderbar! Ich habe meine Funktion beschleunigt, die Werte von vorherigen Werten zählt. Vielen Dank!
Artem Malikov
20

Das Anwenden der rekursiven Funktion auf Numpy-Arrays ist schneller als die aktuelle Antwort.

df = pd.DataFrame(np.repeat(np.arange(2, 6),3).reshape(4,3), columns=['A', 'B', 'D'])
new = [df.D.values[0]]
for i in range(1, len(df.index)):
    new.append(new[i-1]*df.A.values[i]+df.B.values[i])
df['C'] = new

Ausgabe

      A  B  D    C
   0  1  1  1    1
   1  2  2  2    4
   2  3  3  3   15
   3  4  4  4   64
   4  5  5  5  325

quelle
3
Diese Antwort funktioniert bei mir mit einer ähnlichen Berechnung perfekt. Ich habe versucht, eine Kombination aus Cumsum und Shift zu verwenden, aber diese Lösung funktioniert viel besser. Vielen Dank.
Simon
Das funktioniert auch perfekt für mich, danke. Ich hatte mit vielen Formen von Iterrows, Itertuples, Apply usw. zu kämpfen, und dies scheint leicht zu verstehen und leistungsfähig zu sein.
Chaim
9

Obwohl es eine Weile her ist, dass diese Frage gestellt wurde, werde ich meine Antwort veröffentlichen, in der Hoffnung, dass sie jemandem hilft.

Haftungsausschluss: Ich weiß, dass diese Lösung kein Standard ist , aber ich denke, dass sie gut funktioniert.

import pandas as pd
import numpy as np

data = np.array([[10, 2, 10, 10],
                 [10, 3, 60, 100],
                 [np.nan] * 4,
                 [10, 22, 280, 250]]).T
idx = pd.date_range('20150131', end='20150203')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df
               A    B     C    D
 =================================
 2015-01-31    10   10    NaN  10
 2015-02-01    2    3     NaN  22 
 2015-02-02    10   60    NaN  280
 2015-02-03    10   100   NaN  250

def calculate(mul, add):
    global value
    value = value * mul + add
    return value

value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
df
               A    B     C     D
 =================================
 2015-01-31    10   10    10    10
 2015-02-01    2    3     23    22 
 2015-02-02    10   60    290   280
 2015-02-03    10   100   3000  250

Im Grunde genommen verwenden wir ein applyFrom Pandas und die Hilfe einer globalen Variablen, die den zuvor berechneten Wert verfolgt.


Zeitvergleich mit einer forSchleife:

data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan

df.loc['2015-01-31', 'C'] = df.loc['2015-01-31', 'D']

%%timeit
for i in df.loc['2015-02-01':].index.date:
    df.loc[i, 'C'] = df.loc[(i - pd.DateOffset(days=1)).date(), 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']

3,2 s ± 114 ms pro Schleife (Mittelwert ± Standardabweichung von 7 Läufen, jeweils 1 Schleife)

data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan

def calculate(mul, add):
    global value
    value = value * mul + add
    return value

value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value

%%timeit
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)

1,82 s ± 64,4 ms pro Schleife (Mittelwert ± Standardabweichung von 7 Läufen, jeweils 1 Schleife)

Also durchschnittlich 0,57 mal schneller.

iipr
quelle
0

Im Allgemeinen besteht der Schlüssel zum Vermeiden einer expliziten Schleife darin, zwei Instanzen des Datenrahmens auf rowindex-1 == rowindex zu verbinden (zusammenzuführen).

Dann hätten Sie einen großen Datenrahmen mit Zeilen von r und r-1, von wo aus Sie eine df.apply () -Funktion ausführen könnten.

Der Aufwand für die Erstellung des großen Datensatzes kann jedoch die Vorteile der Parallelverarbeitung ausgleichen ...

HTH Martin

Martin Alley
quelle