Состав целого: stacked bar и pie

Pie chart — самый спорный график. Часто его лучше заменить на bar.

«Используйте круговую диаграмму экономно и только если секторов пять или меньше».

Состав целого — это доли, которые в сумме дают 100%: структура расходов, доли рынка, источники трафика. Классический инструмент — pie chart, но он кодирует доли углами, которые глаз сравнивает плохо. Альтернативы — stacked bar (накопленные столбики) и просто отсортированный bar по долям.

Спор вокруг pie chart — не вкусовщина, а вопрос восприятия. Согласно иерархии Кливленда–Макгилла, человек точнее всего декодирует положение вдоль общей шкалы, затем длину, и заметно хуже — угол, площадь и цвет. Pie кодирует доли как раз углом и площадью сектора, то есть худшими из каналов, тогда как bar опирается на длину вдоль общей оси — лучший после позиции. Поэтому два близких сектора в 27% и 24% на пироге почти неразличимы, а два столбика той же высоты — очевидно разные. Отсюда практическое правило: если задача — точно сравнить доли или их проранжировать, bar объективно выигрывает у pie почти всегда.

У pie всё же есть ниша, где он оправдан: когда нужно подчеркнуть, что части складываются в единое целое, а самих частей мало (две-три, например «выполнено / осталось» или явное доминирование одной категории больше половины круга). Здесь форма круга интуитивно сообщает «это всё, и вот как оно поделено». Но как только долей становится больше пяти, или они близки по величине, или нужно сравнить структуру между периодами, пирог проигрывает. Для сравнения структуры во времени или между группами берут 100%-stacked bar: он нормирует каждую группу к единице и выстраивает сегменты вдоль общей оси, так что сдвиги долей видны как смещение границ между сегментами.

# Pie — допустим при <=5 секторах
fig, ax = plt.subplots()
ax.pie(shares, labels=names, autopct="%1.1f%%")
ax.set_title("Структура трафика")

# Часто лучше: отсортированный bar
fig, ax = plt.subplots()
ax.barh(names, shares)

# Stacked bar — состав по нескольким группам
ax.bar(groups, part1, label="Часть 1")
ax.bar(groups, part2, bottom=part1, label="Часть 2")
ВЫБОР ГРАФИКА ДЛЯ СОСТАВА
   Сколько частей?
        |
   <=5 -+- да -> pie допустим (или bar)
        |
        +- >5 -> bar по долям (сортировка)
        |
   Несколько групп? -> stacked bar / 100%-stacked

Как работает под капотом

Pie переводит каждую долю в угол сектора: доля 0.25 → 90°. Проблема: углы и площади секторов глаз оценивает хуже длин. Stacked bar кодирует доли высотой сегментов от общей базы; 100%-stacked нормирует каждую группу к единице, чтобы сравнивать структуру, а не объём.

У всех «составных» графиков есть общий технический нюанс — порядок сегментов. У stacked bar только нижний сегмент стоит на общей нулевой базе и потому легко читается; все верхние «плавают» на разной высоте, и сравнивать их между столбцами на глаз тяжело, потому что у них нет общего основания. Отсюда правило: самую важную для сравнения категорию кладут в самый низ стопки, а мелкие и «прочее» — наверх. То же с pie: секторы располагают по убыванию доли, начиная от 12 часов по часовой стрелке, чтобы крупнейшие шли первыми у точки отсчёта, где глаз точнее всего считывает угол. Хаотичный порядок секторов превращает чтение пирога в головоломку.

Нормировка к 100% — это содержательное решение, а не просто арифметика. Обычный stacked bar показывает и структуру, и абсолютный объём (высота всего столбика разная), а 100%-stacked прячет объём, выравнивая все столбцы по единице, и показывает только структуру. Выбор зависит от вопроса: «как растёт пирог в целом и кто внутри» — обычный stacked; «как меняются доли независимо от роста» — 100%-stacked. Частая ловушка — забыть, что при нормировке исчезает информация о размере: на 100%-stacked крошечная группа из 10 наблюдений и огромная из 10000 выглядят одинаково «полными», поэтому рядом полезно подписывать абсолютные n, чтобы читатель не сравнивал доли там, где выборка ненадёжна.

Перед построением доли нормируют к 100%. Сделаем это руками.

# Превращаем абсолютные значения в доли (проценты) для pie/stacked
raw = {"Поиск": 4200, "Соцсети": 1800, "Прямые": 1500,
       "Почта": 600, "Реклама": 900}

total = sum(raw.values())
shares = {k: v / total * 100 for k, v in raw.items()}

# сортируем по убыванию доли
for name, pct in sorted(shares.items(), key=lambda kv: kv[1], reverse=True):
    print("{:<8} {:>5.1f}%  {}".format(name, pct, "#" * int(pct)))

print("Сумма долей:", round(sum(shares.values()), 1), "%")

«Попробуй сам ▶» — проценты, которые вы посчитали, и есть входные данные для ax.pie. Сумма всегда должна давать 100%.

Частые ошибки

Pie с десятком секторов — невозможно сравнить близкие доли. 3D-пирог — искажает площади перспективой. Пирог, где доли не суммируются в 100% (логическая ошибка). Сравнивать два пирога между собой — почти невозможно глазом. Взрыв-эффекты и тени, маскирующие данные.

Best practices

  • Pie — максимум 5 секторов, без 3D и теней.
  • Проверяйте, что доли дают 100%.
  • Для сравнения структуры между группами — 100%-stacked bar, не несколько пирогов.
  • Когда сомневаетесь — берите bar по долям.

Итог: вы освоили базовые типы графиков. Дальше поднимемся на уровень выше — Seaborn, который строит статистические графики одной строкой.

Проверьте себя
1. При каком условии pie chart ещё допустим?
AКогда секторов 20 и больше
BКогда секторов не более пяти
CКогда нужен 3D-эффект
DКогда доли не суммируются в 100%
2. Что лучше использовать для сравнения структуры между несколькими группами?
AНесколько отдельных пирогов
B100%-stacked bar
C3D pie
DScatter