สร้าง DataFrame แพนด้าจากรายการในพจนานุกรมที่ซ้อนกัน


90

สมมติว่าฉันมีพจนานุกรม 'user_dict' ที่ซ้อนกันพร้อมโครงสร้าง:

  • ระดับ 1: UserId (จำนวนเต็มยาว)
  • ระดับ 2:หมวดหมู่ (สตริง)
  • ระดับ 3:แอตทริบิวต์สารพัน (ลอย ints ฯลฯ .. )

ตัวอย่างเช่นรายการของพจนานุกรมนี้จะเป็น:

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

แต่ละรายการในuser_dictมีโครงสร้างที่เหมือนกันและuser_dictมีรายการจำนวนมากที่ฉันต้องการป้อนให้กับ DataFrame แพนด้าโดยสร้างชุดจากแอตทริบิวต์ ในกรณีนี้ดัชนีลำดับชั้นจะเป็นประโยชน์สำหรับวัตถุประสงค์

โดยเฉพาะอย่างยิ่งคำถามของฉันคือมีวิธีที่จะช่วยให้ตัวสร้าง DataFrame เข้าใจหรือไม่ว่าชุดข้อมูลควรสร้างจากค่าของ "ระดับ 3" ในพจนานุกรม

ถ้าฉันลองทำสิ่งที่ชอบ:

df = pandas.DataFrame(users_summary)

รายการใน "ระดับ 1" (UserId's) ถูกนำมาเป็นคอลัมน์ซึ่งตรงข้ามกับสิ่งที่ฉันต้องการบรรลุ (มี UserId เป็นดัชนี)

ฉันรู้ว่าฉันสามารถสร้างซีรีส์นี้ได้หลังจากทำซ้ำรายการพจนานุกรม แต่ถ้ามีวิธีที่ตรงกว่านี้จะมีประโยชน์มาก คำถามที่คล้ายกันคือถามว่าสามารถสร้าง DataFrame แพนด้าจากวัตถุ json ที่แสดงรายการในไฟล์ได้หรือไม่


ดูคำตอบนี้สำหรับทางเลือกอื่นที่ง่ายกว่า
cs95

คำตอบ:


141

MultiIndex ของแพนด้าประกอบด้วยรายการทูเปิล ดังนั้นวิธีการที่เป็นธรรมชาติที่สุดคือการกำหนดรูปแบบการป้อนข้อมูลของคุณใหม่เพื่อให้คีย์เป็นสิ่งที่สอดคล้องกับค่าหลายดัชนีที่คุณต้องการ จากนั้นคุณสามารถสร้าง dataframe โดยpd.DataFrame.from_dictใช้ตัวเลือก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

อีกทางเลือกหนึ่งคือการสร้างดาต้าเฟรมของคุณขึ้นมาโดยการเชื่อมต่อเฟรมข้อมูลของคอมโพเนนต์:

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
มีวิธีที่สมเหตุสมผลในการสรุปสิ่งนี้เพื่อทำงานกับรายการที่มีความลึกตามอำเภอใจหรือไม่ เช่นรายการตามความลึกตามอำเภอใจโดยที่บางสาขาอาจสั้นกว่าสาขาอื่นและไม่มีการใช้นาโนหรือนาโนเมื่อกิ่งที่สั้นลงไปไม่ถึงจุดสิ้นสุด?
naught101

5
คุณได้ดูการสนับสนุนแพนด้า json (เครื่องมือ io) และการทำให้เป็นมาตรฐานหรือไม่? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire

1
สำหรับฉันวิธีแรกสร้าง dataframe ด้วยดัชนีเดียวที่มี tuples วิธีที่สองทำงานได้ตามต้องการ / คาดหวัง!
arturomp

มีเคล็ดลับในการตั้งชื่อคอลัมน์ใหม่เหล่านี้อย่างไร ตัวอย่างเช่นถ้าฉันต้องการให้ตัวเลข 12 และ 15 เหล่านี้อยู่ในคอลัมน์ 'id'
cheremushkin

1
ตอนนี้ @cheremushkin 12 และ 15 อยู่ในแถว 'id' ถ้าคุณเปลี่ยน ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) จะอยู่ในคอลัมน์ 'id' คุณยังสามารถปลดสแต็คได้ ( pandas.pydata.org/pandas-docs/stable/reference/api/… ) ทุกอย่างขึ้นอยู่กับสิ่งที่คุณต้องการจริงๆ
Wouter Overmeire

33

pd.concatยอมรับพจนานุกรม ด้วยเหตุนี้จึงเป็นไปได้ที่จะปรับปรุงคำตอบที่ยอมรับในปัจจุบันในแง่ของความเรียบง่ายและประสิทธิภาพโดยใช้ความเข้าใจในพจนานุกรมเพื่อสร้างคีย์การแมปพจนานุกรมให้กับเฟรมย่อย

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

หรือ,

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
ยอดเยี่ยม! ดีกว่า
เยอะ

3
คุณจะทำอย่างไรถ้าคุณยังมีหมวดหมู่ภายในเพิ่มเติม เช่น12:{cat1:{cat11:{att1:val1,att2:val2}}}. กล่าวอีกนัยหนึ่ง: บางคนจะสรุปวิธีแก้ปัญหาเป็นจำนวนหมวดหมู่ที่ไม่เกี่ยวข้องได้อย่างไร
Lucas Aimaretto

1
@LucasAimaretto โดยปกติแล้วโครงสร้างที่ซ้อนกันโดยพลการสามารถแบนด้วยjson_normalizeไฟล์. ฉันมีคำตอบอื่นซึ่งแสดงให้เห็นว่ามันทำงานอย่างไร
cs95

1
ใช้ไม่ได้หากvเป็นจำนวนเต็มเดียวเช่น คุณรู้ทางเลือกในกรณีเช่นนี้หรือไม่?
sk

11

ดังนั้นฉันจึงใช้ for loop ในการวนซ้ำผ่านพจนานุกรมเช่นกัน แต่สิ่งหนึ่งที่ฉันพบว่าทำงานได้เร็วกว่ามากคือการแปลงเป็นพาเนลจากนั้นเป็น dataframe สมมติว่าคุณมีพจนานุกรมง

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}}}

คำสั่ง

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

โดยที่ pd.Panel (d) [item] ให้ 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

จากนั้นคุณสามารถกดคำสั่ง to_frame () เพื่อเปลี่ยนเป็น dataframe ฉันใช้ reset_index เพื่อเปลี่ยนแกนหลักและแกนรองให้เป็นคอลัมน์แทนที่จะให้เป็นดัชนี

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

สุดท้ายหากคุณไม่ชอบรูปลักษณ์ของเฟรมคุณสามารถใช้ฟังก์ชันทรานสโพสของพาเนลเพื่อเปลี่ยนรูปลักษณ์ก่อนที่จะเรียก to_frame () ดูเอกสารที่นี่ http://pandas.pydata.org/pandas-docs/dev/generated /pandas.Panel.transpose.html

เป็นเพียงตัวอย่าง

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

หวังว่านี่จะช่วยได้


8
พาเนลเลิกใช้งานในเวอร์ชันล่าสุดของแพนด้า (v0.23 ในขณะที่เขียน)
cs95

6

ในกรณีที่มีคนต้องการรับ data frame ใน "รูปแบบยาว" (ค่า leaf มีประเภทเดียวกัน) โดยไม่มี multiindex คุณสามารถทำได้:

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

(ฉันรู้ว่าคำถามเดิมอาจต้องการให้ (I. ) มีระดับ 1 และ 2 เป็นหลายดัชนีและระดับ 3 เป็นคอลัมน์และ (II.) ถามเกี่ยวกับวิธีอื่นนอกเหนือจากการทำซ้ำค่าในคำสั่ง แต่ฉันหวังว่าคำตอบนี้จะยังคงเกี่ยวข้อง และมีประโยชน์ (I. ): สำหรับคนอย่างฉันที่พยายามหาวิธีทำให้ dict ที่ซ้อนกันเป็นรูปร่างนี้และ google จะส่งคืนคำถามนี้เท่านั้นและ (II.): เนื่องจากคำตอบอื่น ๆ เกี่ยวข้องกับการทำซ้ำด้วยและฉันพบสิ่งนี้ แนวทางที่ยืดหยุ่นและอ่านง่าย แต่ไม่แน่ใจเกี่ยวกับประสิทธิภาพ)


0

จากคำตอบที่ยืนยันแล้วสำหรับฉันสิ่งนี้ได้ผลดีที่สุด:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.