Construya pandas DataFrame a partir de elementos en un diccionario anidado
Supongamos que tengo un diccionario anidado 'user_dict' con estructura:
- Nivel 1: ID de usuario (entero largo)
- Nivel 2: Categoría (Cadena)
- Nivel 3: Atributos variados (flotantes, enteros, 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 tiene user_dict
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 sería útil un índice jerárquico para este propósito.
Específicamente, mi pregunta es si existe una manera 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 hay una forma más directa, sería muy útil. Una pregunta similar sería si es posible construir un DataFrame de pandas a partir de objetos json enumerados en un archivo.
Un pandas MultiIndex consta de una lista de tuplas. Entonces, 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 puedes simplemente construir tu marco de datos usando pd.DataFrame.from_dict
, usando la opción 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 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
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 un diccionario de comprensión para construir un diccionario que asigna claves a subtramas.
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
Esta solución debería funcionar para una profundidad arbitraria al aplanar las claves del diccionario en una cadena de tuplas.
def flatten_dict(nested_dict):
res = {}
if isinstance(nested_dict, dict):
for k in nested_dict:
flattened_dict = flatten_dict(nested_dict[k])
for key, val in flattened_dict.items():
key = list(key)
key.insert(0, k)
res[tuple(key)] = val
else:
res[()] = nested_dict
return res
def nested_dict_to_df(values_dict):
flat_dict = flatten_dict(values_dict)
df = pd.DataFrame.from_dict(flat_dict, orient="index")
df.index = pd.MultiIndex.from_tuples(df.index)
df = df.unstack(level=-1)
df.columns = df.columns.map("{0[1]}".format)
return df