Buat DataFrame pandas dari item dalam kamus bertingkat

90

Misalkan saya memiliki kamus bersarang 'user_dict' dengan struktur:

  • Level 1: UserId (Long Integer)
  • Level 2: Kategori (String)
  • Level 3: Atribut Aneka (float, int, dll ..)

Misalnya, entri kamus ini adalah:

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

setiap item user_dictmemiliki struktur yang sama dan user_dictberisi sejumlah besar item yang ingin saya beri makan ke DataFrame pandas, membuat rangkaian dari atributnya. Dalam hal ini indeks hierarki akan berguna untuk tujuan tersebut.

Secara khusus, pertanyaan saya adalah apakah ada cara untuk membantu konstruktor DataFrame memahami bahwa rangkaian tersebut harus dibangun dari nilai "level 3" dalam kamus?

Jika saya mencoba sesuatu seperti:

df = pandas.DataFrame(users_summary)

Item di "level 1" (UserId) diambil sebagai kolom, yang merupakan kebalikan dari apa yang ingin saya capai (memiliki UserId sebagai indeks).

Saya tahu saya dapat membuat rangkaian setelah mengulang entri kamus, tetapi jika ada cara yang lebih langsung, ini akan sangat berguna. Pertanyaan serupa akan menanyakan apakah mungkin untuk membuat DataFrame pandas dari objek json yang terdaftar dalam sebuah file.

vladimir montealegre
sumber
Lihat jawaban ini untuk alternatif yang lebih sederhana.
cs95

Jawaban:

138

MultiIndex pandas terdiri dari daftar tupel. Jadi pendekatan yang paling alami adalah dengan membentuk kembali dikt input Anda sehingga kuncinya adalah tupel yang sesuai dengan nilai multi-indeks yang Anda butuhkan. Kemudian Anda dapat membuat kerangka data Anda menggunakan pd.DataFrame.from_dict, menggunakan opsi 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

Pendekatan alternatifnya adalah membangun kerangka data Anda dengan menggabungkan kerangka data komponen:

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
sumber
11
Adakah cara yang masuk akal untuk menggeneralisasi ini agar bekerja dengan daftar acak mendalam yang tidak teratur? misalnya daftar ke kedalaman arbitrer, di mana beberapa cabang mungkin lebih pendek dari yang lain, dan Tidak ada atau nan digunakan ketika cabang yang lebih pendek tidak mencapai ujung?
n nothing101
5
Pernahkah Anda melihat dukungan pandas json (alat io) dan normalisasi? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire
1
bagi saya, metode pertama membuat kerangka data dengan indeks tunggal dengan tupel. metode kedua bekerja seperti yang diinginkan / diharapkan!
arturomp
Adakah tips tentang cara memberi nama kolom baru ini? Misalnya, jika saya ingin angka 12 dan 15 ini ada di kolom 'id'.
cheremushkin
1
@cheremushkin 12 dan 15 sekarang ada di baris 'id', jika Anda tranpose ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) mereka ada di kolom 'id'. Anda juga dapat menghapus ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) Semuanya tergantung pada apa yang Anda butuhkan.
Wouter Overmeire
31

pd.concatmenerima kamus. Dengan pemikiran ini, adalah mungkin untuk memperbaiki jawaban yang diterima saat ini dalam hal kesederhanaan dan kinerja dengan menggunakan pemahaman kamus untuk membangun kunci pemetaan kamus ke sub-bingkai.

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

Atau,

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
sumber
4
Cemerlang! Jauh lebih baik :)
pg2455
3
Bagaimana Anda akan melakukannya jika Anda masih memiliki kategori lebih dalam? Seperti 12:{cat1:{cat11:{att1:val1,att2:val2}}}. Dengan kata lain: bagaimana seseorang akan menggeneralisasi solusi untuk sejumlah kategori yang tidak relevan?
Lucas Aimaretto
1
@LucasAimaretto Biasanya struktur bersarang sewenang-wenang dapat diratakan dengan json_normalize. Saya punya jawaban lain yang menunjukkan cara kerjanya.
cs95
1
Tidak berfungsi jika vberupa bilangan bulat tunggal misalnya. Apakah Anda mengetahui alternatif dalam kasus seperti itu?
sk
11

Jadi saya dulu menggunakan for loop untuk iterasi melalui kamus juga, tapi satu hal yang saya temukan yang bekerja lebih cepat adalah mengonversi ke panel dan kemudian ke dataframe. Katakanlah Anda memiliki kamus 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}}}

Perintah

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

dimana pd.Panel (d) [item] menghasilkan dataframe

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

Anda kemudian dapat menekan perintah to_frame () untuk mengubahnya menjadi dataframe. Saya menggunakan reset_index juga untuk mengubah sumbu mayor dan minor menjadi kolom daripada menjadikannya sebagai indeks.

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

Terakhir, jika Anda tidak menyukai tampilan bingkai, Anda dapat menggunakan fungsi transpose panel untuk mengubah tampilan sebelum memanggil to_frame () lihat dokumentasi di sini http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html

Sekadar contoh

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

Semoga ini membantu.

Mishiko
sumber
8
Panel tidak digunakan lagi di versi panda yang lebih baru (v0.23 pada saat penulisan).
cs95
6

Jika seseorang ingin mendapatkan bingkai data dalam "format panjang" (nilai daun memiliki jenis yang sama) tanpa multiindeks, Anda dapat melakukan ini:

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

(Saya tahu pertanyaan awal mungkin ingin (I.) memiliki Level 1 dan 2 sebagai multiindex dan Level 3 sebagai kolom dan (II.) Menanyakan tentang cara lain selain iterasi atas nilai dalam dikt. Tapi saya harap jawaban ini masih relevan dan berguna (I.): untuk orang-orang seperti saya yang telah mencoba menemukan cara untuk mendapatkan dikt bersarang ke dalam bentuk ini dan google hanya mengembalikan pertanyaan ini dan (II.): karena jawaban lain juga melibatkan beberapa iterasi dan saya menemukan ini pendekatan fleksibel dan mudah dibaca; meskipun tidak yakin dengan kinerja.)

Melkor.cz
sumber
0

Berdasarkan jawaban terverifikasi, bagi saya ini bekerja paling baik:

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