2017-09-26 4 views
3

Angenommen, ich habe einen Datenrahmen (oder Serie) wie folgt aus:Pandas gelten, aber der Zugang zuvor berechneten Wert

 Value 
0 0.5 
1 0.8 
2 -0.2 
3 None 
4 None 
5 None 

Ich möchte ein neues Ergebnis Spalte erstellen.

Der Wert jedes Ergebnisses wird durch den Wert vorheriger Wert über eine beliebige Funktion f bestimmt.

Wenn der vorherige Wert nicht verfügbar ist (Keine oder NaN), möchte ich stattdessen das vorherige Ergebnis verwenden (und natürlich f darauf anwenden).


Mit dem vorherigen Wert ist einfach, ich brauche nur shift zu verwenden. Der Zugriff auf das vorherige Ergebnis scheint jedoch nicht so einfach zu sein.

Der folgende Code berechnet beispielsweise das Ergebnis, kann jedoch bei Bedarf nicht auf das vorherige Ergebnis zugreifen.

df['Result'] = df['Value'].shift(1).apply(f) 

Gehen Sie davon aus, dass f willkürlich ist, und somit Lösungen Dinge wie cumsum sind nicht möglich verwenden.

Offensichtlich kann dies durch Iteration erfolgen, aber ich möchte wissen, ob eine Panda-y-Lösung existiert.

df['Result'] = None 
for i in range(1, len(df)): 
    value = df.iloc[i-1, 'Value'] 
    if math.isnan(value) or value is None: 
    value = df.iloc[i-1, 'Result'] 
    df.iloc[i, 'Result'] = f(value) 

Beispiel Ausgang gegeben f = lambda x: x+1:

Bad:

Value Result 
0 0.5  NaN 
1 0.8  1.5 
2 -0.2  1.8 
3 NaN  0.8 
4 NaN  NaN 
5 NaN  NaN 

Gut:

Value Result 
0 0.5  NaN 
1 0.8  1.5 
2 -0.2  1.8 
3 NaN  0.8 
4 NaN  1.8 <-- previous Value not available, used f(previous result) 
5 NaN  2.8 <-- same 
+1

Wie wäre es 'df [ 'Ergebnis'] = df [ 'Value']. shift (1) .apply (f) .fill() '? – IanS

+1

@IanS Die mit 'ffill' aufgefüllten Werte werden nicht mit' f' versehen. – gberger

+0

Editierte Stelle, um das obige zu verdeutlichen – gberger

Antwort

3

Sieht aus wie es eine Schleife zu mir sein muss. Und ich verabscheue Schleifen ... also wenn ich Schleife, verwende ich numba

Numba gibt Ihnen die Möglichkeit, Ihre Anwendungen mit Hochleistungs-Funktionen direkt in Python geschrieben zu beschleunigen. Mit ein paar Anmerkungen kann array-orientierter und math-lastiger Python-Code Just-in-Time zu nativen Maschinenanweisungen kompiliert werden, ähnlich in der Leistung von C, C++ und Fortran, ohne Sprachen oder Python-Interpreter wechseln zu müssen.

https://numba.pydata.org/

from numba import njit 


@njit 
def f(x): 
    return x + 1 

@njit 
def g(a): 
    r = [np.nan] 
    for v in a[:-1]: 
     if np.isnan(v): 
      r.append(f(r[-1])) 
     else: 
      r.append(f(v)) 
    return r 

df.assign(Result=g(df.Value.values)) 

    Value Result 
0 0.5  NaN 
1 0.8  1.5 
2 -0.2  1.8 
3 NaN  0.8 
4 NaN  1.8 
5 NaN  2.8 
-1

Dies könnte in Pandas passen Stil Codierung. Allerdings denke ich, dass weitere Tests erforderlich sind. Dies gilt nicht für allgemeine Funktionen. Diese hat die Plus 1-Funktion irgendwie ausgetrickst.

import pandas as pd 
import numpy as np 


df = pd.DataFrame({'Value':[0.5,0.8,-0.2,None,None,None]}) 
index = df['Value'].index[df['Value'].apply(np.isnan)] 
window = max(index)-min(index)+1 
df['next'] =df['Value'].shift(1) 


def getX(x): 
    last = np.where(~np.isnan(x))[0][-1] 
    return (x[last])+len(x)-last 



df['plus_one'] = df['next'].rolling(window=3,min_periods=1).apply(lambda x: getX(x)) 
+0

Dies funktioniert nicht, weil 'f' eine beliebige Funktion ist, zB' math.sin' oder irgendetwas anderes. – gberger

0

Ich denke, das könnte funktionieren, aber ich bin mir nicht sicher. Es speichert den zuvor berechneten Wert in einem Abschluss.

def use_previous_if_none(f): 
    prev = None 
    def wrapped(val): 
     nonlocal prev 
     if math.isnan(val) or val is None: 
       val = prev 
     res = f(val) 
     prev = res 
     return res 
    return wrapped 

df['Result'] = df.Value.shift(1).apply(use_previous_if_none(f)) 
+0

@piRSquared Gedanken? – gberger

0

Ich schlage eine Lösung ohne explizite Schleifen vor. Anstatt den vorherigen Wert zu referenzieren, ffil()'s den NaNs und wenden Sie dann f so oft wie erforderlich nur auf die Werte an, die bei NaNs lagen.

Wir beginnen mit der Hilfe-Funktion definiert, die fn Zeiten nennen:

def apply_f_n_times(arg): 
    x = arg[0] 
    n = int(arg[1]) 
    for i in range(n): 
     x = f(x) 
    return x 

df = pd.DataFrame({'value': [1, 2, 3, 5, None, None, 12, 9, None, 6, 1, None, None, None]}) 
df['Result'] = df['Value'].shift(1).apply(f) 
# the following 2 lines will create counter of consecutive NaNs 
tmp = df['Result'].isnull() 
df['Apply_times'] = tmp * (tmp.groupby((tmp != tmp.shift()).cumsum()).cumcount() + 1) 
# fill NaNs with previous good value 
df['Result'] = df['Result'].ffill() 
# apply N times 
df['Result'] = df[['Result', 'Apply_times']].apply(apply_f_n_times, axis=1) 

Das Ergebnis:

Out[2]: 
     Value Result Apply_times 
0  1.0  nan   1 
1  2.0  2.0   0 
2  3.0  3.0   0 
3  5.0  4.0   0 
4  nan  6.0   0 
5  nan  7.0   1 
6 12.0  8.0   2 
7  9.0 13.0   0 
8  nan 10.0   0 
9  6.0 11.0   1 
10 1.0  7.0   0 
11 nan  2.0   0 
12 nan  3.0   1 
13 nan  4.0   2 
Verwandte Themen