2025, Oct 04 05:15

Как превратить столбец словарей в длинный датафрейм с id и schedule_name в pandas

Пошаговое решение в pandas: преобразуем столбец словарей в «длинный» датафрейм с колонками id и schedule_name. Выравнивание ключей, stack, reset_index.

Преобразование вложенного JSON в аккуратную, готовую к анализу таблицу — задача, которая встречается регулярно. Частая ситуация: в столбце pandas хранится набор словарей, где ключи — идентификаторы, а значения — понятные человеку подписи. Цель — превратить каждую пару ключ–значение из всех строк в опрятный датафрейм из двух столбцов: id и schedule_name. Корректно выполнить это по всему столбцу, а не только для одной строки, — именно то, на чём многие спотыкаются.

С чего начинаем

Предположим, в одном из столбцов датафрейма лежат словари с привязками расписаний по строкам. В общих чертах это выглядит так:

tbl['schedules']
0  {'3263524': 'Group 1 CORE DAYS', '3263525': 'Group 1 CORE NIGHTS', '3263526': 'Group 1 EDUCATION', '3263527': 'Group 1 ROUNDING'}
1  {'3263524': 'Group 1 CORE DAYS', '3881368': 'VS Days', '3881370': 'VS Education Shift A', '3881455': 'VS Education Shift B'}

Искомый «длинный» датафрейм содержит по одной строке на каждую пару из любого словаря:

id        schedule_name
3263524   Group 1 CORE DAYS
3263525   Group 1 CORE NIGHTS
3263526   Group 1 EDUCATION
3263527   Group 1 ROUNDING
3881368   VS Days
3881370   VS Education Shift A
3881455   VS Education Shift B

Наивная попытка, которая не срабатывает

Легко поддаться желанию передать один из словарей в from_dict, но так мы обрабатываем лишь одну строку и всё равно не получаем нужные имена столбцов и индекс:

sched_map = pd.DataFrame.from_dict(tbl['schedules'].iloc[0], orient='index')

Такой подход игнорирует остальные строки и к тому же требует дополнительных шагов, чтобы привести названия столбцов и индекс к нужному виду.

Что здесь на самом деле происходит

Чистое решение начинается с выравнивания всех ключей словарей по строкам как по столбцам. Превращение серии словарей в «широкий» датафрейм делает именно это: каждый уникальный ключ становится отдельным столбцом, а там, где ключ отсутствует, подставляется NaN. Затем stack переводит данные в «длинный» формат, и каждая пара ключ–значение превращается в отдельную строку. Опускание уровня индекса в колонку раскрывает ключи словаря как обычные данные. Переименование двух столбцов завершает форму, а удаление дублей сводит повторяющиеся пары.

Решение

Вся логика укладывается в такой конвейер на чистом pandas:

flat_schedules = (
    pd.DataFrame(tbl['schedules'].tolist())
      .stack().reset_index(level=1)
      .set_axis(['id', 'schedule_name'], axis=1)
      .drop_duplicates(ignore_index=True)
)

Результат:

id         schedule_name
3263524    Group 1 CORE DAYS
3263525    Group 1 CORE NIGHTS
3263526    Group 1 EDUCATION
3263527    Group 1 ROUNDING
3881368    VS Days
3881370    VS Education Shift A
3881455    VS Education Shift B

Почему это работает, шаг за шагом

Первый шаг, DataFrame(tbl['schedules'].tolist()), формирует «широкую» таблицу, выравнивая ключи по столбцам. Эффект хорошо виден, если взглянуть на промежуточный результат:

3263524              3263525            3263526  \
0  Group 1 CORE DAYS  Group 1 CORE NIGHTS  Group 1 EDUCATION   
1  Group 1 CORE DAYS                  NaN                NaN   

            3263527  3881368               3881370               3881455  
0  Group 1 ROUNDING      NaN                   NaN                   NaN  
1               NaN  VS Days  VS Education Shift A  VS Education Shift B  

Вызов stack() переворачивает эту широкую матрицу в длинный ряд индекс–значение, где каждая строка соответствует одной паре ключ–значение, включая пары из всех исходных строк:

0  3263524       Group 1 CORE DAYS
   3263525     Group 1 CORE NIGHTS
   3263526       Group 1 EDUCATION
   3263527        Group 1 ROUNDING
1  3263524       Group 1 CORE DAYS
   3881368                 VS Days
   3881370    VS Education Shift A
   3881455    VS Education Shift B

reset_index(level=1) переносит ключи словаря из составного индекса в обычный столбец. set_axis(['id', 'schedule_name'], axis=1) задаёт названия двух столбцов. drop_duplicates(ignore_index=True) убирает повторяющиеся пары и перенумеровывает строки с нуля.

Зачем важны эти детали

При загрузке вложенного JSON сразу приводите его к аккуратной и предсказуемой схеме — так вы избежите проблем дальше по конвейеру. Правильное выравнивание ключей по строкам до поворота предотвращает тихую потерю данных и странные случаи при объединениях, фильтрации или агрегациях. Компактный конвейер, переводящий столбец словарей в чистую «длинную» таблицу, проще понимать, тестировать и переиспользовать.

Итоги

Используйте двухэтапное преобразование формы: сначала расширьте данные, выровняв ключи словарей по столбцам, затем сожмите их с помощью stack, получив по строке на каждую пару ключ–значение. Завершите, сделав ключи отдельным столбцом, присвоив осмысленные названия колонкам и устранив дубликаты. Такая последовательность даёт надёжное соответствие id и schedule_name, которое без опаски можно подключать к остальному пайплайну на pandas.

Статья основана на вопросе на StackOverflow от skohrs и ответе PaulS.