2016-11-05 1 views
1

Ich möchte N/A Werte in einem DataFrame in einer selektiven Weise füllen. Insbesondere wenn es eine Folge von konsekutiven Nans innerhalb einer Spalte gibt, möchte ich, dass sie mit dem vorhergehenden Nicht-Nan-Wert gefüllt werden, aber nur dann, wenn die Länge der Nan-Sequenz unter einem spezifizierten Schwellenwert liegt. Wenn der Schwellenwert beispielsweise 3 ist, wird eine Sequenz innerhalb der Spalte von 3 oder weniger mit dem vorhergehenden Nicht-Nan-Wert gefüllt, während eine Sequenz von 4 oder mehr Nans unverändert bleibt.Verwendung von fillna() selektiv in Pandas

Das heißt, wenn der Eingangsdatenrahmen

2 5 4 
    nan nan nan 
    nan nan nan 
    5 nan nan 
    9 3 nan 
    7 9 1 

I ist der Ausgang sein will:

2 5 4 
    2 5 nan 
    2 5 nan 
    5 5 nan 
    9 3 nan 
    7 9 1 

Die fillna Funktion, wenn sie an einen Datenrahmen angewandt wird, hat die Methode und Limit-Optionen. Aber diese reichen leider nicht aus, um die Aufgabe zu erfüllen. Ich habe versucht, method='ffill' und limit=3 anzugeben, aber das füllt die ersten 3 Nans einer Sequenz, nicht selektiv wie oben beschrieben.

Ich nehme an, dies, indem Sie Spalte für Spalte mit einigen bedingten Anweisungen codiert werden kann, aber ich vermute, es muss etwas mehr Pythonic sein. Irgendwelche Vorschläge auf eine effiziente Art, dies zu erreichen?

Antwort

3

Arbeiten mit zusammenhängenden Gruppen ist immer noch ein wenig peinlich in Pandas .. oder zumindest weiß ich nicht von einem glatten Weg, dies zu tun, was nicht das Gleiche ist. :-)

Eine Möglichkeit zu bekommen, was Sie wollen, wäre die Vergleichs- cumsum-groupby Muster zu verwenden:

In [68]: nulls = df.isnull() 
    ...: groups = (nulls != nulls.shift()).cumsum() 
    ...: to_fill = groups.apply(lambda x: x.groupby(x).transform(len) <= 3) 
    ...: df.where(~to_fill, df.ffill()) 
    ...: 
Out[68]: 
    0 1 2 
0 2.0 5.0 4.0 
1 2.0 5.0 NaN 
2 2.0 5.0 NaN 
3 5.0 5.0 NaN 
4 9.0 3.0 NaN 
5 7.0 9.0 1.0 

Okay, eine andere Alternative, die Ich mag es nicht, weil es zu schwierig ist, :

def method_2(df): 
    nulls = df.isnull() 
    filled = df.ffill(limit=3) 
    unfilled = nulls & (~filled.notnull()) 
    nf = nulls.replace({False: 2.0, True: np.nan}) 
    do_not_fill = nf.combine_first(unfilled.replace(False, np.nan)).bfill() == 1 
    return df.where(do_not_fill, df.ffill()) 

Diese verwendet keine groupby Tools und sollte schneller sein, so. Beachten Sie, dass ein anderer Ansatz wäre, manuell festzulegen, welche Elemente gefüllt werden sollen, weil sie eine Gruppe der Länge 1, 2 oder 3 sind.

+0

Vielen Dank @DSM. Das ist eine sehr schöne Lösung, die uns tatsächlich das gibt, wonach wir suchen. Nur ein Kommentar: Es ist ziemlich langsam. Ich habe es mit einem Limit von 3 für einen DataFrame der Größe 530x11500 verwendet und es dauerte ungefähr 32 Sekunden. So, während diese Lösung groß ist, würde eine alternative Lösung, die die Laufzeit verringert, sehr geschätzt werden – splinter

+0

Viel schnell tatsächlich! Wandzeit: 9.01 s – splinter

Verwandte Themen