Урок курса
melt, pivot_table и explode: широкий и длинный форматы
NumPy и pandas: практический тренажёрКак melt переводит таблицу из широкого формата в длинный
df.melt собирает несколько столбцов значений в два: название исходного столбца и его значение. Идентификаторы из id_vars сохраняются. Например, оценки по предметам можно собрать в столбцы «предмет» и «оценка»:
import pandas as pd
df = pd.DataFrame({"student": ["Аня"], "math": [8], "physics": [9]})
long = df.melt(id_vars="student", value_vars=["math", "physics"],
var_name="subject", value_name="score")
print(long.to_dict(orient="records"))Результат:
[{'student': 'Аня', 'subject': 'math', 'score': 8}, {'student': 'Аня', 'subject': 'physics', 'score': 9}]pivot_table строит сводную таблицу и агрегирует повторяющиеся сочетания ключей; explode разворачивает элементы списков внутри ячеек в отдельные строки. Это разные преобразования. Ниже разберём, как выбрать операцию под структуру данных.
В предыдущем уроке мы соединяли таблицы через merge и concat — операции, которые работают по горизонтали и вертикали, но не меняют саму структуру данных. Сейчас займёмся другим: научимся переформатировать таблицу целиком — переводить её из широкого представления в длинное и обратно.
Широкий и длинный форматы: зачем переводить данные
Возьмём простую таблицу с оценками студентов:
| student | math | physics | |---------|------|---------| | Аня | 85 | 90 | | Боря | 70 | 65 |
Это широкий формат (wide): каждый предмет — отдельный столбец. Для чтения глазами удобно, но для анализа — нет. Если нужно отфильтровать все оценки ниже 80, или сгруппировать результаты по предмету, или подсчитать среднее по каждому предмету через groupby — придётся писать отдельный код для каждого столбца.
Проблема решается переходом к длинному формату (long или tidy), где одна строка = одно наблюдение по одному измерению:
| student | subject | score | |---------|---------|-------| | Аня | math | 85 | | Аня | physics | 90 | | Боря | math | 70 | | Боря | physics | 65 |
Теперь df[df['score'] < 80] даёт все слабые результаты сразу, а groupby('subject')['score'].mean() считает среднее по каждому предмету одной строкой — независимо от того, сколько предметов в таблице.
Для этого перевода в pandas есть pd.melt.
import pandas as pd
df_wide = pd.DataFrame({
'student': ['Аня', 'Боря'],
'math': [85, 70],
'physics': [90, 65]
})
df_long = pd.melt(
df_wide,
id_vars=['student'], # столбцы-идентификаторы — остаются неизменными
value_vars=['math', 'physics'], # столбцы, которые «разворачиваем» в строки
var_name='subject', # как назвать столбец с именами бывших столбцов
value_name='score' # как назвать столбец со значениями
)
print(df_long)
Вывод:
student subject score 0 Аня math 85 1 Боря math 70 2 Аня physics 90 3 Боря physics 65
Что происходит внутри: каждый столбец из value_vars становится набором строк. Столбцы из id_vars дублируются для каждого такого столбца. Итоговое число строк — len(df) × len(value_vars), то есть 2 × 2 = 4.
Параметры по смыслу: — id_vars — «якорные» столбцы, которые идентифицируют запись. В нашем примере это student. Они не разворачиваются, а просто повторяются в каждой строке результата.
value_vars— столбцы, которые нужно превратить в строки. Если пропустить этот параметр, pandas возьмёт все столбцы, кроме тех, что вid_vars, — иногда это удобно, но лучше указывать явно.var_nameиvalue_name— имена двух новых столбцов. По умолчанию pandas называет ихvariableиvalue, что неинформативно; всегда стоит задавать свои имена.
Широкий формат удобен для ввода данных вручную и для отчётов. Длинный — для аналитических операций: фильтрации по единому столбцу значений, группировки через groupby, объединения с другими таблицами через merge. pd.melt — это переключатель между ними в одну сторону. Обратное преобразование с агрегацией делает pd.pivot_table, о котором — в следующем разделе.
Сводная таблица с агрегацией через pd.pivot_table
pd.melt идёт в одну сторону — из широкого формата в длинный. pd.pivot_table — в обратную: берёт длинную таблицу и строит из неё агрегированный широкий формат.
Представим такую таблицу продаж:
import pandas as pd
df_sales = pd.DataFrame({
'city': ['Москва', 'Москва', 'Казань', 'Казань', 'Москва'],
'month': ['Jan', 'Feb', 'Jan', 'Feb', 'Jan'],
'revenue': [100, 200, 150, 130, 80]
})
Здесь несколько строк с одинаковой парой city + month (Москва, Jan встречается дважды). Мы хотим получить таблицу, где строки — города, столбцы — месяцы, а в ячейках — суммарная выручка:
pt = pd.pivot_table(
df_sales,
index='city', # уникальные значения этого столбца станут строками
columns='month', # уникальные значения этого столбца станут столбцами
values='revenue', # что агрегировать
aggfunc='sum' # как агрегировать
)
print(pt)
Вывод:
month Feb Jan city Казань 130 150 Москва 200 180
Параметры по смыслу:
index— столбец, чьи уникальные значения образуют строки результата.columns— столбец, чьи уникальные значения образуют столбцы результата.values— столбец с числами, которые нужно агрегировать.aggfunc— функция агрегации:'sum','mean','count'или любая Python-функция. По умолчанию'mean'— это частая причина неожиданных результатов, поэтому всегда указывай явно.
Именованный индекс и как от него избавиться
У результата pivot_table есть особенность: city становится именованным индексом, а month — именем оси столбцов. Это не ошибка, но если нужна плоская таблица без индекса — вызывай reset_index():
pt_flat = pt.reset_index()
print(pt_flat)
Вывод:
month city Feb Jan 0 Казань 130 150 1 Москва 200 180
После reset_index() city превращается в обычный столбец, индекс становится стандартным RangeIndex. Обрати внимание: метка month над строкой столбцов никуда не девается — это columns.name, оставшееся от параметра columns='month'. Это косметика API, не влияющая на данные; при необходимости убирается через pt_flat.columns.name = None.
Ещё одна частая ситуация — пропуски в результате. Если для какой-то комбинации строка/столбец данных нет, pivot_table ставит NaN. Параметр fill_value позволяет заменить их нулём или любым другим значением:
pt = pd.pivot_table(
df_sales,
index='city',
columns='month',
values='revenue',
aggfunc='sum',
fill_value=0
)
Сводная таблица, построенная через pivot_table, — это полноценный DataFrame, с которым можно работать дальше: сортировать, фильтровать, применять rank или cumsum по строкам или столбцам.
Разворачивание списков в строки через explode
До сих пор мы меняли форму таблицы, перекладывая столбцы в строки (melt) или строки в столбцы (pivot_table). Но иногда данные приходят в ещё более сжатом виде: одна ячейка содержит список значений. Например, у каждого товара — несколько тегов, у каждого заказа — список позиций.
import pandas as pd
df_tags = pd.DataFrame({
'product': ['Стол', 'Стул', 'Лампа'],
'tags': [['дерево', 'мебель'], ['пластик', 'мебель'], ['свет']]
})
print(df_tags)
product tags
0 Стол [дерево, мебель]
1 Стул [пластик, мебель]
2 Лампа [свет]
Здесь в столбце tags — Python-списки, а не отдельные значения. Фильтровать, группировать или агрегировать по тегам в таком виде не получится: pandas воспринимает каждый список как единый объект.
df.explode('col') раскладывает список в отдельные строки. Каждый элемент получает свою строку, а все остальные столбцы в этой строке дублируются.
df_ex = df_tags.explode('tags')
print(df_ex)
product tags 0 Стол дерево 0 Стол мебель 1 Стул пластик 1 Стул мебель 2 Лампа свет
Обрати внимание на индекс: значения 0, 0, 1, 1, 2. Pandas сохраняет исходный индекс строки и повторяет его для каждого элемента списка. Порядок строк при этом не нарушается, и позиционный доступ через .iloc работает корректно даже с повторяющимися метками.
Сброс индекса нужен в двух конкретных ситуациях:
Контракт на уникальный RangeIndex — если последующий код или внешняя функция ожидает, что индекс строго последовательный и без повторений. 2.
Доступ по метке через .loc — если обращаться к строкам по метке 0, df_ex.loc[0] вернёт сразу несколько строк, что может быть неожиданным.
df_ex = df_tags.explode('tags').reset_index(drop=True)
print(df_ex)
product tags 0 Стол дерево 1 Стол мебель 2 Стул пластик 3 Стул мебель 4 Лампа свет
reset_index(drop=True) создаёт новый последовательный RangeIndex и выбрасывает старый. Параметр drop=True здесь обязателен: без него старый индекс переедет в отдельный столбец с именем index, что обычно нежелательно.
Ещё один нюанс: если в столбце встречается пустой список [], explode превратит его в строку с NaN — сигнал, что у записи не было ни одного элемента. Это не ошибка, но стоит держать в голове при дальнейшей фильтрации.
После explode с таблицей можно работать стандартными средствами: groupby('tags').size() подсчитает, сколько товаров имеет каждый тег; merge по столбцу tags присоединит справочник тегов. explode сам по себе не агрегирует — он только переводит данные в форму, удобную для последующего анализа.
Сквозной пример: melt, pivot_table и explode вместе
Три операции из этого урока решают разные задачи — важно не путать, когда какую применять:
- melt — есть несколько столбцов с однотипными значениями (оценки по предметам, выручка по месяцам), нужно перенести их имена и значения в строки для последующей фильтрации или
groupby. - pivot_table — есть длинная таблица с повторяющимися комбинациями ключей, нужно свернуть её в сводку с агрегацией.
- explode — в одной ячейке хранится список, нужно разложить каждый элемент в отдельную строку.
Наиболее частая связка на практике — explode → groupby + agg: сначала вложенная структура раскрывается, потом по ней считается агрегат. Этот сценарий нельзя воспроизвести стандартными агрегациями без предварительного разворачивания.
Возьмём таблицу товаров с тегами из предыдущего раздела и подсчитаем, сколько товаров несёт каждый тег:
import pandas as pd
df_tags = pd.DataFrame({
'product': ['Стол', 'Стул', 'Лампа'],
'tags': [['дерево', 'мебель'], ['пластик', 'мебель'], ['свет']]
})
tag_counts = (
df_tags
.explode('tags')
.reset_index(drop=True)
.groupby('tags', as_index=False)
.agg(product_count=('product', 'count'))
)
print(tag_counts)
tags product_count 0 дерево 1 1 мебель 2 2 пластик 1 3 свет 1
Тег «мебель» встречается у двух товаров — groupby это видит только потому, что explode сначала превратил каждый список в отдельные строки. Именованная агрегация product_count=('product', 'count') — тот же синтаксис agg, что вы использовали в уроке о groupby.
Резюме по выбору инструмента:
| Ситуация | Инструмент |
|---|---|
| Столбцы-значения → строки | pd.melt |
| Строки → агрегированные столбцы | pd.pivot_table |
| Список в ячейке → отдельные строки | explode |
Все три операции приводят данные к форме, удобной для следующего шага анализа: melt переносит имена value_vars и их значения в строки, pivot_table сжимает длинную таблицу в читаемую сводку с агрегацией, explode вскрывает вложенные структуры. Освоив эти преобразования, вы сможете применять к результату привычные инструменты — groupby, merge, сортировку и оконные функции, с которыми продолжим работу в следующих уроках.
Задача 1
DataFrame df создан как:
import pandas as pd
df = pd.DataFrame({
'city': ['Москва', 'Казань'],
'jan': [100, 200],
'feb': [150, 250]
})
result = pd.melt(df, id_vars=['city'], value_vars=['jan', 'feb'])
Чему равен result.shape?
Задача 2
DataFrame df создан как:
import pandas as pd
df = pd.DataFrame({
'name': ['Стол', 'Стул', 'Шкаф'],
'colors': [['белый', 'чёрный'], ['синий'], ['белый', 'серый', 'чёрный']]
})
result = df.explode('colors').reset_index(drop=True)
Чему равен result.shape[0]?
Задача 3
Продажи по кварталам: melt → pivot_table → explode за один проход
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая выполняет три последовательных преобразования формата данных.
Шаг 1. Чтение и melt.
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит четыре токена через пробел:region(строка без пробелов),q1(целое число),q2(целое число),q3(целое число). Пример строки:Север 100 200 150. - Строит
pd.DataFrameиз словаря списков с ключами'region','q1','q2','q3'. - Вызывает
pd.meltс параметрами:
id_vars=['region']value_vars=['q1', 'q2', 'q3']var_name='quarter'value_name='sales'
Сохраняет результат в df_long.
5. Выводит строку: melt shape=<shape> — например, melt shape=(9, 3).
Шаг 2. pivot_table.
- Строит сводную таблицу из
df_long:
index='region'columns='quarter'values='sales'aggfunc='sum'fill_value=0
Сохраняет в pt.
7. Вызывает pt.reset_index() и сохраняет в pt_flat. Убирает имя оси столбцов: pt_flat.columns.name = None.
8. Выводит строку: pivot columns=<список> — список столбцов pt_flat в виде Python-списка, например pivot columns=['region', 'q1', 'q2', 'q3'].
9. Выводит суммарные продажи по каждому региону через pt_flat: для каждой строки pt_flat (в порядке строк) выводит одну строку вида <region> total=<сумма q1+q2+q3>.
Шаг 3. explode.
- Строит
pd.DataFrameс двумя столбцами:
'region'— список уникальных регионов из исходного DataFrame (в порядке первого появления, без дублей).'quarters'— для каждого региона список['q1', 'q2', 'q3'](одинаковый для всех).
- Вызывает
.explode('quarters').reset_index(drop=True)и сохраняет вdf_ex. - Выводит строку:
explode shape=<shape>— например,explode shape=(9, 2). - Выводит все строки
df_exв формате<region> <quarter>— по одной строке на каждую записьdf_ex.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Ограничения: n >= 1.
Формат ввода
n region1 q1_1 q2_1 q3_1 ... regionN q1_N q2_N q3_N
Формат вывода
melt shape=(<строк>, <столбцов>)
pivot columns=[...]
<region1> total=<число>
...
explode shape=(<строк>, <столбцов>)
<region> <quarter>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
Найди и исправь: три ошибки в melt, pivot_table и explode
Что уже дано
В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:
- В вызове
pd.meltне передан параметрid_vars— из-за этого столбец'product'тоже разворачивается в строки вместо того, чтобы оставаться идентификатором. - В вызове
pd.pivot_tableне указан параметрaggfunc— pandas использует'mean'по умолчанию вместо требуемого'sum'. - После
.explode('months')не вызван.reset_index(drop=True)— индекс результата содержит повторяющиеся метки вместо сквозногоRangeIndex.
Каждая ошибка находится в отдельной строке кода. Исправьте все три.
Что нужно сделать
Исправьте три ошибки так, чтобы программа:
- Читала из
stdinцелое числоn— количество строк таблицы продуктов. - Читала следующие
nстрок; каждая содержит три токена через пробел:product(строка без пробелов),jan(целое число),feb(целое число). - Строила
pd.DataFrameиз словаря списков с ключами'product','jan','feb'. - Переводила его в длинный формат через
pd.meltсid_vars=['product'],value_vars=['jan', 'feb'],var_name='month',value_name='sales'. Выводилаmelt_shape=<shape>. - Строила сводную таблицу через
pd.pivot_tableсindex='product',columns='month',values='sales',aggfunc='sum',fill_value=0. Выводилаpivot_sum_jan=<сумма по jan>. - Строила
pd.DataFrameс столбцами'product'(список уникальных продуктов в порядке первого появления) и'months'(для каждого —['jan', 'feb']). Разворачивала через.explode('months').reset_index(drop=True). Выводилаexplode_index=<список меток индекса>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Ограничения: n >= 1.
Формат ввода
n product1 jan1 feb1 ... productN janN febN
Формат вывода
melt_shape=(<строк>, <столбцов>)
pivot_sum_jan=<число>
explode_index=[0, 1, ..., 2u-1], где u — число уникальных продуктов
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Сводная таблица продаж по отделам и кварталам: pivot_table с aggfunc, fill_value и reset_index
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая строит сводную таблицу продаж из длинного формата.
- Читает из
stdinцелое числоn— количество строк таблицы (n >= 1). - Читает следующие
nстрок; каждая содержит три токена через пробел:dept(строка без пробелов),quarter(строка видаQ1,Q2и т.д.),amount(целое число). Пример строки:Alpha Q1 700. - Строит
pd.DataFrameиз словаря списков с ключами'dept','quarter','amount'. - Строит сводную таблицу через
pd.pivot_tableс параметрами:
index='dept'columns='quarter'values='amount'aggfunc='sum'— явно, так как одна пара(dept, quarter)может встречаться несколько разfill_value=0— заполнить отсутствующие комбинации нулём
- Сохраняет результат в
pt. Вызываетpt.reset_index(), сохраняет вpt_flat. Убирает имя оси столбцов:pt_flat.columns.name = None. - Выводит строку:
shape=<shape>— формуpt_flatв виде(строк, столбцов), напримерshape=(3, 3). - Выводит строку:
columns=<список>— список столбцовpt_flatв виде Python-списка, напримерcolumns=['dept', 'Q1', 'Q2']. Порядок столбцов —'dept', затем столбцы кварталов в алфавитном порядке (pandas сортирует их автоматически). - Для каждой строки
pt_flat(в порядке строк) выводит одну строку вида:<dept> total=<сумма всех кварталов>. Сумму вычисляйте как сумму всех числовых столбцовpt_flat(то есть всех столбцов, кроме'dept').
Ввод и вывод
Программа читает stdin и пишет в stdout.
Ограничения: n >= 1.
Формат ввода
n dept1 quarter1 amount1 ... deptN quarterN amountN
Гарантируется: n >= 1; все значения dept и quarter — строки без пробелов; amount — целое число.
Формат вывода
shape=(<строк>, <столбцов>)
columns=[...]
<dept1> total=<число>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Теги товаров: explode и groupby после разворачивания списков
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая работает с таблицей товаров, где у каждого товара есть список тегов и число единиц на складе.
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит несколько токенов через пробел:
- первый токен —
product(строка без пробелов); - второй токен —
product_count(целое число); - все последующие токены — теги товара (одно или несколько слов без пробелов внутри).
Пример строки: Стол 3 дерево мебель
- Строит
pd.DataFrameиз словаря списков с ключами'product','product_count'и'tags', где'tags'— список тегов для каждого товара. - Разворачивает столбец
'tags'через.explode('tags').reset_index(drop=True)и сохраняет вdf_ex. - Выводит строку:
explode shape=<shape>— например,explode shape=(5, 3). - Выводит строку:
tag columns=<список столбцов df_ex>— список столбцов в виде Python-списка. - Группирует
df_exпо столбцу'tags'сas_index=Falseи вычисляет агрегатproduct_count=('product_count', 'sum'). Сохраняет вgrouped. Сортируетgroupedпо'product_count'по убыванию, затем по'tags'по возрастанию (для стабильного порядка при одинаковых суммах). Сбрасывает индекс черезreset_index(drop=True). - Для каждой строки
groupedвыводит одну строку вида:<tag> <product_count>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n product1 count1 tag1a tag1b ... ... productN countN tagNa ...
Формат вывода
explode shape=(<строк>, <столбцов>)
tag columns=['product', 'product_count', 'tags']
<tag1> <sum1>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- melt, pivottable и explode: широкий и длинный форматы — задача по NumPy и pandas
Практическая задача по теме «melt, pivottable и explode: широкий и длинный форматы» с проверкой ответа онлайн.
- Сводная таблица pandas: агрегаты, суммы и пустые ячейки
Как построить сводную таблицу pandas, суммировать повторные пары ключей и заполнить пустые ячейки? Пример и задача с проверкой.
- pandas explode и groupby: подсчёт тегов товаров
Разверните списки тегов через explode и посчитайте частоты тегов с groupby. Задача на преобразование DataFrame.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
