Erstellen Sie Pandas DataFrame aus Elementen im verschachtelten Wörterbuch

89

Angenommen, ich habe ein verschachteltes Wörterbuch 'user_dict' mit folgender Struktur:

  • Stufe 1: UserId (Long Integer)
  • Stufe 2: Kategorie (String)
  • Stufe 3: Verschiedene Attribute (Floats, Ints usw.)

Ein Eintrag in diesem Wörterbuch wäre beispielsweise:

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

Jedes Element in user_dicthat dieselbe Struktur und user_dictenthält eine große Anzahl von Elementen, die ich einem Pandas DataFrame zuführen möchte, um die Serie aus den Attributen zu erstellen. In diesem Fall wäre ein hierarchischer Index für diesen Zweck nützlich.

Insbesondere ist meine Frage, ob es eine Möglichkeit gibt, dem DataFrame-Konstruktor zu helfen, zu verstehen, dass die Reihe aus den Werten der "Ebene 3" im Wörterbuch erstellt werden sollte.

Wenn ich etwas versuche wie:

df = pandas.DataFrame(users_summary)

Die Elemente in "Ebene 1" (die Benutzer-IDs) werden als Spalten verwendet, was das Gegenteil von dem ist, was ich erreichen möchte (Benutzer-IDs als Index haben).

Ich weiß, dass ich die Serie nach dem Durchlaufen der Wörterbucheinträge erstellen könnte, aber wenn es einen direkteren Weg gibt, wäre dies sehr nützlich. Eine ähnliche Frage wäre die Frage, ob es möglich ist, einen Pandas-DataFrame aus in einer Datei aufgelisteten JSON-Objekten zu erstellen.

vladimir montealegre
quelle
In dieser Antwort finden Sie einfachere Alternativen.
CS95

Antworten:

138

Ein Pandas MultiIndex besteht aus einer Liste von Tupeln. Der natürlichste Ansatz wäre also, Ihr Eingabediktat so umzuformen, dass seine Schlüssel Tupel sind, die den von Ihnen benötigten Multi-Index-Werten entsprechen. Dann können Sie Ihren Datenrahmen einfach pd.DataFrame.from_dictmit der folgenden Option erstellen 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

Ein alternativer Ansatz wäre, Ihren Datenrahmen durch Verketten der Komponentendatenrahmen aufzubauen:

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
quelle
11
Gibt es eine vernünftige Möglichkeit, dies zu verallgemeinern, um mit Listen mit beliebiger Tiefe zu arbeiten? zB Listen bis zu einer beliebigen Tiefe, in denen einige Zweige kürzer sein können als andere, und ein None oder nan wird verwendet, wenn kürzere Zweige das Ende nicht erreichen?
naught101
5
Haben Sie sich die Unterstützung von Pandas JSON (Io Tools) und die Normalisierung angesehen? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire
1
Für mich hat die erste Methode einen Datenrahmen mit einem einzelnen Index mit Tupeln erstellt. Die zweite Methode funktionierte wie gewünscht / erwartet!
Arturomp
Irgendwelche Tipps, wie man diese neuen Spalten benennt? Zum Beispiel, wenn ich möchte, dass diese Nummern 12 und 15 in der Spalte 'id' stehen.
Zeremuschkin
1
@cheremushkin 12 und 15 befinden sich jetzt in der Zeile 'id'. Wenn Sie ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) übertragen, befinden sie sich in der Spalte 'id'. Sie können auch entstapeln ( pandas.pydata.org/pandas-docs/stable/reference/api/… ). Alles hängt davon ab, was Sie wirklich brauchen.
Wouter Overmeire
30

pd.concatakzeptiert ein Wörterbuch. Vor diesem Hintergrund ist es möglich, die derzeit akzeptierte Antwort in Bezug auf Einfachheit und Leistung zu verbessern, indem ein Wörterbuchverständnis verwendet wird , um Wörterbuchzuordnungsschlüssel für Unterrahmen zu erstellen.

pd.concat({k: pd.DataFrame(v).T for k, v in user_dict.items()}, axis=0)

Oder,

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
quelle
4
Brillant! Viel besser :)
pg2455
3
Wie würden Sie es tun, wenn Sie noch eine weitere innere Kategorie hätten? Wie zum Beispiel 12:{cat1:{cat11:{att1:val1,att2:val2}}}. Mit anderen Worten: Wie würde jemand die Lösung auf eine irrelevante Anzahl von Kategorien verallgemeinern?
Lucas Aimaretto
1
@LucasAimaretto Normalerweise können beliebig verschachtelte Strukturen mit abgeflacht werden json_normalize. Ich habe eine andere Antwort, die zeigt, wie es funktioniert.
CS95
1
Funktioniert nicht, wenn ves sich beispielsweise um eine einzelne Ganzzahl handelt. Kennen Sie in einem solchen Fall eine Alternative?
Sk
11

Früher habe ich auch eine for-Schleife zum Durchlaufen des Wörterbuchs verwendet, aber eine Sache, die viel schneller funktioniert, ist die Konvertierung in ein Panel und dann in einen Datenrahmen. Angenommen, Sie haben ein Wörterbuch 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}}}

Der Befehl

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

Dabei ergibt pd.Panel (d) [item] einen Datenrahmen

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

Sie können dann den Befehl to_frame () drücken, um daraus einen Datenrahmen zu machen. Ich benutze auch reset_index, um die Haupt- und Nebenachse in Spalten umzuwandeln, anstatt sie als Indizes zu haben.

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

Wenn Ihnen das Aussehen des Rahmens nicht gefällt, können Sie die Transponierungsfunktion des Bedienfelds verwenden, um das Erscheinungsbild vor dem Aufruf von to_frame () zu ändern. Weitere Informationen finden Sie in der Dokumentation unter http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html

Nur als Beispiel

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

Hoffe das hilft.

Mischiko
quelle
8
Panel ist in neueren Versionen von Pandas (v0.23 zum Zeitpunkt des Schreibens) veraltet.
CS95
6

Wenn jemand den Datenrahmen in einem "langen Format" (Blattwerte haben denselben Typ) ohne Multiindex erhalten möchte, können Sie dies tun:

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

(Ich weiß, dass die ursprüngliche Frage wahrscheinlich möchte, dass (I.) Level 1 und 2 als Multiindex und Level 3 als Spalten haben und (II.) Nach anderen Möglichkeiten als der Iteration über Werte im Diktat fragt. Aber ich hoffe, diese Antwort ist immer noch relevant und nützlich (I.): für Leute wie mich, die versucht haben, einen Weg zu finden, um das verschachtelte Diktat in diese Form zu bringen, und Google gibt nur diese Frage zurück und (II.): weil andere Antworten auch eine Iteration beinhalten und ich dies finde Ansatz flexibel und leicht zu lesen, jedoch nicht sicher über die Leistung.)

Melkor.cz
quelle
0

Aufbauend auf einer verifizierten Antwort hat dies für mich am besten funktioniert:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
El_1988
quelle