Цель: прочитать данные из таблицы SQL, где столбец содержит JSON (массивы), извлечь определенные ключи / значения из JSON в новые столбцы, а затем записать в новую таблицу.Одна из радостей исходного формата данных заключается в том, что некоторые записи данных являются массивами JSON, а некоторые - не массивами (просто JSON).Таким образом, мы можем начать с :
testcase = [(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]),
(2, {'a': 30, 'b': 40}),
(3, {'a': 100, 'b': 200, 'd': 300})]
for x in testcase:
print(x)
(1, [{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}])
(2, {'a': 30, 'b': 40})
(3, {'a': 100, 'b': 200, 'd': 300})
Обратите внимание, что первый элемент каждого кортежа - это идентификатор записи.Первая запись - это массив длины два, вторая и третья записи не являются массивами. Желаемый вывод: (в виде DataFrame):
a b data
1 1 2 '{"c": 3}'
1 11 12 '{"c": 13}'
2 30 40 '{}'
3 100 200 '{"d": 300}'
Здесь вы можете видеть, что я извлек ключи 'a' и 'b' из диктов в новые столбцы, оставляяостальные ключи / значения на месте.Желаемое поведение - пустой dict для id = 2.
Сначала я выделил id и данные в отдельные списки.Я пользуюсь этой возможностью, чтобы превратить диктовку в список диктовок (длиной 1), чтобы типы теперь были согласованными:
id = [x[0] for x in testcase]
data_col = [x[1] if type(x[1]) == list else [x[1]] for x in testcase]
for x in data_col:
print(x)
[{'a': 1, 'b': 2, 'c': 3}, {'a': 11, 'b': 12, 'c': 13}]
[{'a': 30, 'b': 40}]
[{'a': 100, 'b': 200, 'd': 300}]
Это кажется немного неуклюжим дополнительным шагом для извлечения id и data_colкак отдельные списки, хотя, по крайней мере, у нас есть приятное свойство, что мы не копируем данные:
id[0] is testcase[0][0]
True
data_col[0] is testcase[0][1]
True
И, как я уже сказал, мне пришлось столкнуться с проблемой, что некоторые записи содержали массивы диктов инекоторые просто диктуют, так что это делает их всех непротиворечивыми.
Здесь происходит основная мелочь, где я выполняю понимание диктата в понимании двойного списка для итерации по каждому диктову:
popped = [(id, {key: element.pop(key, None) for key in ['a', 'b']}) \
for id, row in zip(id, data_col) for element in row]
for x in popped:
print(x)
(1, {'a': 1, 'b': 2})
(1, {'a': 11, 'b': 12})
(2, {'a': 30, 'b': 40})
(3, {'a': 100, 'b': 200})
Мне нужно иметь возможность связать каждую новую строку с ее исходным идентификатором, и вышеизложенное достигает этого, правильно воспроизводя соответствующее значение идентификатора (1, 1, 2, 3).После небольшой уборки я могу выстроить все целевые строки:
import pandas as pd
from psycopg2.extras import Json
id2 = [x[0] for x in popped]
cols = [x[1] for x in popped]
data = [Json(item) for sublist in data_col for item in sublist]
popped_df = pd.DataFrame(cols, index=id2)
popped_df['data'] = data
И это дает мне желаемый DataFrame, как показано выше.Но ... все ли мои шутки со списками необходимы?Я не мог сделать простой json_normalize, потому что я не хочу извлекать все ключи, и он падает с комбинацией массивов и не-массивов.
Он также должен быть настолько быстрым, насколько это возможно, насколько это возможнообрабатывать миллионы строк.По этой причине я на самом деле преобразую DataFrame в список с помощью: list (popped_df.itertuples ()), чтобы затем перейти к execute_values () в psycopg2.extras, так что я пока не буду беспокоиться о создании DataFrame и просто создаю список вывода, нов этом посте я действительно спрашиваю, есть ли более чистый и быстрый способ извлечения этих конкретных ключей из диктовок в новые столбцы и строки, устойчивый к тому, является ли запись массивом или нет, и отслеживание соответствующего идентификатора записи.
Я уклонялся от сквозного подхода к пандам, читая данные с помощью pd.read_sql (), поскольку я считывал, что DataFrame.to_sql () был относительно медленным.