Как json_normalize столбец в pandas с пустыми списками, без потери записей

#python #pandas #dictionary #json-normalize

#python #панды #словарь #json-нормализовать #pandas

Вопрос:

Я использую pd.json_normalize , чтобы разбить "sections" поле в этих данных на строки. Это работает нормально, за исключением строк, где "sections" это пустой список.

Этот идентификатор полностью игнорируется и отсутствует в конечном сплющенном фрейме данных. Мне нужно убедиться, что у меня есть хотя бы одна строка для каждого уникального идентификатора в данных (некоторые идентификаторы могут содержать много строк до одной строки для каждого уникального идентификатора, для каждого уникального section_id , question_id и answer_id по мере того, как я добавляю больше полей в данные):

      {'_id': '5f48f708fe22ca4d15fb3b55',
      'created_at': '2020-08-28T12:22:32Z',
      'sections': []}]
  

Примерные данные:

 sample = [{'_id': '5f48bee4c54cf6b5e8048274',
          'created_at': '2020-08-28T08:23:00Z',
          'sections': [{'comment': '',
            'type_fail': None,
            'answers': [{'comment': 'stuff',
              'feedback': [],
              'value': 10.0,
              'answer_type': 'default',
              'question_id': '5e59599c68369c24069630fd',
              'answer_id': '5e595a7c3fbb70448b6ff935'},
             {'comment': 'stuff',
              'feedback': [],
              'value': 10.0,
              'answer_type': 'default',
              'question_id': '5e598939cedcaf5b865ef99a',
              'answer_id': '5e598939cedcaf5b865ef998'}],
            'score': 20.0,
            'passed': True,
            '_id': '5e59599c68369c24069630fe',
            'custom_fields': []},
           {'comment': '',
            'type_fail': None,
            'answers': [{'comment': '',
              'feedback': [],
              'value': None,
              'answer_type': 'not_applicable',
              'question_id': '5e59894f68369c2398eb68a8',
              'answer_id': '5eaad4e5b513aed9a3c996a5'},
             {'comment': '',
              'feedback': [],
              'value': None,
              'answer_type': 'not_applicable',
              'question_id': '5e598967cedcaf5b865efe3e',
              'answer_id': '5eaad4ece3f1e0794372f8b2'},
             {'comment': "stuff",
              'feedback': [],
              'value': 0.0,
              'answer_type': 'default',
              'question_id': '5e598976cedcaf5b865effd1',
              'answer_id': '5e598976cedcaf5b865effd3'}],
            'score': 0.0,
            'passed': True,
            '_id': '5e59894f68369c2398eb68a9',
            'custom_fields': []}]},
         {'_id': '5f48f708fe22ca4d15fb3b55',
          'created_at': '2020-08-28T12:22:32Z',
          'sections': []}]
  

Тесты:

 df = pd.json_normalize(sample)
df2 = pd.json_normalize(df.to_dict(orient="records"), meta=["_id", "created_at"], record_path="sections", record_prefix="section_")
  

На данный момент мне не хватает строки для идентификатора «5f48f708fe22ca4d15fb3b55», которая мне все еще нужна.

 df3 = pd.json_normalize(df2.to_dict(orient="records"), meta=["_id", "created_at", "section__id", "section_score", "section_passed", "section_type_fail", "section_comment"], record_path="section_answers", record_prefix="")
  

Могу ли я как-то изменить это, чтобы убедиться, что я получаю минимум одну строку на идентификатор? Я имею дело с миллионами записей и не хочу позже осознавать, что в моих окончательных данных отсутствовали некоторые идентификаторы. Единственное решение, которое я могу придумать, — это нормализовать каждый фрейм данных, а затем снова присоединить его к исходному фрейму данных.

Ответ №1:

  • Лучший способ решить проблему — исправить dict
  • Если sections это пустое поле list , заполните его [{'answers': [{}]}]
 for i, d in enumerate(sample):
    if not d['sections']:
        sample[i]['sections'] = [{'answers': [{}]}]

df = pd.json_normalize(sample)
df2 = pd.json_normalize(df.to_dict(orient="records"), meta=["_id", "created_at"], record_path="sections", record_prefix="section_")

# display(df2)
  section_comment  section_type_fail                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               section_answers  section_score section_passed               section__id section_custom_fields                       _id            created_at
0                                NaN                                                                                                                                                                        [{'comment': 'stuff', 'feedback': [], 'value': 10.0, 'answer_type': 'default', 'question_id': '5e59599c68369c24069630fd', 'answer_id': '5e595a7c3fbb70448b6ff935'}, {'comment': 'stuff', 'feedback': [], 'value': 10.0, 'answer_type': 'default', 'question_id': '5e598939cedcaf5b865ef99a', 'answer_id': '5e598939cedcaf5b865ef998'}]           20.0           True  5e59599c68369c24069630fe                    []  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z
1                                NaN  [{'comment': '', 'feedback': [], 'value': None, 'answer_type': 'not_applicable', 'question_id': '5e59894f68369c2398eb68a8', 'answer_id': '5eaad4e5b513aed9a3c996a5'}, {'comment': '', 'feedback': [], 'value': None, 'answer_type': 'not_applicable', 'question_id': '5e598967cedcaf5b865efe3e', 'answer_id': '5eaad4ece3f1e0794372f8b2'}, {'comment': 'stuff', 'feedback': [], 'value': 0.0, 'answer_type': 'default', 'question_id': '5e598976cedcaf5b865effd1', 'answer_id': '5e598976cedcaf5b865effd3'}]            0.0           True  5e59894f68369c2398eb68a9                    []  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z
2             NaN                NaN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          [{}]            NaN            NaN                       NaN                   NaN  5f48f708fe22ca4d15fb3b55  2020-08-28T12:22:32Z

df3 = pd.json_normalize(df2.to_dict(orient="records"), meta=["_id", "created_at", "section__id", "section_score", "section_passed", "section_type_fail", "section_comment"], record_path="section_answers", record_prefix="")

# display(df3)
  comment feedback  value     answer_type               question_id                 answer_id                       _id            created_at               section__id section_score section_passed section_type_fail section_comment
0   stuff       []   10.0         default  5e59599c68369c24069630fd  5e595a7c3fbb70448b6ff935  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z  5e59599c68369c24069630fe            20           True               NaN                
1   stuff       []   10.0         default  5e598939cedcaf5b865ef99a  5e598939cedcaf5b865ef998  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z  5e59599c68369c24069630fe            20           True               NaN                
2               []    NaN  not_applicable  5e59894f68369c2398eb68a8  5eaad4e5b513aed9a3c996a5  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z  5e59894f68369c2398eb68a9             0           True               NaN                
3               []    NaN  not_applicable  5e598967cedcaf5b865efe3e  5eaad4ece3f1e0794372f8b2  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z  5e59894f68369c2398eb68a9             0           True               NaN                
4   stuff       []    0.0         default  5e598976cedcaf5b865effd1  5e598976cedcaf5b865effd3  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z  5e59894f68369c2398eb68a9             0           True               NaN                
5     NaN      NaN    NaN             NaN                       NaN                       NaN  5f48f708fe22ca4d15fb3b55  2020-08-28T12:22:32Z                       NaN           NaN            NaN               NaN             NaN
  

Комментарии:

1. Заполнение пустых значений с помощью list empty dict сработало для меня и позволило мне использовать json_normalize чисто (по сравнению с использованием другой библиотеки). Единственное, что мне приходилось учитывать, это когда вы нормализуете поле, а другие поля имеют тип списка — мне пришлось преобразовать эти столбцы списка в строки. Теперь у меня есть рабочее решение, спасибо.

2. @ldacey Я рад, что это сработало для вас. Работа с неправильно структурированными файлами JSON может быть болезненной.

3. @TrentonMcKinney Елки-палки! Я только что столкнулся с этой проблемой. Спасибо за ваше решение здесь!

4. @ScottBoston Привет, Скотт! Рад, что это сработало для вас. Объекты JSON могут быть такой проблемой. Упс! Сказал то же самое 1,5 года назад. Я поддерживаю этот комментарий.

Ответ №2:

Это известная проблема с json_normalize . Я не нашел способа сделать это с помощью json_normalize . Вы можете попробовать использовать flatten_json что-то вроде этого:

 import flatten_json as fj

dic = (fj.flatten(d) for d in sample)
df = pd.DataFrame(dic)
print(df)

                        _id            created_at sections_0_comment  ...            sections_1__id sections_1_custom_fields sections
0  5f48bee4c54cf6b5e8048274  2020-08-28T08:23:00Z                     ...  5e59894f68369c2398eb68a9                       []      NaN
1  5f48f708fe22ca4d15fb3b55  2020-08-28T12:22:32Z                NaN  ...                       NaN                      NaN       []
  

Комментарии:

1. Известная проблема, поскольку в отчете о текущей проблеме есть отчет, или я должен создать его потенциально? Я чувствую, что должно быть хотя бы предупреждение, поскольку вы можете пропускать данные, не зная, почему. Я только что протестировал модуль flatten_json, спасибо — есть ли шанс, что он может сглаживать / разбивать данные на строки вместо столбцов? Это поле содержит пользовательские поля, которые могут быть добавлены к результатам в любой момент, существуют буквально тысячи уникальных идентификаторов вопросов и ответов, поэтому наличие столбца для каждого из них у меня не сработает.

2. Здесь открыта проблема: github.com/pandas-dev/pandas/issues/21830

3. Чтобы разбить на строки, вам нужно выполнить цикл через dict, а затем выровнять их с помощью flatten функции

4. Круто, дай мне попробовать. Я также создаю новую проблему, которая казалась немного более конкретной: github.com/pandas-dev/pandas/issues/36245 с примером точного вывода, который я ожидаю при использовании мета-столбцов