从嵌套字典中的项目构造pandas DataFrame


90

假设我有一个嵌套的字典'user_dict',其结构为:

  • 级别1: UserId(长整数)
  • 级别2:类别(字符串)
  • 级别3:各种属性(浮点数,整数等)。

例如,该词典的条目为:

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

每个项目都user_dict具有相同的结构,并且user_dict包含大量项目,我希望将它们提供给pandas DataFrame,从而根据属性构造系列。在这种情况下,分层索引对于此目的将是有用的。

具体来说,我的问题是是否存在一种方法来帮助DataFrame构造函数理解应该从字典中“级别3”的值构建该系列的方法?

如果我尝试类似的方法:

df = pandas.DataFrame(users_summary)

“级别1”(用户ID)中的项目被视为列,这与我要实现的(将用户ID作为索引)相反。

我知道我可以在对字典条目进行迭代之后构造序列,但是如果有更直接的方法,这将非常有用。一个类似的问题是询问是否可以从文件中列出的json对象构造pandas DataFrame。


有关更简单的选择,请参见此答案
cs95

Answers:


138

大熊猫MultiIndex由元组列表组成。因此,最自然的方法是调整输入字典的形状,使其键为与所需的多索引值相对应的元组。然后,您可以使用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
有一种合理的方法可以将其推广到与任意深度参差不齐的列表一起使用吗?例如列表到任意深度,其中某些分支可能比其他分支短,而当较短的分支没有到达末尾时使用None或nan?
naught101

5
您是否看过pandas json支持(io工具)和规范化?pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire

1
对我而言,第一种方法创建了一个具有带元组的单个索引的数据框。第二种方法按预期/预期工作!
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

31

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
辉煌!好多了:)
pg2455 '19

3
如果您还有其他内部类别,该怎么办?如12:{cat1:{cat11:{att1:val1,att2:val2}}}。换句话说:某人如何将解决方案归纳为无关的类别数?
卢卡斯·艾玛雷托

1
@LucasAimaretto通常可以将任意嵌套的结构展平json_normalize。我有另一个答案,说明它是如何工作的。
cs95

1
v例如,如果是单个整数则不起作用。您知道在这种情况下的替代方法吗?
sk

11

因此,我过去也使用for循环遍历字典,但是我发现可以更快地工作的一件事是先转换为面板,然后转换为数据框。假设您有一本字典

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]产生一个数据帧

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()将其转换为数据帧。我也使用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)中不建议使用Panel。
cs95

6

如果有人想获取没有长索引的“长格式”(叶值具有相同类型)的数据帧,则可以执行以下操作:

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作为多索引,将Level 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.