Наиболее эффективный способ преобразования сложного списка словарей в Pandas Dataframe - PullRequest
0 голосов
/ 15 апреля 2020

У меня есть список словарей в event_records и подмножество списка ниже. Каждый словарь содержит 2 или 3 пары ключ-значение. Первый ключ - item, а соответствующее значение - event#status.

Второй ключ - count, и соответствующее значение состоит из словаря, содержащего 8 пар ключ-значение + 1 пара ключ-значение, где значение представляет собой список из 9 словарей, каждый из которых содержит 3 пары ключ-значение.

Третий ключ (присутствует только в некоторых случаях) - errors, а соответствующее значение представляет собой словарь с 3 парами ключ-значение в списке.

Какой самый эффективный способ преобразовать приведенный ниже список словарей в event_records в pandas кадр данных? Я попробовал следующий код, но скорость и производительность очень медленные.

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

Ответы [ 3 ]

0 голосов
/ 15 апреля 2020

Обработка данных здесь довольно элегантна благодаря pandas json_normalize & list понимание.

сначала извлекает пользовательские события

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

установить столбцы на 2-й уровень в мультииндексе и сбросить индекс информационного кадра, , и при желании вы можете изменить порядок столбцов

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
0 голосов
/ 15 апреля 2020

Я предлагаю создать три кадра данных и объединить их после. Кроме того, некоторые данные вложены в списки, вложены в dicts и вложены в списки. какое-то путешествие. Лично я использую библиотеку (jmespath), чтобы облегчить путешествие, и ИМХО, проще. как я сказал лично. здесь идет:

import jmespath
from collections import defaultdict

Сначала мы создадим фрейм данных для идентификаторов; вложение здесь не так глубоко, понимание списка (хорошо, понимание вложенного списка) должно сработать, плюс здесь более разумный подход:

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

Второй кадр данных для извлечения - это ' В виде'; это то место, где 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, например: dict:

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

и мы в конце - объединить кадры данных по столбцам:

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
0 голосов
/ 15 апреля 2020

Добавление к фреймам данных - медленный процесс, потому что каждое добавление воссоздает весь объект. В своем коде вы создаете 13 фреймов данных. Я рекомендую вам выполнить все форматирование вне объекта фрейма данных, а затем создать фрейм данных одним упором oop. Существует несколько способов создания фрейма данных (см. Эту страницу 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»), которая извлекает все соответствующие данные из записей событий и возвращает их в формат словаря (например, {"old_id": 123, "new_id": 345, "event_id": 567 ..."feedback": None}). Есть несколько причуд, на которые вы должны обратить внимание. Поскольку в некоторых записях отсутствуют ошибки, необходимо убедиться, что вы добавили «None» или -1 или другое значение, чтобы указать нулевое значение в этом столбце. В противном случае вы получите «Nan» в вашем столбце в pandas. Это займет немного утомительного кода, но будет намного быстрее, чем версия, в которой вы создаете 12 ненужных фреймов данных.

РЕДАКТИРОВАТЬ: уточненный код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...