Suddivisione del dizionario / elenco all'interno di una colonna Panda in colonne separate


146

Ho dei dati salvati in un database postgreSQL. Sto interrogando questi dati usando Python2.7 e trasformandoli in un Pandas DataFrame. Tuttavia, l'ultima colonna di questo frame di dati contiene un dizionario (o un elenco?) Di valori al suo interno. DataFrame è simile al seguente:

[1] df
Station ID     Pollutants
8809           {"a": "46", "b": "3", "c": "12"}
8810           {"a": "36", "b": "5", "c": "8"}
8811           {"b": "2", "c": "7"}
8812           {"c": "11"}
8813           {"a": "82", "c": "15"}

Ho bisogno di dividere questa colonna in colonne separate in modo che DataFrame assomigli a questo:

[2] df2
Station ID     a      b       c
8809           46     3       12
8810           36     5       8
8811           NaN    2       7
8812           NaN    NaN     11
8813           82     NaN     15

Il problema principale che sto riscontrando è che le liste non hanno le stesse lunghezze. Ma tutte le liste contengono solo gli stessi 3 valori: a, b e c. E appaiono sempre nello stesso ordine (un primo, b secondo, c terzo).

Il seguente codice USATO per funzionare e restituire esattamente quello che volevo (df2).

[3] df 
[4] objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
[5] df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
[6] print(df2)

Stavo eseguendo questo codice solo la scorsa settimana e funzionava bene. Ma ora il mio codice è rotto e ottengo questo errore dalla riga [4]:

IndexError: out-of-bounds on slice (end) 

Non ho apportato modifiche al codice ma ora ricevo l'errore. Ritengo che ciò sia dovuto al fatto che il mio metodo non è solido o adeguato.

Eventuali suggerimenti o indicazioni su come dividere questa colonna di elenchi in colonne separate sarebbero molto apprezzati!

EDIT: Penso che i metodi .tolist () e .apply non funzionano sul mio codice perché è una stringa unicode, cioè:

#My data format 
u{'a': '1', 'b': '2', 'c': '3'}

#and not
{u'a': '1', u'b': '2', u'c': '3'}

I dati vengono importati dal database postgreSQL in questo formato. Qualche aiuto o idee con questo problema? c'è un modo per convertire l'unicode?


Ho risposto con una soluzione leggermente diversa, ma il tuo codice dovrebbe effettivamente funzionare bene. Usando il mio esempio fittizio di seguito, questo funziona usando i panda 0.18.1 se lascio fuori la ilocparte
joris

Fa parte del fatto che iloc[:, :3]presuppone che ci saranno 3 elementi, e forse le sezioni di dati più recenti hanno solo 1 o 2 (ad esempio, non ci sono bcome in index 8813)?
Dwanderson,

Risposte:


166

Per convertire la stringa in un dict reale, puoi farlo df['Pollutant Levels'].map(eval). Successivamente, la soluzione seguente può essere utilizzata per convertire il dict in colonne diverse.


Usando un piccolo esempio, puoi usare .apply(pd.Series):

In [2]: df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

In [3]: df
Out[3]:
   a                   b
0  1           {u'c': 1}
1  2           {u'd': 3}
2  3  {u'c': 5, u'd': 6}

In [4]: df['b'].apply(pd.Series)
Out[4]:
     c    d
0  1.0  NaN
1  NaN  3.0
2  5.0  6.0

Per combinarlo con il resto del frame di dati, puoi concatle altre colonne con il risultato sopra:

In [7]: pd.concat([df.drop(['b'], axis=1), df['b'].apply(pd.Series)], axis=1)
Out[7]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

Usando il tuo codice, questo funziona anche se lascio fuori la ilocparte:

In [15]: pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
Out[15]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

2
Sto usando pd.DataFrame(df[col].tolist())da molto tempo, non ci ho mai pensato apply(pd.Series). Molto bella.
Ayhan,

1
Ora capisco il problema. Il file .apply (pd.Series) non funziona sul mio set di dati perché l'intera riga è una stringa unicode. È: u '{' a ':' 1 ',' b ':' 2 ',' c ':' 3 '} e non {u'a': '1', u'b ':' 2 ', u'c ':' 3 '} come mostrano le tue soluzioni. Quindi il codice non può dividerlo in 3 colonne riconoscibili.
Llaffin,

2
@ayhan In realtà, l'ho testato e l' DataFrame(df['col'].tolist())approccio è piuttosto veloce rispetto all'approccio apply!
joris,

3
@llaffin Se è una stringa, puoi convertirla in un dict reale df[col].map(eval)prima di convertirla in un DataFrame
joris

2
Funziona perfettamente, ma è (molto) più lenta della nuova soluzione (2019) contributi di Lech Birek stackoverflow.com/a/55355928/2721710
drasc

85

So che la domanda è piuttosto vecchia, ma sono arrivato qui alla ricerca di risposte. Al momento esiste un modo migliore (e più veloce) per farlo usando json_normalize:

import pandas as pd

df2 = pd.json_normalize(df['Pollutant Levels'])

Questo evita costose funzioni di applicazione ...


4
Wow! Ho svolto funzioni di applicazione noiose e confuse tutto il giorno in Panda su oggetti JSON, e poi mi sono imbattuto in questa risposta e ho pensato "Assolutamente no, non avrebbe potuto essere così facile!" Poi l'ho provato ed è stato. Grazie mille!
Emac,

L'unico problema qui è che non sembra copiare su altre colonne senza json, il che significa che se stai cercando di normalizzare una riga di valori JSON dovrai copiarlo e combinare i due, ancora molto meglio del mio iterativo metodo. Cudos!
Mr. Drew

per questa soluzione come sarebbe possibile selezionare dinamicamente l'elenco di quali colonne devono essere normalizzate? I dati transazionali che sto introducendo dai .jsonfile provengono da origini diverse e non sono sempre le stesse colonne nidificate. Ho cercato di trovare un modo per creare un elenco di colonne che contengono dicts ma non riesco a risolverlo
Callum Smyth,

5
from pandas.io.json import json_normalize
Ramin Melikov,

C'è un modo per applicare un prefisso alle colonne finali? Ho notato che ci sono argomenti come meta_prefixe record_prefix. Tuttavia, non riesco a farlo funzionare con il mio frame di dati (il frame di dati finale è corretto nel mio caso, ma vorrei applicare i prefissi).
J. Snow,

21

Prova questo: i dati restituiti da SQL devono essere convertiti in Dict. o potrebbe essere "Pollutant Levels" è oraPollutants'

   StationID                   Pollutants
0       8809  {"a":"46","b":"3","c":"12"}
1       8810   {"a":"36","b":"5","c":"8"}
2       8811            {"b":"2","c":"7"}
3       8812                   {"c":"11"}
4       8813          {"a":"82","c":"15"}


df2["Pollutants"] = df2["Pollutants"].apply(lambda x : dict(eval(x)) )
df3 = df2["Pollutants"].apply(pd.Series )

    a    b   c
0   46    3  12
1   36    5   8
2  NaN    2   7
3  NaN  NaN  11
4   82  NaN  15


result = pd.concat([df, df3], axis=1).drop('Pollutants', axis=1)
result

   StationID    a    b   c
0       8809   46    3  12
1       8810   36    5   8
2       8811  NaN    2   7
3       8812  NaN  NaN  11
4       8813   82  NaN  15

13

La risposta di Merlin è migliore e super facile, ma non abbiamo bisogno di una funzione lambda. La valutazione del dizionario può essere tranquillamente ignorata in uno dei due modi seguenti, come illustrato di seguito:

Modo 1: due passaggi

# step 1: convert the `Pollutants` column to Pandas dataframe series
df_pol_ps = data_df['Pollutants'].apply(pd.Series)

df_pol_ps:
    a   b   c
0   46  3   12
1   36  5   8
2   NaN 2   7
3   NaN NaN 11
4   82  NaN 15

# step 2: concat columns `a, b, c` and drop/remove the `Pollutants` 
df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

Modo 2: i due passaggi precedenti possono essere combinati in una volta sola:

df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

13

Consiglio vivamente il metodo di estrarre la colonna 'inquinanti':

df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)

è molto più veloce di

df_pollutants = df['Pollutants'].apply(pd.Series)

quando la dimensione di df è gigante.


sarebbe fantastico se tu potessi spiegare come / perché questo funziona ed è molto meglio! per me è sempre più veloce e ~ 200 volte più veloce quando ottieni più di ~ 1000 file
Sam Mason

@SamMason quando lo fai applyl'intero frame di dati è gestito da Panda, ma quando si tratta di valuesesso gioca solo con ciò numpy ndarraysche è intrinsecamente più veloce a causa del fatto che ha cimplementazioni pure .
Sagar Kar,

8

Puoi usare joincon pop+ tolist. Le prestazioni sono paragonabili a concatcon drop+ tolist, ma alcuni possono trovare questo pulitore sintassi:

res = df.join(pd.DataFrame(df.pop('b').tolist()))

Benchmarking con altri metodi:

df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

def joris1(df):
    return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1)

def joris2(df):
    return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)

def jpp(df):
    return df.join(pd.DataFrame(df.pop('b').tolist()))

df = pd.concat([df]*1000, ignore_index=True)

%timeit joris1(df.copy())  # 1.33 s per loop
%timeit joris2(df.copy())  # 7.42 ms per loop
%timeit jpp(df.copy())     # 7.68 ms per loop

3

Una soluzione di linea è la seguente:

>>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1)
>>> print(df)
   Station ID    a    b   c
0        8809   46    3  12
1        8810   36    5   8
2        8811  NaN    2   7
3        8812  NaN  NaN  11
4        8813   82  NaN  15

1

my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col'])

.. avrebbe analizzato correttamente il dict (inserendo ciascuna chiave dict in una colonna df separata e i valori chiave in righe df), quindi i dadi non verrebbero schiacciati in una singola colonna in primo luogo.


0

Ho concatenato questi passaggi in un metodo, devi solo passare il frame di dati e la colonna che contiene il dict da espandere:

def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame:
    """
    dw: DataFrame with some column which contain a dict to expand
        in columns
    column_to_expand: String with column name of dw
    """
    import pandas as pd

    def convert_to_dict(sequence: str) -> Dict:
        import json
        s = sequence
        json_acceptable_string = s.replace("'", "\"")
        d = json.loads(json_acceptable_string)
        return d    

    expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1),
                                    dw[column_to_expand]
                                    .apply(convert_to_dict)
                                    .apply(pd.Series)],
                                    axis=1)
    return expanded_dataframe

-1
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
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.