Costruisci DataFrame panda dagli elementi nel dizionario annidato


90

Supponiamo di avere un dizionario annidato 'user_dict' con struttura:

  • Livello 1: ID utente (intero lungo)
  • Livello 2: Categoria (stringa)
  • Livello 3: attributi assortiti (float, int, ecc ..)

Ad esempio, una voce di questo dizionario sarebbe:

user_dict[12] = {
    "Category 1": {"att_1": 1, 
                   "att_2": "whatever"},
    "Category 2": {"att_1": 23, 
                   "att_2": "another"}}

ogni elemento in user_dictha la stessa struttura e user_dictcontiene un gran numero di elementi che voglio alimentare a un DataFrame panda, costruendo la serie dagli attributi. In questo caso sarebbe utile allo scopo un indice gerarchico.

Nello specifico, la mia domanda è se esiste un modo per aiutare il costruttore DataFrame a capire che la serie dovrebbe essere costruita dai valori del "livello 3" nel dizionario?

Se provo qualcosa di simile:

df = pandas.DataFrame(users_summary)

Gli elementi nel "livello 1" (gli UserId's) sono presi come colonne, che è l'opposto di ciò che voglio ottenere (avere UserId's come indice).

So che potrei costruire la serie dopo aver ripetuto le voci del dizionario, ma se esiste un modo più diretto questo sarebbe molto utile. Una domanda simile sarebbe chiedersi se sia possibile costruire un DataFrame panda da oggetti json elencati in un file.


Vedi questa risposta per alternative più semplici.
cs95

Risposte:


141

Un MultiIndex panda è costituito da un elenco di tuple. Quindi l'approccio più naturale sarebbe rimodellare il dict di input in modo che le sue chiavi siano tuple corrispondenti ai valori multi-indice richiesti. Quindi puoi semplicemente costruire il tuo dataframe usando pd.DataFrame.from_dict, usando l'opzione orient='index':

user_dict = {12: {'Category 1': {'att_1': 1, 'att_2': 'whatever'},
                  'Category 2': {'att_1': 23, 'att_2': 'another'}},
             15: {'Category 1': {'att_1': 10, 'att_2': 'foo'},
                  'Category 2': {'att_1': 30, 'att_2': 'bar'}}}

pd.DataFrame.from_dict({(i,j): user_dict[i][j] 
                           for i in user_dict.keys() 
                           for j in user_dict[i].keys()},
                       orient='index')


               att_1     att_2
12 Category 1      1  whatever
   Category 2     23   another
15 Category 1     10       foo
   Category 2     30       bar

Un approccio alternativo sarebbe costruire il tuo dataframe concatenando i dataframe dei componenti:

user_ids = []
frames = []

for user_id, d in user_dict.iteritems():
    user_ids.append(user_id)
    frames.append(pd.DataFrame.from_dict(d, orient='index'))

pd.concat(frames, keys=user_ids)

               att_1     att_2
12 Category 1      1  whatever
   Category 2     23   another
15 Category 1     10       foo
   Category 2     30       bar

11
C'è un modo ragionevole per generalizzare questo per lavorare con elenchi irregolari di profondità arbitraria? es. elenca a una profondità arbitraria, dove alcuni rami possono essere più corti di altri, e viene utilizzato Nessuno o nan quando i rami più corti non raggiungono la fine?
nought101

5
Hai esaminato il supporto di pandas json (strumenti io) e la normalizzazione? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire

1
per me, il primo metodo ha creato un dataframe con un singolo indice con tuple. il secondo metodo ha funzionato come desiderato / previsto!
arturomp

Qualche suggerimento su come denominare queste nuove colonne? Ad esempio, se desidero che questi numeri 12 e 15 siano nella colonna "id".
cheremushkin

1
@cheremushkin 12 e 15 sono ora nella riga "id", se si traspone ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) sono nella colonna "id". Puoi anche rimuovere lo stack ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) Dipende tutto da ciò di cui hai veramente bisogno.
Wouter Overmeire

33

pd.concataccetta un dizionario. Con questo in mente, è possibile migliorare la risposta attualmente accettata in termini di semplicità e prestazioni utilizzando una comprensione del dizionario per costruire un dizionario che associa le chiavi ai sub-frame.

pd.concat({k: pd.DataFrame(v).T for k, v in user_dict.items()}, axis=0)

O,

pd.concat({
        k: pd.DataFrame.from_dict(v, 'index') for k, v in user_dict.items()
    }, 
    axis=0)

              att_1     att_2
12 Category 1     1  whatever
   Category 2    23   another
15 Category 1    10       foo
   Category 2    30       bar

4
Brillante! Molto meglio :)
pg2455

3
Come lo faresti se avessi ancora un'ulteriore categoria interna? Come 12:{cat1:{cat11:{att1:val1,att2:val2}}}. In altre parole: come potrebbe qualcuno generalizzare la soluzione a un numero irrilevante di categorie?
Lucas Aimaretto

1
@LucasAimaretto Solitamente le strutture annidate arbitrariamente possono essere appiattite con json_normalize. Ho un'altra risposta che mostra come funziona.
cs95

1
Ad esempio, non funziona se vè un singolo numero intero. Conosci un'alternativa in questo caso?
sk

11

Quindi usavo anche un ciclo for per iterare nel dizionario, ma una cosa che ho scoperto che funziona molto più velocemente è la conversione in un pannello e poi in un dataframe. Supponi di avere un dizionario d

import pandas as pd
d
{'RAY Index': {datetime.date(2014, 11, 3): {'PX_LAST': 1199.46,
'PX_OPEN': 1200.14},
datetime.date(2014, 11, 4): {'PX_LAST': 1195.323, 'PX_OPEN': 1197.69},
datetime.date(2014, 11, 5): {'PX_LAST': 1200.936, 'PX_OPEN': 1195.32},
datetime.date(2014, 11, 6): {'PX_LAST': 1206.061, 'PX_OPEN': 1200.62}},
'SPX Index': {datetime.date(2014, 11, 3): {'PX_LAST': 2017.81,
'PX_OPEN': 2018.21},
datetime.date(2014, 11, 4): {'PX_LAST': 2012.1, 'PX_OPEN': 2015.81},
datetime.date(2014, 11, 5): {'PX_LAST': 2023.57, 'PX_OPEN': 2015.29},
datetime.date(2014, 11, 6): {'PX_LAST': 2031.21, 'PX_OPEN': 2023.33}}}

Il comando

pd.Panel(d)
<class 'pandas.core.panel.Panel'>
Dimensions: 2 (items) x 2 (major_axis) x 4 (minor_axis)
Items axis: RAY Index to SPX Index
Major_axis axis: PX_LAST to PX_OPEN
Minor_axis axis: 2014-11-03 to 2014-11-06

dove pd.Panel (d) [item] restituisce un dataframe

pd.Panel(d)['SPX Index']
2014-11-03  2014-11-04  2014-11-05 2014-11-06
PX_LAST 2017.81 2012.10 2023.57 2031.21
PX_OPEN 2018.21 2015.81 2015.29 2023.33

Puoi quindi premere il comando to_frame () per trasformarlo in un dataframe. Uso anche reset_index per trasformare gli assi maggiore e minore in colonne piuttosto che averli come indici.

pd.Panel(d).to_frame().reset_index()
major   minor      RAY Index    SPX Index
PX_LAST 2014-11-03  1199.460    2017.81
PX_LAST 2014-11-04  1195.323    2012.10
PX_LAST 2014-11-05  1200.936    2023.57
PX_LAST 2014-11-06  1206.061    2031.21
PX_OPEN 2014-11-03  1200.140    2018.21
PX_OPEN 2014-11-04  1197.690    2015.81
PX_OPEN 2014-11-05  1195.320    2015.29
PX_OPEN 2014-11-06  1200.620    2023.33

Infine, se non ti piace l'aspetto della cornice puoi usare la funzione di trasposizione del pannello per cambiare l'aspetto prima di chiamare to_frame () vedi la documentazione qui http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html

Proprio come un esempio

pd.Panel(d).transpose(2,0,1).to_frame().reset_index()
major        minor  2014-11-03  2014-11-04  2014-11-05  2014-11-06
RAY Index   PX_LAST 1199.46    1195.323     1200.936    1206.061
RAY Index   PX_OPEN 1200.14    1197.690     1195.320    1200.620
SPX Index   PX_LAST 2017.81    2012.100     2023.570    2031.210
SPX Index   PX_OPEN 2018.21    2015.810     2015.290    2023.330

Spero che sia di aiuto.


8
Il pannello è deprecato nelle versioni più recenti di panda (v0.23 al momento della scrittura).
cs95

6

Nel caso in cui qualcuno desideri ottenere il data frame in un "formato lungo" (i valori foglia hanno lo stesso tipo) senza multiindice, puoi farlo:

pd.DataFrame.from_records(
    [
        (level1, level2, level3, leaf)
        for level1, level2_dict in user_dict.items()
        for level2, level3_dict in level2_dict.items()
        for level3, leaf in level3_dict.items()
    ],
    columns=['UserId', 'Category', 'Attribute', 'value']
)

    UserId    Category Attribute     value
0       12  Category 1     att_1         1
1       12  Category 1     att_2  whatever
2       12  Category 2     att_1        23
3       12  Category 2     att_2   another
4       15  Category 1     att_1        10
5       15  Category 1     att_2       foo
6       15  Category 2     att_1        30
7       15  Category 2     att_2       bar

(So ​​che la domanda originale probabilmente vuole che (I.) abbia i Livelli 1 e 2 come multiindice e il Livello 3 come colonne e (II.) Chiede altri modi oltre all'iterazione sui valori nel dict. Ma spero che questa risposta sia ancora pertinente e utile (I.): a persone come me che hanno cercato di trovare un modo per ottenere il dict annidato in questa forma e google restituisce solo questa domanda e (II.): perché anche altre risposte implicano qualche iterazione e trovo questo approccio flessibile e di facile lettura; non sono sicuro delle prestazioni, però.)


0

Basandosi su una risposta verificata, per me ha funzionato meglio:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
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.