Pandas GroupBy-Spalten mit NaN-Werten (fehlende Werte)

147

Ich habe einen DataFrame mit vielen fehlenden Werten in Spalten, nach denen 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 gelöscht 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 (normalerweise zufällige Gesamtstrukturen) verwende, möchte ich vermeiden, zu komplizierte Codeteile zu schreiben.

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

Gyula Sámuel Karli
quelle
1
@PhillipCloud Ich habe diese Frage so bearbeitet, dass sie nur die Frage enthält, die eigentlich recht gut ist und sich auf die Verbesserung der offenen Pandas von Jeff bezieht .
Andy Hayden
1
Es ist ziemlich erschwerend, NaNs nicht in Gruppen aufnehmen (und vermehren) zu können. Das Zitieren von R ist nicht überzeugend, da dieses Verhalten nicht mit vielen anderen Dingen übereinstimmt. Wie auch immer, der Dummy-Hack ist auch ziemlich schlecht. Die Größe (einschließlich NaNs) und die Anzahl (ignoriert NaNs) einer Gruppe unterscheiden sich jedoch, wenn NaNs vorhanden sind. dfgrouped = df.groupby (['b']). a.agg (['sum', 'size', 'count']) dfgrouped ['sum'] [dfgrouped ['size']! = dfgrouped ['count ']] = Keine
Brian Preslopsky
Können Sie zusammenfassen, was Sie konkret erreichen wollen? dh wir sehen eine Ausgabe, aber was ist die "gewünschte" Ausgabe?
Ca
1
Mit 1.1 Pandas werden Sie bald in der Lage sein , zu spezifizieren dropna=Falsein der groupby()gewünschte Ergebnis zu erhalten. Weitere Informationen
cs95

Antworten:

130

Dies wird im Abschnitt Fehlende Daten der Dokumente erwähnt :

NA-Gruppen in GroupBy werden automatisch ausgeschlossen. Dieses Verhalten stimmt beispielsweise mit R überein.

Eine Problemumgehung besteht darin, vor dem Ausführen der Gruppe einen Platzhalter zu verwenden (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 heißt, das fühlt sich ziemlich schrecklich an ... vielleicht sollte es eine Option geben, NaN in groupby aufzunehmen (siehe dieses Github-Problem - das denselben Platzhalter-Hack verwendet).

Andy Hayden
quelle
4
Dies ist eine logische, aber lustige Lösung, an die ich früher gedacht habe. Pandas macht NaN-Felder aus den leeren, und wir müssen sie zurück ändern. Aus diesem Grund denke ich darüber nach, nach anderen Lösungen zu suchen, z. B. nach einem SQL Server und dem Abfragen der Tabellen von dort (sieht etwas zu kompliziert aus) oder nach einer anderen Bibliothek trotz Pandas zu suchen oder meine eigene zu verwenden (die ich möchte) loswerden). Thx
Gyula Sámuel Karli
@ GyulaSámuelKarli Für mich scheint dies ein kleiner Fehler zu sein (siehe Fehlerbericht oben), und meine Lösung ist eine Problemumgehung. Ich finde es seltsam, dass Sie die gesamte Bibliothek abschreiben.
Andy Hayden
1
Ich möchte Pandas nicht aufschreiben, sondern nur nach dem Tool suchen, das meinen Anforderungen am besten entspricht.
Gyula Sámuel Karli
1
Schauen Sie sich meine Antwort unten an. Ich glaube, ich habe eine ziemlich gute (sauberere und wahrscheinlich schnellere) Lösung gefunden. stackoverflow.com/a/43375020/408853
ca.
4
Nein, dies stimmt nicht mit R überein. Df%>% group_by gibt auch NA-Zusammenfassungen mit einer Warnung aus, die vermieden werden kann, indem die Gruppierungsspalte durch fct_explicit_na geleitet wird und anschließend eine (fehlende) Ebene erstellt wird.
Verwüstende Pflege
40

Altes Thema, wenn jemand immer noch darüber stolpert - eine andere Problemumgehung besteht darin, vor dem Gruppieren über .astype (str) in string zu konvertieren. Das schont die NaNs.

in:
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
out:
    a
b   
4   1
6   3
nan 2
M. Kiewisch
quelle
@ K3 --- rnc: Siehe den Kommentar zu deinem Link - der Autor des Beitrags in deinem Link hat etwas falsch gemacht.
Thomas
@ Thomas, ja, genau wie im obigen Beispiel. Bitte bearbeiten Sie, ob Sie das Beispiel sicher (und trivial) machen können.
K3 --- rnc
Das sumvon aist hier eine Zeichenfolgenverkettung, keine numerische Summe. Dies "funktioniert" nur, weil 'b' aus unterschiedlichen Einträgen bestand. Sie müssen 'a' numerisch und 'b' string sein
BallpointBen
27

Pandas> = 1.1

Ab Pandas 1.1 haben Sie eine bessere Kontrolle über dieses Verhalten. NA-Werte sind jetzt im Grouper zulässig, indem Sie dropna=False:

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

# Example from the docs
df

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

# without NA (the default)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5
# with NA
df.groupby('b', dropna=False).sum()

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

Sie können die Vorabversion von Version 1.1 mit dem folgenden Befehl installieren:

pip install https://github.com/pandas-dev/pandas/releases/download/v1.1.0rc0/pandas-1.1.0rc0.tar.gz
cs95
quelle
4
Hoffentlich macht diese Antwort einen allmählichen Marsch nach oben. Es ist der richtige Ansatz.
kdbanman
Ich glaube nicht, dass 1.1 schon veröffentlicht wurde. Geprüft auf conda und pip und die Versionen dort sind noch 1.0.4
sammywemmy
1
@sammywemmy Ja, dies kann derzeit nur in einer Entwicklungsumgebung ausgeführt werden . Ich möchte einen Vorsprung haben, wenn es darum geht, alte SO-Posts mit neuen Funktionen zu versehen. ;-)
cs95
9

Ich kann M. Kiewisch keinen Kommentar hinzufügen, da ich nicht genügend Reputationspunkte habe (nur 41, aber mehr als 50 zum Kommentieren).

Ich möchte nur darauf hinweisen, dass die M. Kiewisch-Lösung nicht wie sie ist funktioniert und möglicherweise weitere 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

Dies zeigt, dass für Gruppe b = 4.0 der entsprechende Wert 15 statt 6 ist. Hier werden nur 1 und 5 als Zeichenfolgen verkettet, anstatt sie als Zahlen hinzuzufügen.

Kamaraju Kusumanchi
quelle
12
Das liegt daran, dass Sie den gesamten DF in str konvertiert haben, anstatt nur die bSpalte
Korem
Beachten Sie, dass dies in der genannten Antwort jetzt behoben wurde.
Shaido - Monica
1
Die neue Lösung ist meiner Meinung nach besser, aber immer noch nicht sicher. Stellen Sie sich einen Fall vor, in dem einer der Einträge in Spalte 'b' mit dem String "np.NaN" identisch ist. Dann werden diese Dinge zusammengeschlagen. df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ;; df ['b'] = df ['b']. Astyp (str); df.groupby (['b']). sum ()
Kamaraju Kusumanchi
6

Ein kleiner Punkt zu Andy Haydens Lösung - es funktioniert nicht mehr (mehr?), Weil es np.nan == np.nanergibt False, also diereplace Funktion eigentlich nichts.

Was für mich funktioniert hat, war Folgendes:

df['b'] = df['b'].apply(lambda x: x if not np.isnan(x) else -1)

(Zumindest ist das das Verhalten von Pandas 0.19.2. Es tut mir leid, es als andere Antwort hinzuzufügen, ich habe nicht genug Ruf, um einen Kommentar abzugeben.)

Tuetschek
quelle
12
Es gibt auch df['b'].fillna(-1).
K3 --- rnc
6

Alle bisher gegebenen Antworten führen zu potenziell gefährlichem Verhalten, da Sie möglicherweise 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 lässt sich nicht immer gut verallgemeinern.

Eine weniger hackige Lösung besteht darin, mit pd.drop_duplicates () einen eindeutigen Index von Wertekombinationen mit jeweils eigener ID zu erstellen und diese dann zu gruppieren. Es ist ausführlicher, erledigt aber die Arbeit:

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    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')]))

Dadurch wird das erfolgreiche Ergebnis zurückgegeben, ohne dass Sie sich Gedanken über das Überschreiben realer Daten machen müssen, die als Dummy-Wert verwechselt werden.

Grant Langseth
quelle
Dies ist die beste Lösung für den allgemeinen Fall, aber in Fällen, in denen ich eine ungültige Zeichenfolge / Nummer kenne, die ich stattdessen verwenden kann, werde ich wahrscheinlich Andy Haydens Antwort unten folgen ... Ich hoffe, Pandas behebt dieses Verhalten bald.
Sarah Messer
4

Ich habe dies bereits beantwortet, aber aus irgendeinem Grund wurde die Antwort in einen Kommentar umgewandelt. Dies ist jedoch die effizienteste Lösung:

Es ist ziemlich erschwerend, NaNs nicht in Gruppen aufnehmen (und vermehren) zu können. Das Zitieren von R ist nicht überzeugend, da dieses Verhalten nicht mit vielen anderen Dingen übereinstimmt. Wie auch immer, der Dummy-Hack ist auch ziemlich schlecht. Die Größe (einschließlich NaNs) und die Anzahl (ignoriert NaNs) einer Gruppe unterscheiden sich jedoch, wenn NaNs vorhanden sind.

dfgrouped = df.groupby(['b']).a.agg(['sum','size','count'])

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None

Wenn diese unterschiedlich sind, können Sie den Wert für das Ergebnis der Aggregationsfunktion für diese Gruppe auf Keine zurücksetzen.

Brian Preslopsky
quelle
1
Das war super hilfreich für mich, aber es beantwortet eine etwas andere Frage als die ursprüngliche. IIUC, Ihre Lösung verbreitet NaNs in der Summierung, aber die NaN-Elemente in der Spalte "b" werden weiterhin als Zeilen gelöscht.
Andrew