125 votes

Construire un DataFrame pandas à partir des éléments du dictionnaire imbriqué

Supposons que j'ai un dictionnaire imbriqué 'user_dict' avec une structure :

  • Niveau 1 : UserId (Long Integer)
  • Niveau 2 : Catégorie (Chaîne)
  • Niveau 3 : Attributs assortis (flottants, ints, etc.)

Par exemple, une entrée de ce dictionnaire serait :

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

chaque élément dans user_dict a la même structure et user_dict contient un grand nombre d'éléments que je veux introduire dans un DataFrame pandas, en construisant la série à partir des attributs. Dans ce cas, un index hiérarchique serait utile.

Plus précisément, ma question est de savoir s'il existe un moyen d'aider le constructeur de DataFrame à comprendre que la série doit être construite à partir des valeurs du "niveau 3" du dictionnaire ?

Si j'essaie quelque chose comme :

df = pandas.DataFrame(users_summary)

Les éléments du "niveau 1" (les UserId) sont considérés comme des colonnes, ce qui est l'inverse de ce que je veux obtenir (avoir les UserId comme index).

Je sais que je pourrais construire la série après avoir itéré sur les entrées du dictionnaire, mais s'il existe un moyen plus direct, ce serait très utile. Une question similaire serait de savoir s'il est possible de construire un DataFrame pandas à partir d'objets json listés dans un fichier.

183voto

Wouter Overmeire Points 6676

Un MultiIndex pandas est constitué d'une liste de tuples. L'approche la plus naturelle serait donc de remodeler votre dict d'entrée de sorte que ses clés soient des tuples correspondant aux valeurs multi-index dont vous avez besoin. Ensuite, vous pouvez simplement construire votre cadre de données en utilisant pd.DataFrame.from_dict en utilisant l'option 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

Une autre approche consisterait à construire votre cadre de données en concaténant les cadres de données composants :

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

48voto

coldspeed Points 111053

pd.concat accepte un dictionnaire. En gardant cela à l'esprit, il est possible d'améliorer la réponse actuellement acceptée en termes de simplicité et de performance en utilisant un dictionnaire de type compréhension du dictionnaire pour construire un dictionnaire faisant correspondre les clés aux sous-images.

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

11voto

Mishiko Points 231

J'avais l'habitude d'utiliser une boucle for pour itérer dans le dictionnaire, mais j'ai découvert que la conversion en un panneau, puis en un cadre de données, était beaucoup plus rapide. Disons que vous avez un dictionnaire 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}}}

La commande

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

où pd.Panel(d)[item] donne une trame de données

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

Vous pouvez ensuite utiliser la commande to_frame() pour le transformer en un cadre de données. J'utilise également la commande reset_index pour transformer les axes majeurs et mineurs en colonnes plutôt que de les avoir en indices.

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

Enfin, si vous n'aimez pas l'apparence du cadre, vous pouvez utiliser la fonction transpose de panel pour changer l'apparence avant d'appeler to_frame() ; voir la documentation ici. http://pandas.pydata.org/pandas-docs/dev/generated/pandas.Panel.transpose.html

À titre d'exemple

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

J'espère que cela vous aidera.

9voto

Melkor.cz Points 1411

Si quelqu'un veut obtenir le cadre de données dans un "format long" (les valeurs des feuilles ont le même type) sans multi-index, vous pouvez le faire :

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

(Je sais que la question originale veut probablement (I.) avoir les niveaux 1 et 2 comme multi-index et le niveau 3 comme colonnes et (II.) s'interroge sur d'autres moyens que l'itération sur les valeurs dans le dict. Mais j'espère que cette réponse est toujours pertinente et utile (I.) : pour les gens comme moi qui ont essayé de trouver un moyen d'obtenir le dict imbriqué dans cette forme et google ne retourne que cette question et (II.) : parce que les autres réponses impliquent une certaine itération aussi et je trouve cette approche flexible et facile à lire ; pas sûr de la performance, cependant).

9voto

tRosenflanz Points 426

Cette solution devrait fonctionner pour une profondeur arbitraire en aplatissant les clés du dictionnaire en une chaîne de tuple.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X