Урок курса

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.

  1. Читает из stdin целое число n — количество строк таблицы.
  2. Читает следующие n строк; каждая содержит четыре токена через пробел: region (строка без пробелов), q1 (целое число), q2 (целое число), q3 (целое число). Пример строки: Север 100 200 150.
  3. Строит pd.DataFrame из словаря списков с ключами 'region', 'q1', 'q2', 'q3'.
  4. Вызывает 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.

  1. Строит сводную таблицу из 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.

  1. Строит pd.DataFrame с двумя столбцами:
  • 'region' — список уникальных регионов из исходного DataFrame (в порядке первого появления, без дублей).
  • 'quarters' — для каждого региона список ['q1', 'q2', 'q3'] (одинаковый для всех).
  1. Вызывает .explode('quarters').reset_index(drop=True) и сохраняет в df_ex.
  2. Выводит строку: explode shape=<shape> — например, explode shape=(9, 2).
  3. Выводит все строки 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>
...
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 4

Найди и исправь: три ошибки в melt, pivot_table и explode

Что уже дано

В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:

  1. В вызове pd.melt не передан параметр id_vars — из-за этого столбец 'product' тоже разворачивается в строки вместо того, чтобы оставаться идентификатором.
  2. В вызове pd.pivot_table не указан параметр aggfunc — pandas использует 'mean' по умолчанию вместо требуемого 'sum'.
  3. После .explode('months') не вызван .reset_index(drop=True) — индекс результата содержит повторяющиеся метки вместо сквозного RangeIndex.

Каждая ошибка находится в отдельной строке кода. Исправьте все три.

Что нужно сделать

Исправьте три ошибки так, чтобы программа:

  1. Читала из stdin целое число n — количество строк таблицы продуктов.
  2. Читала следующие n строк; каждая содержит три токена через пробел: product (строка без пробелов), jan (целое число), feb (целое число).
  3. Строила pd.DataFrame из словаря списков с ключами 'product', 'jan', 'feb'.
  4. Переводила его в длинный формат через pd.melt с id_vars=['product'], value_vars=['jan', 'feb'], var_name='month', value_name='sales'. Выводила melt_shape=<shape>.
  5. Строила сводную таблицу через pd.pivot_table с index='product', columns='month', values='sales', aggfunc='sum', fill_value=0. Выводила pivot_sum_jan=<сумма по jan>.
  6. Строила 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 — число уникальных продуктов
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 5

Сводная таблица продаж по отделам и кварталам: pivot_table с aggfunc, fill_value и reset_index

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая строит сводную таблицу продаж из длинного формата.

  1. Читает из stdin целое число n — количество строк таблицы (n >= 1).
  2. Читает следующие n строк; каждая содержит три токена через пробел: dept (строка без пробелов), quarter (строка вида Q1, Q2 и т.д.), amount (целое число). Пример строки: Alpha Q1 700.
  3. Строит pd.DataFrame из словаря списков с ключами 'dept', 'quarter', 'amount'.
  4. Строит сводную таблицу через pd.pivot_table с параметрами:
  • index='dept'
  • columns='quarter'
  • values='amount'
  • aggfunc='sum' — явно, так как одна пара (dept, quarter) может встречаться несколько раз
  • fill_value=0 — заполнить отсутствующие комбинации нулём
  1. Сохраняет результат в pt. Вызывает pt.reset_index(), сохраняет в pt_flat. Убирает имя оси столбцов: pt_flat.columns.name = None.
  2. Выводит строку: shape=<shape> — форму pt_flat в виде (строк, столбцов), например shape=(3, 3).
  3. Выводит строку: columns=<список> — список столбцов pt_flat в виде Python-списка, например columns=['dept', 'Q1', 'Q2']. Порядок столбцов — 'dept', затем столбцы кварталов в алфавитном порядке (pandas сортирует их автоматически).
  4. Для каждой строки 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=<число>
...
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Теги товаров: explode и groupby после разворачивания списков

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

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

  1. Читает из stdin целое число n — количество строк таблицы.
  2. Читает следующие n строк; каждая содержит несколько токенов через пробел:
  • первый токен — product (строка без пробелов);
  • второй токен — product_count (целое число);
  • все последующие токены — теги товара (одно или несколько слов без пробелов внутри).

Пример строки: Стол 3 дерево мебель

  1. Строит pd.DataFrame из словаря списков с ключами 'product', 'product_count' и 'tags', где 'tags' — список тегов для каждого товара.
  2. Разворачивает столбец 'tags' через .explode('tags').reset_index(drop=True) и сохраняет в df_ex.
  3. Выводит строку: explode shape=<shape> — например, explode shape=(5, 3).
  4. Выводит строку: tag columns=<список столбцов df_ex> — список столбцов в виде Python-списка.
  5. Группирует df_ex по столбцу 'tags' с as_index=False и вычисляет агрегат product_count=('product_count', 'sum'). Сохраняет в grouped. Сортирует grouped по 'product_count' по убыванию, затем по 'tags' по возрастанию (для стабильного порядка при одинаковых суммах). Сбрасывает индекс через reset_index(drop=True).
  6. Для каждой строки grouped выводит одну строку вида: <tag> <product_count>.

Ввод и вывод

Программа читает stdin и пишет в stdout.

Формат ввода

n
product1 count1 tag1a tag1b ...
...
productN countN tagNa ...

Формат вывода

explode shape=(<строк>, <столбцов>)
tag columns=['product', 'product_count', 'tags']
<tag1> <sum1>
...
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку