Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 3 из 10 Начальный 40 мин 100 XP

Столбчатые диаграммы и гистограммы в Matplotlib: bar и hist

bar и barh для сравнения категорий, hist для распределений, параметр bins, план против факта и подписи значений над столбцами.

Редакция Питоники

Директор спрашивает: какая категория приносит больше всего? Вы отвечаете: «Электроника — 340 тысяч, одежда — 290, продукты — 410, книги — 95...» — и видите, что слушатель уже утонул. Столбчатая диаграмма отвечает на этот вопрос без слов: высота столбика равна величине, и сравнение происходит за долю секунды. А если вопрос другой — «какие у нас цены, много ли дорогих позиций?» — это работа для hist. Разберём оба и научимся их не путать.

Столбчатая диаграмма в Matplotlib: bar

Продажи по категориям
import matplotlib.pyplot as plt

categories = ["Электроника", "Одежда", "Книги", "Продукты", "Спорт"]
sales = [340, 290, 95, 410, 160]

fig, ax = plt.subplots()
ax.bar(categories, sales)
ax.set_title("Продажи по категориям, тыс. руб.")
ax.set_ylabel("Тыс. руб.")
plt.show()
Вывод
График отображается под редактором
Первый запуск загружает библиотеку — это займёт до минуты

bar() принимает категории по X и высоты по Y. Matplotlib сам расставит подписи под столбцами — plot строки по оси X тоже принимал, но bar для категорий честнее: он рисует отдельные прямоугольники, а не соединяет точки линией. Первый урок курса показывал разницу на анатомии figure и axes; здесь она видна на практике — метод тот же, данные другие.

Горизонтальные столбцы: barh

Подписи категорий бывают длинными: «Бытовая техника и электроника» под вертикальным столбцом не влезает, а поворот на 45 градусов делает график неопрятным. barh() рисует столбцы горизонтально — подписи слева, читаются как строки таблицы:

Выручка по регионам
import matplotlib.pyplot as plt

regions = ["Москва и область", "Санкт-Петербург", "Урал", "Сибирь"]
revenue = [520, 340, 210, 150]

fig, ax = plt.subplots()
ax.barh(regions, revenue)
ax.set_title("Выручка по регионам, млн руб.")
ax.set_xlabel("Млн руб.")
plt.show()
Вывод
График отображается под редактором

Правило большого пальца: больше четырёх-пяти категорий с длинными названиями — берите barh и не мучайтесь с поворотами подписей. Ещё один нюанс: в barh категории идут снизу вверх, поэтому для рейтингов типа «топ-10 товаров» данные сортируют и разворачивают, иначе первое место окажется внизу.

Сортировка столбцов: почему порядок важен

По умолчанию bar рисует категории в порядке передачи — обычно это порядок в данных, то есть алфавит или как попало. Для рейтингов сортируйте по значению: списки сортируются парой строк — sales_sorted, categories_sorted = zip(*sorted(zip(sales, categories), reverse=True)). Для временных рядов, наоборот, оставляйте хронологию: месяцы по возрастанию значения выглядят красиво, но скрывают тренд. И ещё правило редактора: если категорий больше семи, показывайте топ-6 и собирайте остальные в «Прочее» — иначе столбцы превратятся в частокол.

Собираем приёмы: рейтинг топ-5 через barh

Продажи и названия склеиваются zip-ом в пары, sorted выстраивает их по возрастанию, а barh рисует первый элемент списка внизу — в итоге лидер оказывается наверху без reverse:

Топ-5 товаров по продажам
import matplotlib.pyplot as plt

items = ["Ноутбуки", "Телефоны", "Наушники", "Планшеты", "Часы"]
units = [180, 340, 95, 130, 210]

# пары (продажи, название) по возрастанию продаж
pairs = sorted(zip(units, items))
units_sorted = [p[0] for p in pairs]
items_sorted = [p[1] for p in pairs]

fig, ax = plt.subplots()
bars = ax.barh(items_sorted, units_sorted, color="#4C9F70")
ax.bar_label(bars)                 # числа справа от столбцов
ax.set_title("Топ-5 товаров по продажам, шт.")
ax.set_xlabel("Штук")
plt.show()
Вывод
График отображается под редактором

Числа от bar_label стоят справа от столбцов и заменяют собой шкалу по нижней оси: для рейтинга это самый читаемый вариант — взгляд идёт от лидера к аутсайдеру, и каждое значение подписано.

Цвет, ширина и оформление столбцов

Настройка столбцов
import matplotlib.pyplot as plt

categories = ["Янв", "Фев", "Мар", "Апр"]
plan = [80, 95, 110, 125]

fig, ax = plt.subplots()
ax.bar(categories, plan, color="#4C9F70", width=0.6, edgecolor="black")
ax.set_title("План продаж, тыс. руб.")
ax.set_ylabel("Тыс. руб.")
plt.show()
Вывод
График отображается под редактором

width задаёт ширину столбца от 0 до 1: по умолчанию 0.8, при 1.0 столбцы сливаются в ступеньки, при 0.4 расползаются. edgecolor обводит контур — на белом фоне диаграмма выглядит собраннее. Цвет можно задать всем столбцам сразу или списком по одному — так подсвечивают лидера или проблемный месяц. Готовые стили и палитры, дружелюбные к дальтоникам, разберём в уроке про оформление графиков.

Группированные столбцы: план против факта

Классическая задача менеджера — сравнить план и факт по месяцам. Две серии столбцов требуют сдвига: позиции считаются через np.arange, а серии разъезжаются на width/2 в разные стороны:

План и факт рядом
import numpy as np
import matplotlib.pyplot as plt

months = ["Янв", "Фев", "Мар", "Апр"]
plan = [80, 95, 110, 125]
fact = [72, 99, 104, 131]

x = np.arange(len(months))            # позиции: 0, 1, 2, 3
width = 0.35

fig, ax = plt.subplots()
ax.bar(x - width / 2, plan, width, label="План")
ax.bar(x + width / 2, fact, width, label="Факт")
ax.set_xticks(x)
ax.set_xticklabels(months)
ax.set_title("План и факт продаж, тыс. руб.")
ax.set_ylabel("Тыс. руб.")
ax.legend()
plt.show()
Вывод
График отображается под редактором

Схема одна и та же в каждом втором отчёте: числовые позиции через np.arange(len(...)), ширина 0.35, первая серия сдвигается влево, вторая — вправо, а set_xticklabels возвращает месяцы на место голых чисел. Без сдвига начинается беда:

Накопленные столбцы: stacked

Вторая составная форма — столбец из слоёв. Параметр bottom говорит bar(), от какой высоты рисовать второй слой; так складывают выручку по каналам или часы работы по проектам:

Стек по каналам продаж
import matplotlib.pyplot as plt

months = ["Янв", "Фев", "Мар", "Апр"]
online = [40, 52, 61, 70]
retail = [40, 43, 49, 55]

fig, ax = plt.subplots()
ax.bar(months, online, label="Онлайн", color="#2E86AB")
ax.bar(months, retail, bottom=online, label="Розница", color="#F0A04B")
ax.set_title("Выручка по каналам, тыс. руб.")
ax.set_ylabel("Тыс. руб.")
ax.legend()
plt.show()
Вывод
График отображается под редактором

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

Подсветка знаком: рост зелёным, падение красным

Величины бывают со знаком: прирост к прошлому году, прибыль или убыток. Такую диаграмму красят по условию — и вот условная логика из Python напрямую переносится в цвет столбцов:

Цвет по условию
import matplotlib.pyplot as plt

months = ["Янв", "Фев", "Мар", "Апр", "Май", "Июн"]
growth = [5, -3, 8, -1, 4, 9]        # прирост к прошлому году, %

colors = ["#4C9F70" if g >= 0 else "#D62728" for g in growth]

fig, ax = plt.subplots()
ax.bar(months, growth, color=colors)
ax.axhline(0, color="black", linewidth=0.8)   # нулевая линия
ax.set_title("Прирост продаж к прошлому году, %")
ax.set_ylabel("%")
plt.show()
Вывод
График отображается под редактором

axhline провёл горизонтальную нулевую линию — без неё отрицательные столбцы «висят в воздухе». Приём тот же, что и вертикальная линия axvline в кейсе ниже: одна строка кода отделяет хорошие месяцы от плохих. Только договоритесь о конвенции заранее: в России красный — падение, в Китае наоборот, и в международной команде это стоило бы уточнять.

Гистограмма в Matplotlib: hist

Гистограмма похожа на столбчатую диаграмму только внешне. bar сравнивает категории — продажи по городам. hist показывает распределение одной числовой величины: диапазон значений делится на интервалы (бины), и мы считаем, сколько значений попало в каждый. Вопросы «какие у нас цены», «сколько длится доставка», «каковы чеки в кофейне» — это hist.

Критерийbarhist
Что на входеКатегории: города, товары, каналыНепрерывные числа: цены, время, возраст
Какой вопрос решает«Кто больше?»«Как распределены значения?»
Порядок столбцовНесёт смысл, часто сортируютЗадан числовой осью, менять нельзя
Ширина столбцовwidth — вопрос дизайнаЗависит от бинов, влияет на смысл
Зазоры между столбцамиОбычно естьНикогда: интервалы идут подряд

Как построить гистограмму в Matplotlib?

Распределение цен на 200 товаров
import numpy as np
import matplotlib.pyplot as plt

# Цены на 200 товаров, тыс. руб.
np.random.seed(7)
prices = np.random.lognormal(2.2, 0.45, 200)

fig, ax = plt.subplots()
ax.hist(prices, bins=20, color="#5B8FF9", edgecolor="white")
ax.set_title("Распределение цен на товары")
ax.set_xlabel("Цена, тыс. руб.")
ax.set_ylabel("Число товаров")
plt.show()
Вывод
График отображается под редактором

Что видно: длинный правый хвост — много дешёвых позиций и немного очень дорогих. Это логнормальное распределение, типичное для цен, доходов и размеров файлов. Таблица из 200 чисел этого не показывает в принципе, гистограмма — за секунду.

Обращайте внимание и на форму: один «горб» — распределение унимодальное, два горба — смесь двух групп (например, дешёвый и премиальный сегменты), симметричный колокол — примерно нормальное. Форма подсказывает следующий шаг анализа: у двух горбов стоит искать разделяющий признак, у тяжёлого хвоста — медиану вместо среднего. А статистические варианты этой картинки — kde и boxplot — мы построим в уроке про распределения в Seaborn.

Параметр bins и на что он влияет

bins — самое важное решение при построении гистограммы. Мало бинов — детали спрятаны, много — картинка рвётся в шум. Посмотрите на крайности:

Мало бинов против много
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(7)
prices = np.random.lognormal(2.2, 0.45, 200)

# Синтаксис с двумя панелями разберём в уроке 5
fig, axes = plt.subplots(1, 2, figsize=(11, 3.5))
axes[0].hist(prices, bins=5, color="#5B8FF9")
axes[0].set_title("bins=5: слишком грубо")
axes[1].hist(prices, bins=40, color="#F0A04B")
axes[1].set_title("bins=40: слишком шумно")
plt.show()
Вывод
График отображается под редактором

Стартовая эвристика — от 10 до 20 бинов; по умолчанию Matplotlib ставит bins=10. Есть и автоматика: bins="auto" подбирает число по правилу Фридмана–Диакониса. Бины можно задать и своими границами — это удобно, когда интервалы диктует бизнес: bins=[0, 5, 10, 20, 50] для ценовых сегментов. Две панели на одной картинке — это plt.subplots(1, 2): синтаксис разбирается в уроке про несколько графиков.

Как выбрать число бинов, если бизнес-логики нет? Три опоры. Правило Стерджеса: 1 + log2(n) — для двухсот значений это примерно девять бинов, для десяти тысяч — четырнадцать. Формула проста, но робеет перед тяжёлыми хвостами. Правило Фридмана–Диакониса считает ширину бина через межквартильный размах и устойчиво к выбросам — оно и спрятано за bins="auto". Дальше здравый смысл: постройте два-три варианта и выберите тот, что отвечает на вопрос задачи, — автоматика лишь точка старта.

Частота и плотность: что вернёт hist

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

Разбираем результат hist
import matplotlib.pyplot as plt

prices = [3, 4, 7, 8, 8, 9, 12, 15, 22, 40]
counts, edges, patches = plt.hist(prices, bins=[0, 5, 10, 20, 50])
print([int(c) for c in counts])   # сколько цен попало в каждый интервал
print(edges.tolist())             # границы интервалов
plt.show()
Вывод
[2, 4, 2, 2]
[0.0, 5.0, 10.0, 20.0, 50.0]

Десять цен разложились по четырём интервалам как 2, 4, 2, 2 — быстрая самопроверка: сумма counts равна числу точек (если границы покрывают весь диапазон данных). Параметр density=True нормирует высоты так, что сумма площадей бинов становится равна единице: график превращается в оценку плотности распределения. Это нужно при сравнении двух выборок разного размера — частоты у большой выборки всегда выше, плотности сравнимы.

Кейс: средний чек в кофейне

Соберём типичный аналитический график: распределение чеков плюс вертикальная линия на среднем значении. Она ставится методом axvline и мгновенно показывает перекос распределения:

Чеки кофейни с линией среднего
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(11)
# Средний чек по 300 покупкам, руб.
checks = np.random.gamma(2.0, 800, 300)

fig, ax = plt.subplots(figsize=(9, 4))
ax.hist(checks, bins=25, color="#2E86AB", edgecolor="white")
ax.axvline(checks.mean(), color="#D62728", linestyle="--", linewidth=2,
           label=f"Средний чек: {checks.mean():.0f} руб.")
ax.set_title("Распределение чеков в кофейне")
ax.set_xlabel("Чек, руб.")
ax.set_ylabel("Число покупок")
ax.legend()
plt.show()
Вывод
График отображается под редактором

Красная линия стоит правее «горба» — длинный хвост дорогих чеков тянет среднее вверх, а типичный покупатель платит меньше среднего. Это классический аргумент в спорах про «среднюю температуру по больнице»: медиана и среднее расходятся именно на перекошенных распределениях. Видеть такой перекос на гистограмме — навык, который стоит пройти весь этот курс.

Подписи значений над столбцами: bar_label

Числа над столбцами
import matplotlib.pyplot as plt

categories = ["Телефоны", "Ноутбуки", "Планшеты", "Аксессуары"]
sales = [520, 410, 180, 95]

fig, ax = plt.subplots()
bars = ax.bar(categories, sales, color="#4C9F70")
ax.bar_label(bars, fmt="%d")           # значения над столбцами
ax.set_title("Продажи по видам техники, шт.")
ax.set_ylabel("Штук")
ax.set_ylim(0, 600)                    # запас сверху для подписей
plt.show()
Вывод
График отображается под редактором

bar_label появился в Matplotlib 3.4 и закрыл многолетнюю боль: раньше числа над столбцами приходилось рисовать вручную циклом с ax.text(). bar_label работает и с barh, и с накопленными стеками. Не забудьте поднять ylim — иначе подпись самого высокого столбца упрётся в потолок графика.

Данные для таких диаграмм обычно живут в таблицах: если вы уже проходили раздел Pandas, обратите внимание, что у DataFrame есть встроенные графики — df["sales"].plot(kind="bar") вызывает тот же Matplotlib. Синтаксис сокращается, а все приёмы из этого урока остаются применимы.

Что дальше

Вы умеете сравнивать категории (bar, barh), группировать и складывать серии, смотреть распределения (hist, bins, density) и подписывать значения. Следующий урок — про связи: диаграмма рассеяния scatter покажет, как рост связан с весом, а площадь квартиры — с её ценой, и что делать, когда точек десять тысяч. Ориентироваться в остальном курсе помогает самоучитель Matplotlib — все десять уроков на одной странице.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import matplotlib.pyplot as plt

data = [1, 2, 2, 3, 10]
counts, edges, patches = plt.hist(data, bins=[0, 2, 4, 6, 8, 10])
print([int(c) for c in counts])
import matplotlib.pyplot as plt

bars = plt.bar(["A", "B", "C", "D"], [5, 12, 9, 3])
print(len(bars))
Проверь себя
0 / 5

1. Чем гистограмма отличается от столбчатой диаграммы?

2. Какое значение width в bar сделает столбцы вплотную друг к другу?

3. Что возвращает plt.hist()?

4. Что делает ax.bar_label(bars)?

5. Сколько бинов у hist по умолчанию?

Карточки терминов
Запомнено: 0 / 6
Практика

Магазин: продажи по категориям — Электроника 520, Одежда 410, Книги 180, Аксессуары 95 штук. Постройте горизонтальную столбчатую диаграмму barh с заголовком «Продажи по категориям», зелёными столбцами (#4C9F70) и подписями значений через bar_label. Подпишите ось X «Штук».

practice.py
Вопросы и ответы по уроку

Как задать ширину бина в гистограмме?

Параметр bins задаёт количество корзин, а ширина бина получается делением диапазона данных на это количество: при ценах от 50 до 150 и bins=10 ширина бина равна 10. Нужны корзины точной ширины — передайте список границ: bins=[50, 60, 70] сделает бины по 10 принудительно. Разница важна на данных с выбросами: фиксированные границы не дадут одному хвосту съесть весь масштаб.

Как построить гистограмму в Python?

plt.hist(данные, bins=20). Данные — список или массив чисел, bins задаёт число интервалов. Для аккуратного вида добавьте edgecolor="white", чтобы бины отделялись контуром.

Чем гистограмма отличается от столбчатой диаграммы?

Гистограмма показывает распределение непрерывной величины по интервалам, столбчатая диаграмма сравнивает отдельные категории. Ориентир: категории — bar(), числа — hist().

Сколько бинов выбирать в гистограмме?

Начните с 10–20 и подстройте под задачу: мало бинов прячет детали, много создаёт шум. Есть автоматика bins="auto" — она подбирает число по правилу Фридмана–Диакониса.

Как подписать столбцы диаграммы значениями?

bars = ax.bar(...); ax.bar_label(bars). Формат числа настраивается параметром fmt, например fmt="%d" для целых. Работает и для barh, и для накопленных стеков.

Как построить столбчатую диаграмму в Matplotlib?

ax.bar(категории, высоты): список категорий по X и список чисел по Y — подписи под столбцами Matplotlib ставит сам. Длинные названия категорий выносит на горизонтальный barh, значения над столбцами подписывает ax.bar_label(bars).

Понравился урок? Сошлитесь на него

«Мало бинов — детали спрятаны, много — картинка рвётся в шум.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.

Проверьте знания по Matplotlib / Seaborn

В челлендже — 20 задач по Matplotlib / Seaborn, по 2 из каждого урока этого раздела. Формат: фрагмент кода и четыре варианта — что напечатает. После ответа — вердикт и объяснение со ссылкой на урок-источник.

Тест по Matplotlib / Seaborn: 20 задач