panda GroupBy colonne con valori NaN (mancanti)


147

Ho un DataFrame con molti valori mancanti nelle colonne che desidero raggruppare:

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]}

osserva che Pandas ha eliminato le righe con valori target NaN. (Voglio includere queste righe!)

Dato che ho bisogno di molte di queste operazioni (molti col hanno valori mancanti) e uso di funzioni più complicate rispetto ai soli mediani (tipicamente foreste casuali), voglio evitare di scrivere pezzi di codice troppo complicati.

Eventuali suggerimenti? Devo scrivere una funzione per questo o c'è una soluzione semplice?


1
@PhillipCloud Ho modificato questa domanda per includere solo la domanda, che è abbastanza buona, relativa al potenziamento dei panda aperti di Jeff.
Andy Hayden,

1
Non riuscire a includere (e propagare) NaN nei gruppi è abbastanza aggravante. Citando R non è convincente, poiché questo comportamento non è coerente con molte altre cose. Comunque, anche l'hack fittizio è piuttosto male. Tuttavia, la dimensione (include NaN) e il conteggio (ignora le NaN) di un gruppo differiranno se ci sono NaN. dfgrouped = df.groupby (['b']). a.agg (['sum', 'size', 'count']) dfgrouped ['sum'] [dfgrouped ['size']! = dfgrouped ['count ']] = Nessuno
Brian Preslopsky

Puoi riassumere ciò che stai specificamente cercando di ottenere? cioè vediamo un output, ma qual è l'output "desiderato"?
circa

3
Con panda 1.1 sarà presto in grado di specificare dropna=Falsein groupby()per ottenere il risultato desiderato.Maggiori informazioni
cs95

Risposte:


131

Questo è menzionato nella sezione Dati mancanti dei documenti :

I gruppi NA in GroupBy vengono automaticamente esclusi. Questo comportamento è coerente con R, ad esempio.

Una soluzione alternativa consiste nell'utilizzare un segnaposto prima di eseguire il groupby (ad esempio -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

Detto questo, sembra un trucco piuttosto terribile ... forse ci dovrebbe essere un'opzione per includere NaN nel groupby (vedi questo problema di github - che usa lo stesso hack segnaposto).


4
Questa è una logica ma una sorta di soluzione divertente a cui ho pensato prima, Pandas crea campi NaN da quelli vuoti e dobbiamo cambiarli di nuovo. Questo è il motivo per cui sto pensando di cercare altre soluzioni come eseguire un server SQL e interrogare le tabelle da lì (sembra un po 'troppo complicato), o cercare un'altra libreria nonostante Panda, o usare la mia (che voglio per sbarazzarsi di). Grazie
Gyula Sámuel Karli il

@ GyulaSámuelKarli Per me questo sembra un piccolo bug (vedi il report dei bug sopra), e la mia soluzione è una soluzione alternativa. Trovo strano che tu scriva l'intera biblioteca.
Andy Hayden,

1
Non voglio scrivere Panda, ma cerco lo strumento più adatto alle mie richieste.
Gyula Sámuel Karli,

1
Dai un'occhiata alla mia risposta qui sotto, credo di aver trovato una soluzione abbastanza buona (più pulita e probabilmente più veloce). stackoverflow.com/a/43375020/408853
circa

4
No, questo non è coerente con R. df%>% group_by fornirà anche i riepiloghi NA con un avviso che può essere evitato passando la colonna di raggruppamento attraverso fct_explicit_na e quindi viene creato un livello (mancante).
Ravaging Care,

40

Argomento antico, se qualcuno si imbatte ancora in questo, un'altra soluzione è quella di convertire via .astype (str) in stringa prima di raggruppare. Ciò conserverà le NaN.

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

@ K3 --- rnc: vedi il commento al tuo link - l'autore del post nel tuo link ha fatto qualcosa di sbagliato.
Thomas,

@Thomas, sì, esattamente come nell'esempio sopra. Modifica se puoi rendere l'esempio sicuro (e come banale).
K3 --- rnc,

Il sumof aè la concatenazione di stringhe qui, non una somma numerica. Questo "funziona" solo perché "b" consisteva in voci distinte. Devi avere 'a' per essere numerico e 'b' per essere stringa
BallpointBen,

30

panda> = 1.1

Da Panda 1.1 hai un migliore controllo su questo comportamento, i valori NA sono ora consentiti nel grouper usando 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

4
Spero che questa risposta porti una graduale marcia verso l'alto. È l'approccio corretto.
kdbanman,

Non credo che 1.1 sia stato ancora rilasciato. Controllato su conda e pip e le versioni sono ancora 1.0.4
sammywemmy l'

@sammywemmy pandas 1.1 è uscito il 28 luglio '20, credo.
CS95

@ cs95 haha. Grazie. Ho già effettuato l'upgrade.
sammywemmy

9

Non riesco ad aggiungere un commento a M. Kiewisch poiché non ho abbastanza punti reputazione (ne ho solo 41 ma ne ho bisogno più di 50 per commentare).

Ad ogni modo, voglio solo sottolineare che la soluzione di M. Kiewisch non funziona così com'è e potrebbe aver bisogno di ulteriori modifiche. Consideriamo ad esempio

>>> 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

che mostra che per il gruppo b = 4.0, il valore corrispondente è 15 invece di 6. Qui sta semplicemente concatenando 1 e 5 come stringhe invece di aggiungerlo come numeri.


13
Questo perché hai convertito l'intero DF in str, anziché solo nella bcolonna
Korem il

Si noti che questo è stato risolto nella risposta menzionata ora.
Shaido - Ripristina Monica il

1
La nuova soluzione è migliore ma ancora non sicura, secondo me. Si consideri un caso in cui una delle voci nella colonna 'b' è uguale a np.NaN con stringhe. Quindi quelle cose vengono messe insieme. df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ; df ['b'] = df ['b']. astype (str); df.groupby (['b']). sum ()
Kamaraju Kusumanchi

6

Un piccolo punto alla soluzione di Andy Hayden: non funziona (più?) Perché np.nan == np.nancede False, quindi la replacefunzione in realtà non fa nulla.

Ciò che ha funzionato per me è stato questo:

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

(Almeno questo è il comportamento di Panda 0.19.2. Mi dispiace aggiungerlo come risposta diversa, non ho abbastanza reputazione per commentare.)


12
C'è anche df['b'].fillna(-1).
K3 --- rnc,

6

Tutte le risposte fornite finora comportano comportamenti potenzialmente pericolosi in quanto è possibile selezionare un valore fittizio che fa effettivamente parte del set di dati. Ciò è sempre più probabile quando si creano gruppi con molti attributi. In poche parole, l'approccio non sempre si generalizza bene.

Una soluzione meno complicata è utilizzare pd.drop_duplicates () per creare un indice univoco di combinazioni di valori ognuna con il proprio ID e quindi raggruppare su quell'id. È più dettagliato ma fa il lavoro svolto:

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]

Nota che ora puoi semplicemente fare quanto segue:

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')]))

Ciò restituirà il risultato positivo senza doversi preoccupare di sovrascrivere dati reali che vengono scambiati come valore fittizio.


Questa è la migliore soluzione per il caso generale, ma nei casi in cui conosco una stringa / un numero non valido che posso usare, probabilmente andrò con la risposta di Andy Hayden qui sotto ... Spero che Panda risolva presto questo comportamento.
Sarah Messer,

4

Ho già risposto a questa domanda, ma per qualche motivo la risposta è stata convertita in un commento. Tuttavia, questa è la soluzione più efficiente:

Non essere in grado di includere (e propagare) NaN nei gruppi è abbastanza aggravante. Citando R non è convincente, poiché questo comportamento non è coerente con molte altre cose. Comunque, anche l'hack fittizio è piuttosto male. Tuttavia, la dimensione (include NaN) e il conteggio (ignora le NaN) di un gruppo differiranno se ci sono NaN.

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

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

In caso contrario, è possibile impostare il valore su Nessuno per il risultato della funzione di aggregazione per quel gruppo.


1
Questo mi è stato di grande aiuto, ma risponde a una domanda leggermente diversa da quella originale. IIUC, la tua soluzione propaga NaN nella somma, ma gli elementi NaN nella colonna "b" vengono comunque eliminati come righe.
Andrew

0

Panda installati 1.1 in Anaconda

Non sono in grado di commentare la risposta di CS95, ma mi ha aiutato a risolvere il problema.

Ho provato a installare Pandas 1.1 ma non è riuscito a utilizzare il suo codice, quindi ho cercato su Google e sono riuscito a installarlo.

Per prima cosa eseguo il prompt di anaconda come amministratore e incollo il seguente codice:

pip install pandas==1.1.0rc0

Successivamente includi l'uso dropna = False

Link: https://libraries.io/pypi/pandas


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.