Supongamos que tengo un diccionario anidado 'user_dict' con estructura:
- Nivel 1: UserId (entero largo)
- Nivel 2: Categoría (cadena)
- Nivel 3: Atributos surtidos (flotadores, entradas, etc.)
Por ejemplo, una entrada de este diccionario sería:
user_dict[12] = {
"Category 1": {"att_1": 1,
"att_2": "whatever"},
"Category 2": {"att_1": 23,
"att_2": "another"}}
cada elemento user_dict
tiene la misma estructura y user_dict
contiene una gran cantidad de elementos que quiero alimentar a un DataFrame de pandas, construyendo la serie a partir de los atributos. En este caso, un índice jerárquico sería útil para este propósito.
Específicamente, mi pregunta es si existe una forma de ayudar al constructor de DataFrame a comprender que la serie debe construirse a partir de los valores del "nivel 3" en el diccionario.
Si intento algo como:
df = pandas.DataFrame(users_summary)
Los elementos en el "nivel 1" (los UserId) se toman como columnas, que es lo opuesto a lo que quiero lograr (tener UserId como índice).
Sé que podría construir la serie después de iterar sobre las entradas del diccionario, pero si hubiera una forma más directa, sería muy útil. Una pregunta similar sería preguntar si es posible construir un DataFrame de pandas a partir de objetos json enumerados en un archivo.
fuente
Respuestas:
Un MultiIndex de pandas consiste en una lista de tuplas. Por lo tanto, el enfoque más natural sería remodelar su dictado de entrada para que sus claves sean tuplas correspondientes a los valores de índices múltiples que necesita. Luego puede simplemente construir su marco de datos usando
pd.DataFrame.from_dict
, usando la opciónorient='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 enfoque alternativo sería construir su marco de datos concatenando los marcos de datos de los componentes:
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
fuente
pd.concat
acepta un diccionario. Con esto en mente, es posible mejorar la respuesta actualmente aceptada en términos de simplicidad y rendimiento mediante el uso de una comprensión de diccionario para construir un diccionario que mapee las claves de los sub-marcos.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
fuente
12:{cat1:{cat11:{att1:val1,att2:val2}}}
. En otras palabras: ¿cómo podría alguien generalizar la solución a un número irrelevante de categorías?json_normalize
. Tengo otra respuesta que muestra cómo funciona.v
es un único entero, por ejemplo. ¿Conoce alguna alternativa en tal caso?Así que solía usar un bucle for para iterar a través del diccionario también, pero una cosa que encontré que funciona mucho más rápido es convertir a un panel y luego a un marco de datos. Digamos que tienes un diccionario 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}}}
El 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
donde pd.Panel (d) [item] produce un marco de datos
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
Luego puede presionar el comando to_frame () para convertirlo en un marco de datos. También uso reset_index para convertir los ejes mayor y menor en columnas en lugar de tenerlos 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, si no le gusta la apariencia del marco, puede usar la función de transposición del panel para cambiar la apariencia antes de llamar a to_frame () ver la documentación aquí http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html
Solo como un ejemplo
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 esto ayude.
fuente
En caso de que alguien quiera obtener el marco de datos en un "formato largo" (los valores de hoja tienen el mismo tipo) sin multiindex, puede hacer esto:
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
(Sé que la pregunta original probablemente quiera que (I.) tenga los Niveles 1 y 2 como multiíndice y el Nivel 3 como columnas y (II.) Pregunta sobre otras formas además de la iteración sobre los valores en el dict. Pero espero que esta respuesta siga siendo relevante y útil (I.): para personas como yo que han intentado encontrar una manera de obtener el dict anidado en esta forma y Google solo devuelve esta pregunta y (II.): porque otras respuestas también implican alguna iteración y encuentro esto enfoque flexible y fácil de leer; sin embargo, no estoy seguro del rendimiento).
fuente
Sobre la base de una respuesta verificada, para mí esto funcionó mejor:
ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0) ab.T
fuente