【问题标题】:Most Efficient Way to Convert a Complex List of Dictionaries Into a Pandas Dataframe将复杂的字典列表转换为 Pandas 数据框的最有效方法
【发布时间】:2020-07-27 21:03:42
【问题描述】:

我在event_records 中有一个字典列表,下面是该列表的一个子集。每个字典包含 2 或 3 个键值对。第一个键是item,对应的值是event#status

第二个键是count,对应的值由一个包含 8 个键值对 + 1 个键值对的字典组成,其中值是 9 个字典的列表,每个字典包含 3 个键值对。

第三个键(仅在某些时候出现)是errors,对应的值是一个字典,列表中有 3 个键值对。

event_records 中的以下字典列表转换为熊猫数据框的最有效方法是什么?我试过下面的代码,但是速度和性能都很慢。

from pandas.io.json import json_normalize
import pandas as pd

df1 = json_normalize(event_records)
df2 = df1['customEvents']
custom_events_list = []
for element in df2: 
    df3 = json_normalize(element)
    df4 = df3[['type', 'value']]
    df5 = df4.T
    df5.columns = df5.iloc[0]
    df5 = df5[1:]
    custom_events_list.append(df5)
df6 = pd.concat(custom_events_list)
df6 = df6.reset_index(drop = True)
df7 = df1.join(df6)

df8 = df1['errors']
event_error_list = []
for element in df8: 
    df9 = json_normalize(element)
    df10 = df9[['response', 'feedback']]
    event_error_list.append(df10)
df11 = pd.concat(event_error_list)
df11 = df11.reset_index(drop = True)
df12 = df7.join(df11)
df13 = df12[['old_id', 'new_id', 'event_id', 'event_time', 'value', 'quantity', 'unique_id', 'A3', 'A4', 'A6', 'A9', 'A10', 'A11', 'A12', 'A13', 'A14', 'response', 'feedback']]

event_records = [{'item': 'event#status',
  'count': {'item': 'event#count',
   'old_id': '123',
   'new_id': '456',
   'event_id': '111',
   'event_time': '1200',
   'value': 1.0,
   'quantity': '1',
   'unique_id': '222',
   'customEvents': [{'item': 'event#custom', 'type': 'A3', 'value': ''},
    {'item': 'event#custom', 'type': 'A4', 'value': '11AA'},
    {'item': 'event#custom', 'type': 'A6', 'value': 'AAB1'},
    {'item': 'event#custom', 'type': 'A9', 'value': ''},
    {'item': 'event#custom', 'type': 'A10', 'value': '10.5'},
    {'item': 'event#custom', 'type': 'A11', 'value': 'ABC'},
    {'item': 'event#custom', 'type': 'A12', 'value': 'NYR'},
    {'item': 'event#custom', 'type': 'A13', 'value': 'NYR'},
    {'item': 'event#custom', 'type': 'A14', 'value': 'NYR'}]},
  'errors': [{'item': 'event#Error',
    'response': 'NONE',
    'feedback': 'Event not found'}]},
 {'item': 'event#status',
  'count': {'item': 'event#count',
   'old_id': '567',
   'new_id': '789',
   'event_id': '333',
   'event_time': '1400',
   'value': 1.0,
   'quantity': '1',
   'unique_id': '444',
   'customEvents': [{'item': 'event#custom', 'type': 'A3', 'value': ''},
    {'item': 'event#custom', 'type': 'A4', 'value': '22BB'},
    {'item': 'event#custom', 'type': 'A6', 'value': 'CCD1'},
    {'item': 'event#custom', 'type': 'A9', 'value': ''},
    {'item': 'event#custom', 'type': 'A10', 'value': '20.5'},
    {'item': 'event#custom', 'type': 'A11', 'value': 'ABC'},
    {'item': 'event#custom', 'type': 'A12', 'value': 'NYR'},
    {'item': 'event#custom', 'type': 'A13', 'value': 'NYR'},
    {'item': 'event#custom', 'type': 'A14', 'value': 'NYR'}]}}]

所需的 Pandas 数据帧输出如下:

old_id    new_id    event_id    event_time    value    quantity    unique_id    A3    A4    A6    A9    A10    A11    A12    A13    A14    response    feedback
123       456       111         1200          1.0      1           222                11AA  AAB1        10.5   ABC    NYR    NYR    NYR    NONE        Event not found
567       789       333         1400          1.0      1           444                22BB  CCD1        20.5   ABC    NYR    NYR    NYR

【问题讨论】:

  • 请检查您的 event_records 数据,它似乎没有正确格式化
  • 谢谢。我更新了 event_records,现在格式正确。

标签: python python-3.x pandas dataframe dictionary


【解决方案1】:

添加到数据帧是一个缓慢的过程,因为每次添加都会重新创建整个对象。在您的代码中,您创建 13 个数据框。我建议您在数据框对象之外进行所有格式化,然后一举创建数据框。有多种方法可以创建数据框(请参阅 geeks for geeks page 获取一些示例),您可以选择最适合您的方法

对我来说最快的方法是遍历事件记录列表,如下所示:

processed_records = []
for event_record in event_records:
    processed_records.append(process_record(event_record))

df = pd.DataFrame(processed_records)

然后您需要编写一个名为“process_record”的函数,该函数从事件记录中提取所有相关数据并以字典格式返回(例如{"old_id": 123, "new_id": 345, "event_id": 567 ..."feedback": None})。您必须注意一些怪癖。因为有些记录没有错误,所以您需要确保添加“None”或-1 或其他一些值来指示此列上的空值。否则,您将在 pandas 的列中得到一个“Nan”。这将需要一些乏味的代码,但它会比创建 12 个不必要的数据帧的版本快得多。

编辑:澄清代码

【讨论】:

    【解决方案2】:

    感谢 pandas json_normalize 和列表理解,这里的数据处理非常优雅。

    首先提取自定义事件

    parent_fields = ['old_id', 'new_id', 'event_id', 'event_time', 'value', 'quantity', 'unique_id']
    custom_events = json_normalize(
        [r['count'] for r in event_records], 
        'customEvents',
        parent_fields,
        record_prefix='#'
    )
    

    然后提取errors。在这里,我使用了一个鲜为人知的列表推导功能,它允许对嵌套元素进行过滤和迭代,以生成输入 DataFrame 构造函数的记录

    errors = pd.DataFrame(
       [(e['response'], e['feedback'],r['count']['unique_id'])
        for r in event_records if 'errors' in r
        for e in r['errors']], 
       columns=['response', 'feedback', 'unique_id'])
    

    合并两个数据框

    df = custom_events.merge(
        errors, 
        left_on='unique_id', 
        right_on='unique_id',
        how='left'
    )
    shaped = df.set_index(
        [c for c in df.columns if c != '#value']
    ).unstack('#type')
    

    此时,shaped 是具有所需形状的数据框,但列仍然是多索引而不是平面列表。

    #shaped outputs:
                                                                                        #value
    #type                                                                                  A10  A11  A12  A13  A14 A3    A4    A6 A9
    old_id new_id event_id event_time value quantity unique_id response feedback
    123    456    111      1200       1.0   1        222       NONE     Event not found   10.5  ABC  NYR  NYR  NYR     11AA  AAB1
    567    789    333      1400       1.0   1        444       NaN      NaN               20.5  ABC  NYR  NYR  NYR     22BB  CCD1
    

    在多索引中将列设置为第二级并重置数据框的索引,如果您愿意,可以重新排序列

    shaped.columns = shaped.columns.levels[1]
    shaped.reset_index()
    # outputs:
    #type old_id new_id event_id event_time  value quantity unique_id response         feedback   A10  A11  A12  A13  A14 A3    A4    A6 A9
    0        123    456      111       1200    1.0        1       222     NONE  Event not found  10.5  ABC  NYR  NYR  NYR     11AA  AAB1
    1        567    789      333       1400    1.0        1       444      NaN              NaN  20.5  ABC  NYR  NYR  NYR     22BB  CCD1
    

    【讨论】:

      【解决方案3】:

      我建议我们创建三个数据框并在之后连接。此外,一些数据嵌套在列表中,嵌套在字典中,嵌套在列表中。一段旅程。就个人而言,我使用库(jmespath)使旅程更轻松,恕我直言,更简单。正如我所说,就个人而言。这里是:

      import jmespath
      from collections import defaultdict
      

      首先我们为 ids 创建数据框;这里的嵌套不是那么深,列表推导(以及嵌套列表推导)应该可以解决问题,而且在这里它是一种更明智的方法:

      df1 = pd.DataFrame({key:value 
                          for key,value 
                          in entry['count'].items()
                          if key not in  ('customEvents','item')} 
                         for entry in event_records)
      
      df1
      
        old_id    new_id  event_id    event_time  value   quantity    unique_id
      0   123     456     111           1200       1.0       1         222
      1   567     789     333           1400       1.0       1         444
      

      要提取的第二个数据帧是“As”;这就是jmespath 发挥作用的地方,因为它允许轻松遍历嵌套列表/字典。你可以在这里写一个嵌套列表理解,但是 jmespath 可以让我们避免这种嵌套:

      customEvents 的路径是:list -> dict -> count -> customEvents -> list<br> 在 jmespath 中通过点 (.) 符号访问键,而通过方括号 ([]) 符号访问列表

      As =jmespath.compile('[].count.customEvents[]')
      out = As.search(event_records)
      
      print(out)
      
      [{'item': 'event#custom', 'type': 'A3', 'value': ''},
       {'item': 'event#custom', 'type': 'A4', 'value': '11AA'},
       {'item': 'event#custom', 'type': 'A6', 'value': 'AAB1'},
       {'item': 'event#custom', 'type': 'A9', 'value': ''},
       {'item': 'event#custom', 'type': 'A10', 'value': '10.5'},
       {'item': 'event#custom', 'type': 'A11', 'value': 'ABC'},
       {'item': 'event#custom', 'type': 'A12', 'value': 'NYR'},
       {'item': 'event#custom', 'type': 'A13', 'value': 'NYR'},
       {'item': 'event#custom', 'type': 'A14', 'value': 'NYR'},
       {'item': 'event#custom', 'type': 'A3', 'value': ''},
       {'item': 'event#custom', 'type': 'A4', 'value': '22BB'},
       {'item': 'event#custom', 'type': 'A6', 'value': 'CCD1'},
       {'item': 'event#custom', 'type': 'A9', 'value': ''},
       {'item': 'event#custom', 'type': 'A10', 'value': '20.5'},
       {'item': 'event#custom', 'type': 'A11', 'value': 'ABC'},
       {'item': 'event#custom', 'type': 'A12', 'value': 'NYR'},
       {'item': 'event#custom', 'type': 'A13', 'value': 'NYR'},
       {'item': 'event#custom', 'type': 'A14', 'value': 'NYR'}]
      

      接下来,我们使用defaultdict 选项来提取我们的类型和值键

      d = defaultdict(list)
      
      for i in out:
          d[i['type']].append(i['value'])
      
      print(d)
      
      defaultdict(list,
                  {'A3': ['', ''],
                   'A4': ['11AA', '22BB'],
                   'A6': ['AAB1', 'CCD1'],
                   'A9': ['', ''],
                   'A10': ['10.5', '20.5'],
                   'A11': ['ABC', 'ABC'],
                   'A12': ['NYR', 'NYR'],
                   'A13': ['NYR', 'NYR'],
                   'A14': ['NYR', 'NYR']})
      

      将其读入数据框:

      df2 = pd.DataFrame(d)
      df2
      
        A3     A4      A6     A9  A10     A11 A12 A13 A14
      0       11AA    AAB1        10.5    ABC NYR NYR NYR
      1       22BB    CCD1        20.5    ABC NYR NYR NYR
      

      第三部分是提取错误数据: 列表的[] 和键的. 的相同概念也适用于此;但是,我们可以在 key:value 对中取回我们的数据,就像字典一样:

      errors = jmespath.compile('[].errors[].{response:response,feedback:feedback}')
      err = errors.search(event_records)
      
      print(err)
      
      [{'response': 'NONE', 'feedback': 'Event not found'}]
      

      读入数据框:

      df3 = pd.DataFrame(err)
      df3
      
          response    feedback
      0   NONE    Event not found
      

      我们在最后 - concatenate 列上的数据框:

      result = pd.concat([df1,df2,df3],axis = 1)
      
      
      
         old_id  new_id   event_id    event_time  value   quantity    unique_id   A3  A4  A6  A9  A10 A11 A12 A13 A14 response    feedback
      0   123     456      111         1200       1.0         1       222            11AA AAB1        10.5    ABC NYR NYR NYR NONE    Event not found
      1   567     789     333         1400        1.0         1       444           22BB  CCD1        20.5    ABC NYR NYR NYR NaN NaN
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-29
        • 2021-04-21
        • 2022-01-07
        相关资源
        最近更新 更多