Tạo DataFrame cho gấu trúc từ các mục trong từ điển lồng nhau


90

Giả sử tôi có một từ điển lồng nhau 'user_dict' với cấu trúc:

  • Cấp độ 1: UserId (Số nguyên dài)
  • Mức 2: Danh mục (Chuỗi)
  • Mức 3: Các thuộc tính được phân loại (float, ints, v.v.)

Ví dụ, một mục của từ điển này sẽ là:

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

mỗi mục trong user_dictcó cấu trúc giống nhau và user_dictchứa một số lượng lớn các mục mà tôi muốn cung cấp cho gấu trúc DataFrame, xây dựng chuỗi từ các thuộc tính. Trong trường hợp này, một chỉ mục phân cấp sẽ hữu ích cho mục đích này.

Cụ thể, câu hỏi của tôi là liệu có tồn tại một cách nào để giúp phương thức khởi tạo DataFrame hiểu rằng chuỗi phải được xây dựng từ các giá trị của "cấp 3" trong từ điển không?

Nếu tôi thử một cái gì đó như:

df = pandas.DataFrame(users_summary)

Các mục trong "cấp độ 1" (UserId) được lấy làm cột, điều này ngược lại với những gì tôi muốn đạt được (có UserId làm chỉ mục).

Tôi biết tôi có thể xây dựng chuỗi sau khi lặp lại các mục từ điển, nhưng nếu có cách trực tiếp hơn, điều này sẽ rất hữu ích. Một câu hỏi tương tự sẽ hỏi liệu có thể tạo DataFrame gấu trúc từ các đối tượng json được liệt kê trong một tệp hay không.


Xem câu trả lời này để biết các lựa chọn thay thế đơn giản hơn.
cs95

Câu trả lời:


138

Một con gấu trúc MultiIndex bao gồm một danh sách các bộ giá trị. Vì vậy, cách tiếp cận tự nhiên nhất sẽ là định hình lại mệnh lệnh đầu vào của bạn để các khóa của nó là các bộ giá trị tương ứng với các giá trị đa chỉ mục mà bạn yêu cầu. Sau đó, bạn chỉ có thể xây dựng khung dữ liệu của mình bằng pd.DataFrame.from_dictcách sử dụng tùy chọ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

Một cách tiếp cận thay thế sẽ là xây dựng khung dữ liệu của bạn bằng cách nối các khung dữ liệu thành phần:

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

11
Có cách nào hợp lý để tổng quát hóa điều này để làm việc với các danh sách có độ sâu tùy ý không? Ví dụ: danh sách có độ sâu tùy ý, trong đó một số nhánh có thể ngắn hơn những nhánh khác, và không có hoặc nan được sử dụng khi các nhánh ngắn hơn không đến cuối?
naught101

5
Bạn đã xem xét hỗ trợ pandas json (công cụ io) và chuẩn hóa chưa? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire

1
đối với tôi, phương pháp đầu tiên tạo khung dữ liệu với một chỉ mục duy nhất với các bộ giá trị. phương pháp thứ hai đã hoạt động như mong muốn / mong đợi!
arturomp

Bất kỳ mẹo nào về cách đặt tên cho các cột mới này? Ví dụ: nếu tôi muốn các số 12 và 15 này nằm trong cột 'id'.
cheremushkin

1
@cheremushkin 12 và 15 hiện nằm trong hàng 'id', nếu bạn chuyển mục đích ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) chúng nằm trong cột 'id'. Bạn cũng có thể bỏ đóng gói ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) Tất cả phụ thuộc vào những gì bạn thực sự cần.
Wouter Overmeire

31

pd.concatchấp nhận một từ điển. Với suy nghĩ này, có thể cải thiện câu trả lời được chấp nhận hiện tại về tính đơn giản và hiệu suất bằng cách sử dụng khả năng hiểu từ điển để xây dựng các khóa ánh xạ từ điển đến các khung phụ.

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

Hoặc là,

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

4
Xuất sắc! Nhiều hơn :)
pg2455

3
Bạn sẽ làm thế nào nếu bạn vẫn còn một hạng mục bên trong nữa? Chẳng hạn như 12:{cat1:{cat11:{att1:val1,att2:val2}}}. Nói cách khác: làm thế nào một người có thể khái quát giải pháp cho một số danh mục không liên quan?
Lucas Aimaretto

1
@LucasAimaretto Thông thường có thể làm phẳng các cấu trúc lồng nhau tùy ý json_normalize. Tôi có một câu trả lời khác cho thấy nó hoạt động như thế nào.
cs95

1
Không hoạt động nếu vlà một số nguyên đơn lẻ chẳng hạn. Bạn có biết một giải pháp thay thế trong trường hợp như vậy?
sk

11

Vì vậy, tôi cũng đã từng sử dụng vòng lặp for để duyệt qua từ điển, nhưng một điều tôi thấy rằng hoạt động nhanh hơn nhiều là chuyển đổi sang bảng điều khiển và sau đó sang khung dữ liệu. Giả sử bạn có từ điển 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}}}

Lệnh

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

trong đó pd.Panel (d) [item] tạo ra khung dữ liệu

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

Sau đó, bạn có thể nhấn lệnh to_frame () để biến nó thành khung dữ liệu. Tôi cũng sử dụng reset_index để biến trục chính và trục nhỏ thành các cột thay vì đặt chúng dưới dạng chỉ số.

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

Cuối cùng, nếu bạn không thích giao diện của khung, bạn có thể sử dụng chức năng chuyển vị của bảng để thay đổi giao diện trước khi gọi to_frame () xem tài liệu tại đây http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html

Chỉ là một ví dụ

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

Hi vọng điêu nay co ich.


8
Bảng điều khiển không được dùng trong các phiên bản gấu trúc mới hơn (v0.23 tại thời điểm viết bài).
cs95

6

Trong trường hợp ai đó muốn lấy khung dữ liệu ở "định dạng dài" (các giá trị lá có cùng kiểu) mà không có đa chỉ mục, bạn có thể làm như sau:

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

(Tôi biết câu hỏi ban đầu có thể muốn (I.) có Cấp 1 và 2 dưới dạng đa chỉ mục và Cấp 3 dưới dạng cột và (II.) Hỏi về các cách khác ngoài việc lặp qua các giá trị trong chính tả. Nhưng tôi hy vọng câu trả lời này vẫn có liên quan và hữu ích (I.): với những người như tôi, những người đã cố gắng tìm cách để có được câu lệnh lồng vào hình dạng này và google chỉ trả về câu hỏi này và (II.): vì các câu trả lời khác cũng liên quan đến một số lần lặp lại và tôi tìm thấy điều này cách tiếp cận linh hoạt và dễ đọc; tuy nhiên, không chắc chắn về hiệu suất.)


0

Dựa trên câu trả lời đã xác minh, đối với tôi, điều này hoạt động tốt nhất:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.