【问题标题】:Load multi nested dict/json into pandas将多嵌套的 dict/json 加载到 pandas 中
【发布时间】:2019-06-24 14:48:02
【问题描述】:

我正在尝试将一个非常混乱的多嵌套JSON 加载到熊猫中。我已经在使用json_normalize,但试图弄清楚如何加入 2 个嵌套的 dicts 以及解压缩它们的子 dicts 和 lists 一直困扰着我。我对 pandas 的了解有限,但我假设我可以利用它的性能优势,如果我能做到这一点。

我有 2 个包含战争数据的字典,一个从 JSON API 响应加载,一个在数据库中。我正在尝试比较两者的新攻击和防御。

战争示例

{
  "state": "active",
  "team_size": 20,
  "teams": {
    "id": "12345679",
    "name": "Good Guys",
    "level": 10,
    "attacks": 4,
    "destruction_percentage": 22.6,
    "members": [
      {
        "id": "1",
        "name": "John",
        "level": 12
      },
      {
        "id": "2",
        "name": "Tom",
        "level": 11,
        "attacks": [
          {
            "attackerTag": "2",
            "defenderTag": "4",
            "damage": 64,
            "order": 7
          }
        ]
      }
    ]
  },
  "opponent": {
    "id": "987654321",
    "name": "Bad Guys",
    "level": 17,
    "attacks": 5,
    "damage": 20.95,
    "members": [
      {
        "id": "3",
        "name": "Betty",
        "level": 17,
        "attacks": [
          {
            "attacker_id": "3",
            "defender_id": "1",
            "damage": 70,
            "order": 1
          },
          {
            "attacker_id": "3",
            "defender_id": "7",
            "damage": 100,
            "order": 11
          }
        ],
        "opponentAttacks": 0,
        "some_useless_data": "Want to ignore, this doesn't show in every record"
      },
      {
        "id": "4",
        "name": "Fred",
        "level": 9,
        "attacks": [
          {
            "attacker_id": "4",
            "defender_id": "9",
            "damage": 70,
            "order": 4
          }
        ],
        "opponentAttacks": 0
      }
    ]
  }
}

现在我假设 pandas 将是我最好的选择,就性能而言,而不是将它们压缩在一起并循环遍历每个成员并进行比较。

因此,至少可以说,我试图获得一个平整且易于遍历的dataframe 很难。最好我会假设以下布局。我只是想让两个团队都成为一个df 的所有成员。 我们可以省略 stateteam_size 键,专注于获取每个成员及其各自的 attacksteam_id's

示例 df (预期,结果)

id   name   level  attacks         member.team_id  ...
1    John   12     NaN             "123456789"
2    Tom    11     [{...}]         "123456789"
3    Betty  17     [{...}, {...}]  "987654321"
4    Fred   9      [{...}]         "987654321"

这就是我想要df 的基本要点。因此,我可以获取两个数据帧并比较新的攻击。

注意 在我尝试之前,我只是 pop()'d stateteam_size 来自字典,因为我想要的只是所有成员,团队几乎都嵌入其中

我尝试了以下方法,但没有运气,我知道这不是正确的方法,因为它在 dict 树上向后工作。

old_df = json_normalize(war,
                        'members',
                        ['id', 'name', 'level', 'attacks'],
                        record_prefix='member')

#Traceback (most recent call last):
#  File "test.py", line 83, in <module>
#    new_parse(old_war, new_war)
#  File "test.py", line 79, in new_parse
#    record_prefix='member')
#  File "/home/jbacher/.local/lib/python3.7/site-packages/pandas/io/json/normalize.py", line 262, in json_normalize
#    _recursive_extract(data, record_path, {}, level=0)
#  File "/home/jbacher/.local/lib/python3.7/site-packages/pandas/io/json/normalize.py", line 238, in _recursive_extract
#    recs = _pull_field(obj, path[0])
#  File "/home/jbacher/.local/lib/python3.7/site-packages/pandas/io/json/normalize.py", line 185, in _pull_field
#    result = result[spec]
#KeyError: 'members'

我以为我可以使用类似下面的东西,但这也不起作用。

df = pd.DataFrame.from_dict(old, orient='index')
df.droplevel('members')

#Traceback (most recent call last):
#  File "test.py", line 106, in <module>
#    new_parse(old_war, new_war)
#  File "test.py", line 87, in new_parse
#    df.droplevel('members')
#  File "/home/jbacher/.local/lib/python3.7/site-packages/pandas/core/generic.py", line 4376, in __getattr__
#    return object.__getattribute__(self, name)
#AttributeError: 'DataFrame' object has no attribute 'droplevel'

感谢任何指导!希望我投入足够的精力来帮助理解我的预期结果,如果没有,请告诉我!

编辑 公平地说,我确实知道如何做到这一点,只需循环 dict 并创建一个具有适当日期的新成员列表,但我觉得这比使用 pandas 效率低得多,因为我正在为数百万次战争这样做线程应用程序和我可以从中获得的每一点性能对我和应用程序来说都是一个奖励。 - 再次感谢!

【问题讨论】:

  • 是否可以创建有效的 json 样本数据?
  • 这是一个有趣的问题,但正如 jezrael 所说,您应该提供一个您希望我们使用的 json。不只是一个伪的。--我敢打赌,尝试规范化每个团队的成员列表,并附加 2 个 dfs。我在我的手机上,但你可以在这里找到一些关于如何使用 json_normalize() 的帖子
  • 我更正了,我取出了一些东西,这样做我破坏了 JSON 格式。还从中删除了...。只知道一个团队中显然不止 2 个成员。哈哈对不起
  • 我看到了,它有我现在需要开始的东西,只需要应用于我的实际应用程序。我放置了虚拟 json,但与我需要的非常相似。谢谢!
  • 大约 30 分钟前我给你发了一封电子邮件

标签: python json pandas dictionary


【解决方案1】:

我相信你可以使用:

need = ['member.id', 'member.name', 'member.level', 'member.attacks','id']
df1 = json_normalize(war['teams'],
                     'members',
                     ['id', 'name', 'level', 'attacks'], 
                     record_prefix='member.')[need]
#print (df1)

df2 = json_normalize(war['opponent'],
                     'members',
                     ['id', 'name', 'level', 'attacks'], 
                     record_prefix='member.')[need]
#print (df2)


df1.columns = np.where(df1.columns.str.startswith('member.'), 
                       df1.columns.str.split('.', n=1).str[1],
                       'member.' + df1.columns)
df2.columns = np.where(df2.columns.str.startswith('member.'), 
                       df2.columns.str.split('.', n=1).str[1],
                       'member.' + df2.columns)


df = pd.concat([df1, df2], sort=False, ignore_index=True)
print (df)
  id   name  level                                            attacks  \
0  1   John     12                                                NaN   
1  2    Tom     11  [{'attackerTag': '2', 'defenderTag': '4', 'dam...   
2  3  Betty     17  [{'attacker_id': '3', 'defender_id': '1', 'dam...   
3  4   Fred      9  [{'attacker_id': '4', 'defender_id': '9', 'dam...   

   member.id  
0   12345679  
1   12345679  
2  987654321  
3  987654321  

【讨论】:

  • member.team_id 在哪里,也不是 OP 想要的输出。
  • @U9-Forward - 交换了 member_ 没有 prefix 列名。如果有必要,我可以改变它。但首先我想等待我的解决方案是否正常工作。
  • 是的,这行得通,虽然就像@U9-Forward 所说的team 在哪里发挥作用?就像我如何将它添加为至少只是代表id 的列以及如何删除我不需要的数据?
  • @Jaba - 所以预期输出中的... id name level attacks member.team_id ... 有必要忽略吗?
  • 谢谢!这是天赐之物。我将尝试对此进行计时,但我现在要说这比我原来的方法要快得多。哈哈,非常感谢!
【解决方案2】:

尝试使用这个四线:

d=war['teams']['members']+war['teams']['opponent']['members']
df = pd.DataFrame(d)
df = df.iloc[:,:4][['id','name','level','attacks']]
df['member.team_id']=[war['teams']['opponent']['id'] if i in war['teams']['opponent']['members'] else war['teams']['id'] for i in d]
print(df)

输出:

  id   name  level                                            attacks  \
0  1   John     12                                                NaN   
1  2    Tom     11  [{'attackerTag': '2', 'defenderTag': '4', 'dam...   
2  3  Betty     17  [{'attacker_id': '3', 'defender_id': '1', 'dam...   
3  4   Fred      9  [{'attacker_id': '4', 'defender_id': '9', 'dam...   

  member.team_id  
0       12345679  
1       12345679  
2      987654321  
3      987654321  

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-04
    • 2018-11-28
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    相关资源
    最近更新 更多