281 Stimmen

Pandas GroupBy-Spalten mit NaN (fehlenden) Werten

Ich habe ein DataFrame mit vielen fehlenden Werten in Spalten, die ich gruppieren möchte:

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

Sehen Sie, dass Pandas die Zeilen mit NaN-Zielwerten fallen gelassen hat. (Ich möchte diese Zeilen einschließen!)

Da ich viele solcher Operationen benötige (viele Spalten haben fehlende Werte) und kompliziertere Funktionen als nur Mediane verwende (typischerweise Random Forests), möchte ich vermeiden, zu komplizierte Code-Stücke zu schreiben.

Irgendwelche Vorschläge? Sollte ich eine Funktion dafür schreiben oder gibt es eine einfache Lösung?

338voto

cs95 Punkte 325143

Pandas >= 1.1

Von pandas 1.1 an haben Sie eine bessere Kontrolle über dieses Verhalten, NA-Werte sind jetzt im Grouper erlaubt mit dropna=False:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

# Beispiel aus der Dokumentation
df

   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2

# ohne NA (Standard)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5

# mit NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4

177voto

Andy Hayden Punkte 324102

Dies wird im Abschnitt zu fehlenden Daten der Dokumentation erwähnt:

NA-Gruppen in GroupBy werden automatisch ausgeschlossen. Dieses Verhalten ist konsistent mit R

Ein Workaround besteht darin, einen Platzhalter zu verwenden, bevor das GroupBy durchgeführt wird (z. B. -1):

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

Das fühlt sich zwar wie ein ziemlich schrecklicher Hack an... vielleicht sollte es eine Option geben, NaN in GroupBy einzuschließen (siehe diese Github-Issue - die denselben Platzhalter-Hack verwendet).

Wie jedoch in einer anderen Antwort beschrieben: "Ab pandas 1.1 haben Sie eine bessere Kontrolle über dieses Verhalten. NA-Werte sind jetzt im Grouper mit dropna=False erlaubt".

52voto

M. Kiewisch Punkte 553

Ein altes Thema, wenn jemand immer noch darüber stolpert - eine weitere Lösung besteht darin, über .astype(str) in einen String zu konvertieren, bevor Sie gruppieren. Dadurch werden die NaN's erhalten bleiben.

df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()

    a
b   
4   1
6   3
nan 2

13voto

Ich kann keinen Kommentar zu M. Kiewisch hinzufügen, da ich nicht genügend Rufpunkte habe (habe nur 41, aber brauche mehr als 50, um kommentieren zu können).

Wie dem auch sei, ich möchte darauf hinweisen, dass M. Kiewischs Lösung so nicht funktioniert und möglicherweise noch einige Anpassungen benötigt. Betrachten Sie zum Beispiel

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

was zeigt, dass für die Gruppe b=4.0 der entsprechende Wert 15 anstelle von 6 ist. Hier werden einfach 1 und 5 als Zeichenfolgen verkettet anstatt sie als Zahlen hinzuzufügen.

8voto

Grant Langseth Punkte 1537

Alle bisherigen Antworten führen möglicherweise zu gefährlichem Verhalten, da es durchaus möglich ist, dass Sie einen Dummy-Wert auswählen, der tatsächlich Teil des Datensatzes ist. Dies wird immer wahrscheinlicher, wenn Sie Gruppen mit vielen Attributen erstellen. Einfach ausgedrückt, der Ansatz verallgemeinert nicht immer gut.

Eine weniger hacky Lösung besteht darin, pd.drop_duplicates() zu verwenden, um einen eindeutigen Index von Wertekombinationen zu erstellen, von denen jeder eine eigene ID hat, und dann nach dieser ID zu gruppieren. Es ist ausführlicher, erledigt aber die Arbeit:

def safe_groupby(df, group_cols, agg_dict):
    # Namen der Gruppenspalte auf eindeutigen Wert setzen
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # Endgültige Reihenfolge der Spalten erhalten
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # Eindeutigen Index von gruppierten Werten erstellen
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # Eindeutigen Index mit dem DataFrame zusammenführen
    df = df.merge(group_idx, on=group_cols)
    # DataFrame nach Gruppen-ID gruppieren und Werte aggregieren
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # Gruppierten Wertindex mit den Ergebnissen der Aggregation zusammenführen
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # Index umbenennen
    df_agg.index.name = None
    # Neu sortierte Spalten zurückgeben
    return df_agg[agg_col_order]

Beachten Sie, dass Sie jetzt einfach Folgendes tun können:

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

Dies gibt das erfolgreiche Ergebnis zurück, ohne sich Sorgen machen zu müssen, dass echte Daten überschrieben werden, die fälschlicherweise als Dummy-Wert angesehen werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X