Suponha que eu tenha um dicionário aninhado 'user_dict' com a estrutura:
- Nível 1: UserId (número inteiro longo)
- Nível 2: Categoria (String)
- Nível 3: Atributos variados (flutuadores, ints, etc.)
Por exemplo, uma entrada deste dicionário seria:
user_dict[12] = {
"Category 1": {"att_1": 1,
"att_2": "whatever"},
"Category 2": {"att_1": 23,
"att_2": "another"}}
cada item em user_dict
tem a mesma estrutura e user_dict
contém um grande número de itens que desejo alimentar a um DataFrame pandas, construindo a série a partir dos atributos. Nesse caso, um índice hierárquico seria útil para o propósito.
Especificamente, minha pergunta é se existe uma maneira de ajudar o construtor DataFrame a entender que a série deve ser construída a partir dos valores do "nível 3" no dicionário.
Se eu tentar algo como:
df = pandas.DataFrame(users_summary)
Os itens no "nível 1" (os UserId's) são considerados colunas, o que é o oposto do que desejo alcançar (ter UserId's como índice).
Sei que poderia construir a série após iterar as entradas do dicionário, mas se houver uma maneira mais direta, isso seria muito útil. Uma pergunta semelhante seria se é possível construir um DataFrame do pandas a partir de objetos json listados em um arquivo.
fonte
Respostas:
Um pandas MultiIndex consiste em uma lista de tuplas. Portanto, a abordagem mais natural seria remodelar seu dicionário de entrada para que suas chaves sejam tuplas correspondentes aos valores de vários índices que você precisa. Então, você pode simplesmente construir seu dataframe usando
pd.DataFrame.from_dict
, usando a opçãoorient='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
Uma abordagem alternativa seria construir seu dataframe concatenando os dataframes do componente:
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
fonte
pd.concat
aceita um dicionário. Com isso em mente, é possível melhorar a resposta atualmente aceita em termos de simplicidade e desempenho, usando uma compreensão de dicionário para construir um dicionário de chaves de mapeamento para subquadros.pd.concat({k: pd.DataFrame(v).T for k, v in user_dict.items()}, axis=0)
Ou,
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
fonte
12:{cat1:{cat11:{att1:val1,att2:val2}}}
. Em outras palavras: como alguém poderia generalizar a solução para um número irrelevante de categorias?json_normalize
. Tenho outra resposta que mostra como funciona.v
for um único inteiro, por exemplo. Você conhece uma alternativa nesse caso?Eu costumava usar um loop for para iterar no dicionário também, mas uma coisa que descobri que funciona muito mais rápido é converter para um painel e depois para um dataframe. Digamos que você tenha um dicionário 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}}}
O 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
onde pd.Panel (d) [item] produz um 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
Você pode então clicar no comando to_frame () para transformá-lo em um dataframe. Eu uso reset_index também para transformar os eixos principais e secundários em colunas, em vez de tê-los como índices.
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
Finalmente, se você não gosta da aparência do quadro, pode usar a função transpor do painel para alterar a aparência antes de chamar to_frame () consulte a documentação aqui http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html
Só como exemplo
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
Espero que isto ajude.
fonte
Caso alguém deseje obter o quadro de dados em um "formato longo" (os valores de folha são do mesmo tipo) sem multiindex, você pode fazer isso:
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
(Eu sei que a pergunta original provavelmente quer (I.) ter os níveis 1 e 2 como multiíndices e o nível 3 como colunas e (II.) Perguntar sobre outras maneiras além da iteração sobre os valores no dicionário. Mas espero que esta resposta ainda seja relevante e útil (I.): para pessoas como eu, que tentaram encontrar uma maneira de colocar o dicionário aninhado nesta forma e o Google só retorna esta pergunta e (II.): porque outras respostas também envolvem alguma iteração e eu acho isso abordagem flexível e fácil de ler; não tenho certeza sobre o desempenho, no entanto.)
fonte
Com base na resposta verificada, para mim funcionou melhor:
ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0) ab.T
fonte