Construya pandas DataFrame a partir de elementos en un diccionario anidado

Resuelto vladimir montealegre asked hace 12 años • 7 respuestas

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_dictla misma estructura y user_dictcontiene 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.

vladimir montealegre avatar Nov 27 '12 06:11 vladimir montealegre
Aceptado

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
Wouter Overmeire avatar Nov 27 '2012 10:11 Wouter Overmeire

pd.concatAcepta 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
cs95 avatar Jan 22 '2019 03:01 cs95

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
tRosenflanz avatar Mar 24 '2021 21:03 tRosenflanz