繁体   English   中英

将复杂的字典列表转换为 Pandas Dataframe 的最有效方法

[英]Most Efficient Way to Convert a Complex List of Dictionaries Into a Pandas Dataframe

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

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

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

event_records中的以下字典列表转换为 pandas dataframe 的最有效方法是什么? 我尝试了以下代码,但是速度和性能都很慢。

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 dataframe output如下:

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

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

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

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

df = pd.DataFrame(processed_records)

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

编辑:澄清代码

由于 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')

此时,shape 是具有所需shaped的 dataframe,但是列仍然是多索引而不是平面列表。

#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

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

import jmespath
from collections import defaultdict

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

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

拔出的第二个 dataframe 是“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']})

将其读入 dataframe:

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对中取回我们的数据,例如 dict:

errors = jmespath.compile('[].errors[].{response:response,feedback:feedback}')
err = errors.search(event_records)

print(err)

[{'response': 'NONE', 'feedback': 'Event not found'}]

读入 dataframe:

df3 = pd.DataFrame(err)
df3

    response    feedback
0   NONE    Event not found

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM