Столбчатые диаграммы и гистограммы в 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:
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.
| Критерий | bar | hist |
|---|---|---|
| Что на входе | Категории: города, товары, каналы | Непрерывные числа: цены, время, возраст |
| Какой вопрос решает | «Кто больше?» | «Как распределены значения?» |
| Порядок столбцов | Несёт смысл, часто сортируют | Задан числовой осью, менять нельзя |
| Ширина столбцов | width — вопрос дизайна | Зависит от бинов, влияет на смысл |
| Зазоры между столбцами | Обычно есть | Никогда: интервалы идут подряд |
Как построить гистограмму в Matplotlib?
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 считает, сколько значений попало в каждый бин — частоту. Функция возвращает тройку: высоты бинов, границы и прямоугольники. С этим можно работать как с обычными массивами:
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))
1. Чем гистограмма отличается от столбчатой диаграммы?
2. Какое значение width в bar сделает столбцы вплотную друг к другу?
3. Что возвращает plt.hist()?
4. Что делает ax.bar_label(bars)?
5. Сколько бинов у hist по умолчанию?
Магазин: продажи по категориям — Электроника 520, Одежда 410, Книги 180, Аксессуары 95 штук. Постройте горизонтальную столбчатую диаграмму barh с заголовком «Продажи по категориям», зелёными столбцами (#4C9F70) и подписями значений через bar_label. Подпишите ось X «Штук».
Как задать ширину бина в гистограмме?
Параметр 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-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
Matplotlib / Seaborn · Урок 2
Линейные графики в Matplotlib: plot, цвета, стили и подписи
plt.plot() под микроскопом: цвета, маркеры, стили линий, легенды, диапазоны осей и кейс с двумя температурными кривыми за месяц.
Matplotlib / Seaborn · Урок 4
Диаграммы рассеяния scatter: ищем связи между величинами
scatter для поиска связей между величинами: форма облака точек, цвет и размер как третье измерение, alpha против оверплоттинга и кейс с 300 измерениями.
Matplotlib / Seaborn · Урок 8
Распределения в Seaborn: histplot, boxplot и violinplot
Boxplot отвечает на вопрос «где медиана, каков разброс и кто выбивается» быстрее любой таблицы. Разбираем histplot с kde, ящик с усами и violinplot на ценах квартир — и учимся сравнивать группы через hue.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
openpyxl · Урок 12
Диаграммы в Excel через openpyxl: BarChart и LineChart
Собираем настоящие диаграммы Excel из кода: BarChart по продажам недели и LineChart по выручке — Reference, add_data, категории, якорь и размеры в сантиметрах.
openpyxl диаграммаopenpyxl chart Reference
Pandas · Урок 4
Очистка данных в Pandas: пропуски NaN, дубликаты и типы
Настоящая грязная выгрузка из CRM: пропуски NaN, дубликаты и цены-строки. Чистим её isna, dropna, fillna, drop_duplicates и astype — шаг за шагом.
обработка отсутствующих значений pandasочистка данных python pandas
Matplotlib / Seaborn · Урок 5
Круговые диаграммы и subplots: несколько графиков на одном полотне
pie и donut с честными границами применимости, сетка графиков через plt.subplots, общий заголовок suptitle и мини-дашборд 2 на 2 в финале.
круговая диаграмма pythonmatplotlib subplots
Проверьте знания по Matplotlib / Seaborn
В челлендже — 20 задач по Matplotlib / Seaborn, по 2 из каждого урока этого раздела. Формат: фрагмент кода и четыре варианта — что напечатает. После ответа — вердикт и объяснение со ссылкой на урок-источник.
Тест по Matplotlib / Seaborn: 20 задач