Аналитика29 августа 2026 г.

pivot и pivot_table в pandas: что делать с повторяющимися парами ключей

Автор
Ненашев А.
На чтение
7 мин
Дата обновления
29.08.2026
Старт:В любой момент
Срок обучения:5 дней
Аналитика данных на Python: pandas, numpy, визуализация
Python – один из самых популярных и востребованных языков программирования на сегодняшний день. Будучи высокоуровневым языком, он идеально подходит для разработки самостоятельных программ и сценариев и ориентирован на повышение производительности разработчика и читаемости кода. Синтаксис Python очень лаконичен, но в то же время его библиотеки включают большой объем полезных функций. Именно поэтому он отлично подходит для тех, кто только начинает свой профессиональный путь в программировании. Что необходимо для того, чтобы освоить Python новичку? Компьютер и базовые знания английского. Сегодня Python применяется в актуальной сфере анализа больших данных. Освоение этого языка программирования может стать первой ступенью на пути к освоению одной из самых востребованных профессий 21 века
Подробнее
1 500 ₽50 000 ₽
#COURSE##INNER#

pivot и pivot_table в pandas: что делать с повторяющимися парами ключей

Если pivot() в pandas не разворачивает длинную таблицу в широкую, причина часто не в самом развороте, а в том, что одна будущая ячейка получается из нескольких строк. Для pandas это не мелкая помеха, а логическая неоднозначность: библиотека не знает, какое значение положить в пересечение строки и столбца. Поэтому рабочий порядок такой: сначала определить, какой должна быть одна ячейка результата, потом найти все повторы пары ключей, затем задать явное правило обработки через pivot_table() или подготовить данные до разворота, и в конце сверить итог.

Ожидаемая сетка результата

У широкого отчета всегда есть скрытый вопрос: что означает одна ячейка. В задаче из учебного отчета удобно считать, что каждая ячейка — это итоговые баллы одного студента по одному модулю. Тогда строки — студенты, столбцы — модули, а значение — число баллов. Если для пары студент + модуль существует ровно одна строка, pivot() подходит без дополнительных настроек.

По документации pandas, pivot() работает только там, где комбинации ключей уникальны. Это полезное ограничение: оно заставляет заранее проверить, действительно ли вы переводите «длинную» запись в «широкую», а не пытаетесь незаметно спрятать несколько разных событий в одну ячейку.

Учебный пример

Учебный пример. Ниже условный отчет в среде Python с pandas. В нем три студента и три модуля, а повторения возникают там, где баллы пришли несколькими строками: как исправление, ручная корректировка или второй тест.

import pandas as pd

report = pd.DataFrame({
    "student": ["Анна", "Анна", "Анна", "Борис", "Борис", "Борис", "Вика", "Вика", "Вика"],
    "module": ["Python", "Python", "SQL", "Python", "BI", "BI", "SQL", "Python", "Python"],
    "points": [8, 2, 7, 5, 6, 1, 9, 4, 3],
    "reason": [
        "основная попытка",
        "добавлены баллы за исправление",
        "одна запись",
        "одна запись",
        "основная оценка",
        "ручная корректировка",
        "одна запись",
        "первый тест",
        "второй тест",
    ],
})

Ожидаемая сетка здесь такая:

studentBIPythonSQL
Анна—??
Борис??—
Вика—??

Проблема в том, что знаки вопроса для Анна + Python, Борис + BI и Вика + Python должны быть вычислены по нескольким строкам, а не прочитаны из одной записи. Именно на этом месте pivot() и останавливается.

Повтор пары ключей

Когда вы запускаете report.pivot(index="student", columns="module", values="points"), pandas не может выбрать одно значение для дублирующихся сочетаний ключей. Технически это выглядит как ошибка reshape, но по сути это предупреждение о несогласованной структуре данных. Если проигнорировать его и сразу выбрать первую попавшуюся агрегацию, можно получить аккуратную, но смыслово неверную таблицу.

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

duplicates = (
    report.groupby(["student", "module"], as_index=False)
    .agg(
        rows=("points", "size"),
        points_total=("points", "sum"),
        reasons=("reason", lambda s: "; ".join(s))
    )
)

duplicate_pairs = duplicates.query("rows > 1")
studentmodulerowspoints_totalreasons
АннаPython210основная попытка; добавлены баллы за исправление
БорисBI27основная оценка; ручная корректировка
ВикаPython27первый тест; второй тест

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

Явное правило агрегации

В нашем учебном кейсе будем считать, что каждая строка добавляет баллы к итогу модуля. Тогда повторы не конфликтуют между собой, а суммируются. Это уже не задача для pivot(), а задача для pivot_table(), потому что здесь нужна явная агрегация.

По документации pandas, pivot_table() строит сводную таблицу и агрегирует значения. Для этого нужно не только назвать ключи, но и честно прописать правило: sum, mean, count и так далее. Важно именно слово «явно»: не «как-нибудь собрать», а зафиксировать, что повтор означает в вашем отчете.

wide = report.pivot_table(
    index="student",
    columns="module",
    values="points",
    aggfunc="sum"
)
studentBIPythonSQL
Анна—107
Борис75—
Вика—79

Здесь арифметика проверяется вручную: у Анны по Python 8 + 2 = 10, у Бориса по BI 6 + 1 = 7, у Вики по Python 4 + 3 = 7. Обратите внимание на пустые ячейки: после pivot_table() отсутствующие сочетания ключей остаются пропусками, и для учебного отчета это честнее, чем автоматически подставлять 0. Пустая ячейка означает «записи для такой пары нет», а ноль означал бы уже другой смысл — «запись есть, но результат равен нулю».

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

Как сохранить объяснение каждого повторения

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

Если вам нужно видеть именно число исходных строк в каждой будущей ячейке, не стоит полагаться на count по столбцу points: такой расчет считает только непустые значения и совпадает с числом строк лишь там, где в points нет пропусков. Надежнее добавить явный счетчик строк и агрегировать его суммой. Документация pandas отдельно показывает, что у сводных таблиц могут появляться иерархические заголовки, если в результате участвуют несколько наборов значений. Это удобно, когда нужно рядом видеть итог баллов и точное число исходных записей.

report["row_n"] = 1

audit = report.pivot_table(
    index="student",
    columns="module",
    values=["points", "row_n"],
    aggfunc="sum"
)

В таком результате появятся два блока столбцов: points и row_n. Для пары Анна + Python вы увидите points = 10 и row_n = 2, а для Анна + SQL — points = 7 и row_n = 1. Для отсутствующих сочетаний снова останутся пропуски. Если вам нужен именно визуальный отчет по числу строк, можно уже в отдельной копии этой таблицы подставить 0 только в блок row_n, не меняя смысл столбцов с баллами.

Редакционная рекомендация на случай, когда повторы нельзя агрегировать: добавьте еще один ключ, например номер попытки. Тогда вы не «лечите» дубль агрегацией, а честно признаете, что в данных есть третье измерение.

report["attempt"] = report.groupby(["student", "module"]).cumcount() + 1

После этого можно строить широкую таблицу уже по student и паре module + attempt. Такой ход нужен не всегда, но он спасает там, где каждая повторная строка должна остаться самостоятельным наблюдением.

Сверка итогов

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

long_total = report["points"].sum()
wide_total = wide.sum().sum()
unique_pairs = report[["student", "module"]].drop_duplicates().shape[0]

assert long_total == 45
assert wide_total == 45
assert unique_pairs == 6

Здесь результат прозрачен: в исходном отчете 9 строк и 45 баллов, после агрегации остается 6 уникальных пар student + module, но те же 45 баллов. Это и есть проверяемый эффект корректного pivot_table(): таблица стала шире и короче, а смысл выбранного показателя сохранился.

Практический вывод такой: если pivot() ругается на повторяющиеся пары ключей, не заменяйте его на pivot_table() автоматически. Сначала сформулируйте, что должна означать одна ячейка, затем соберите список повторов и только после этого задайте правило агрегации или добавьте третий ключ. В нашем условном кейсе это дало широкую матрицу баллов без подмены пустых сочетаний нулями, таблицу причин повторов и контрольную сверку суммы 45 = 45.

Если хотите отдельно отработать похожие задачи, у курса «Аналитика данных на Python: pandas, numpy, визуализация» на внутренней карточке каталога перечислены темы по pandas, первичной обработке, повторам в данных, переформированию, группировке и агрегации; официальная страница школы также подтверждает блоки по pandas, первичной обработке и преобразованию данных.

Курсы

112790
0,0
0 отзывов
Аналитика данных на Python: pandas, numpy, визуализация
  • В любой момент
  • 5 дней
  • с преподавателем
  • удостоверение
1 500 ₽50 000 ₽
-97%
Перейти на сайт