My deep learning

Как преобразовать вложенные структуры JSON в массив Pandas на Python

Наиболее часто встречаемая проблема — это вложенные структуры данных, которые поставляются в JSON формате по API. Пример таких структур можно посмотреть в статье подготовка и оптимизация данных для задач машинного обучения. В большинстве случаев требуются дополнительные действия для того, чтобы развернуть вложения в двумерном виде. В этой статье я покажу как это сделать.

Вложенные структуры JSON с которыми придется повозиться

Попробуем поработать с данными на примере публичного API Государственной Думы Российской Федерации. Сервис предоставляет сведения о членах обеих палат и законотворческой деятельности.

Вот пример ответа по запросу о списке депутатов и сенаторов (в данном случае :token и :app_token заменяются на ключи в реальном запросе):

import requests

json_object = requests.get('http://api.duma.gov.ru/api/:token/deputies.json?app_token=:app_token')

Что содержалось в ответе?

...
{'id': '99100491',
  'name': 'Абдулатипов Рамазан Гаджимурадович',
  'position': 'Депутат ГД',
  'isCurrent': False,
  'factions': [{'id': '72100020',
    'name': 'Депутатская группа "Российские регионы"',
    'startDate': '1996-01-16',
    'endDate': '1997-11-12'},
   {'id': '72100024',
    'name': 'Фракция Всероссийской политической партии "ЕДИНАЯ РОССИЯ"',
    'startDate': '2011-12-04',
    'endDate': '2013-01-27'}]}
...

В этом примере мы наблюдаем вложенный список по ключу factions, в который помещены два словаря, агрегирующих данные о вхождении депутата во фракции. Такое вложение развернуть сразу не получится. Ситуация осложняется еще и тем, что по части депутатов ключ factions отсутствует.

Как преобразовать json в Pandas?

Мы можем разобрать json-объект с помощью pandas.json_normalize — метода, который разбирает структурированные данные из JSON в табличный формат, а также json.load, который десериализует текст или байткод, содержащий json-документ в python-объекты. Применим к нашему json-объекта.

import numpy as np
import pandas as pd
import json

extracted = pd.io.json.json_normalize(json_object.json())

Как и предполагалось, данные по factions остались неразвернутыми.

id name position isCurrent factions
99100491 Абдулатипов Рамазан Гаджимурадович Депутат ГД False [{‘id’: ‘72100020’, ‘name’: ‘Депутатская группа “Российские регионы”’, ‘startDate’: ‘1996-01-16’, ‘endDate’: ‘1997-11-12’}, {‘id’: ‘72100024’, ‘name’: ‘Фракция Всероссийской политической партии “ЕДИНАЯ РОССИЯ”’, ‘startDate’: ‘2011-12-04’, ‘endDate’: ‘2013-01-27’}]

Что бы разобрать вложенный список из factions для начала избавимся от пропусков

factions_with_id = json_object.json()
for i in factions_with_id:
    is_factions = i.get('factions')
    if not is_factions:
        factions_with_id.remove(i)

Теперь развернем factions относительно айдишников депутатов (meta=’id’). Чтобы сохранить семантику в полученной таблице, будем использовать аргумент record_prefix=’factions.’, который добавим к вновь образуемым столбцам массива.

factions_with_id = pd.io.json.json_normalize(factions_with_id,
                                             record_path='factions',
                                             meta='id',
                                             record_prefix='factions.',
                                             errors="ignore")

Нам удалось развернуть список словарей в полноценную таблицу:

factions_with_id.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5031 entries, 0 to 5030
Data columns (total 5 columns):
factions.id           5031 non-null object
factions.name         5031 non-null object
factions.startDate    5031 non-null object
factions.endDate      5031 non-null object
id                    5031 non-null object
dtypes: object(5)
memory usage: 196.6+ KB

Теперь в исходном массиве удалим колонку factions и с помощью метода merge() добавим в массив развернутые данные.

extracted.drop(['factions'], axis='columns', inplace=True)
summary = extracted.merge(factions_with_id, how='outer')
summary.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 5703 entries, 0 to 5702
Data columns (total 8 columns):
id                    5703 non-null object
name                  5703 non-null object
position              5703 non-null object
isCurrent             5703 non-null bool
factions.id           5031 non-null object
factions.name         5031 non-null object
factions.startDate    5031 non-null object
factions.endDate      5031 non-null object
dtypes: bool(1), object(7)
memory usage: 362.0+ KB

Теперь все хорошо — данные извлечены и представлены в удобном, с точки зрения семантики, табличном виде:

id name position isCurrent factions.id factions.name factions.startDate factions.endDate
99100491 Абдулатипов Рамазан Гаджимурадович Депутат ГД False 72100020 Депутатская группа “Российские регионы” 1996-01-16 1997-11-12
99100491 Абдулатипов Рамазан Гаджимурадович Депутат ГД False 72100024 Фракция Всероссийской политической партии “ЕДИНАЯ РОССИЯ” 2011-12-04 2013-01-27

Все статьи с тегом preprocessing

Этот проект поддерживается KonstantinKlepikov