Перейти к содержанию
Educora
Университет25 мин33 / 42

Визуализация в matplotlib

Строй линейные графики, столбчатые диаграммы, диаграммы рассеяния, гистограммы и подграфики, правильно подписывай оси, заголовки и легенды, выбирай число интервалов и изучи правила честной и понятной графики.

Проверь себя
В этом уроке ты узнаешь
  • Строить график объектно-ориентированным способом через fig, ax = plt.subplots() и добавлять заголовок, подписи осей и легенду
  • Выбирать тип графика под вопрос: линия, столбцы, рассеяние, гистограмма
  • Обосновывать число интервалов гистограммы по правилам Стёрджеса и Фридмана–Диакониса
  • Распознавать вводящие в заблуждение графики и строить честные и понятные

В 1973 году статистик Фрэнсис Энскомб опубликовал четыре небольших набора данных, у которых средние, дисперсии, коэффициент корреляции и линия регрессии почти одинаковы. Но на графике один набор рассеян вокруг прямой, другой лежит на кривой, в третьем один выброс меняет всё, а в четвёртом все точки, кроме одной, стоят на вертикальной линии. Вывод: прежде чем верить числам, на данные нужно посмотреть. Главный инструмент для этого в Python — matplotlib.

Figure и Axes: как устроен график

Определение
Figure и Axes

Figure — всё изображение, «холст». Axes — одна область графика внутри него: оси, линии, заголовок, легенда. В одной Figure может быть несколько Axes. Большинство методов принадлежит Axes: ax.plot, ax.set_title, ax.legend.

У matplotlib два стиля: краткий стиль «pyplot», например plt.plot(...), и объектно-ориентированный. Всегда начинай со второго: fig, ax = plt.subplots() — такой код остаётся понятным и при нескольких графиках. В браузере картинка появляется под кодом автоматически; на компьютере plt.show() открывает окно, а fig.savefig('chart.png', dpi=200) сохраняет файл.

Python
import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
baku = [279, 153, 426, 390, 455, 510]
ganja = [150, 306, 168, 210, 240, 265]

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(months, baku, marker='o', label='Baku')
ax.plot(months, ganja, marker='s', linestyle='--', label='Ganja')
ax.set_title('Monthly revenue by branch, 2026')
ax.set_xlabel('Month')
ax.set_ylabel('Revenue, AZN')
ax.grid(alpha=0.3)
ax.legend()
plt.show()
Линейный график показывает изменение во времени. Маркеры отмечают точки измерений, а разные стили линий различают ряды даже для тех, кто плохо различает цвета.
  • Заголовок — что показывает график, где и когда.
  • Подписи осей с единицами: «Revenue, AZN», «Height, cm».
  • Легенда (legend) — если рядов больше одного.
  • Бледная сетка (grid(alpha=0.3)) — облегчает чтение значений и не отвлекает.

Выбор типа графика

ВопросГрафикmatplotlib
Как меняется со временем?линейный графикax.plot
Как соотносятся категории?столбчатая диаграммаax.bar, ax.barh
Связаны ли две числовые переменные?диаграмма рассеянияax.scatter
Как распределены значения?гистограмма, «ящик с усами»ax.hist, ax.boxplot
Python
import numpy as np
import matplotlib.pyplot as plt

products = np.array(['Coffee', 'Cake', 'Tea', 'Juice', 'Sandwich'])
revenue = np.array([564, 540, 378, 212, 305])
order = np.argsort(revenue)

fig, ax = plt.subplots(figsize=(6, 3.5))
bars = ax.barh(products[order], revenue[order], color='#4C72B0')
ax.bar_label(bars, fmt='%d AZN', padding=3)
ax.set_xlim(0, 680)
ax.set_xlabel('Revenue, AZN')
ax.set_title('Revenue by product, Q1 2026')
print(products[order][-1], revenue.max())
plt.show()
▸ Ожидаемый результат
Coffee 564

Диаграмма рассеяния показывает связь между двумя числовыми переменными. Цвет может передавать третью переменную (c=, cmap=), а np.polyfit(x, y, 1) находит линию тренда методом наименьших квадратов. Данные ниже выдуманы: площадь квартиры, число комнат и цена.

Python
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(3)
area = rng.uniform(40, 140, 60)
rooms = np.clip(area // 30, 1, 4).astype(int)
price = 1.5 * area + 10 * rooms + rng.normal(0, 15, 60)
slope, intercept = np.polyfit(area, price, 1)
print(f'slope: {slope:.2f} thousand AZN per m2')
print(f'correlation r = {np.corrcoef(area, price)[0, 1]:.3f}')

fig, ax = plt.subplots(figsize=(6, 4))
points = ax.scatter(area, price, c=rooms, cmap='viridis', alpha=0.8)
xs = np.array([40, 140])
ax.plot(xs, slope * xs + intercept, color='red', label='trend line')
fig.colorbar(points, ax=ax, label='rooms')
ax.set_xlabel('Area, m²')
ax.set_ylabel('Price, thousand AZN')
ax.legend()
plt.show()
▸ Ожидаемый результат
slope: 2.00 thousand AZN per m2
correlation r = 0.960
Цветовая шкала viridis читается и при чёрно-белой печати, и людьми с нарушениями цветового зрения. Коэффициент корреляции r подробно разбирается в следующем уроке.

Гистограмма и число интервалов

Гистограмма делит ось значений на равные интервалы (bins) и считает наблюдения, попавшие в каждый. Если интервалов слишком мало, форма распределения теряется; если слишком много — график превращается в шумную «расчёску». Два известных правила дают отправную точку:

k = ⌈log₂ n⌉ + 1 h = 2 · IQR · n^(−1/3)
где:
  • kчисло интервалов (правило Стёрджеса)
  • hширина интервала (правило Фридмана–Диакониса), в единицах данных
  • nчисло наблюдений
  • IQRмежквартильный размах Q3 − Q1

Правило Стёрджеса хорошо для примерно нормальных и не слишком больших данных; правило Фридмана–Диакониса устойчиво к выбросам. ax.hist(x, bins='fd') применяет второе автоматически.

Python
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
heights = rng.normal(170, 8, 500)
k = int(np.ceil(np.log2(heights.size))) + 1
print('Sturges bins:', k)
print(f'mean = {heights.mean():.1f} cm, std = {heights.std(ddof=1):.1f} cm')

fig, ax = plt.subplots(figsize=(6, 4))
ax.hist(heights, bins=k, edgecolor='white')
ax.axvline(heights.mean(), color='red', linestyle='--', label='mean')
ax.set_xlabel('Height, cm')
ax.set_ylabel('Number of people')
ax.legend()
plt.show()
▸ Ожидаемый результат
Sturges bins: 10
mean = 169.9 cm, std = 7.7 cm
Пример 1: интервалы для роста 500 человек

n = 500, рост распределён примерно нормально, σ ≈ 8 см. Найди интервалы по правилам Стёрджеса и Фридмана–Диакониса. Для нормального распределения IQR ≈ 1,35σ.

Показать решение
Стёрджес: log₂ 500 ≈ 8,97 → ⌈8,97⌉ = 9 → k = 9 + 1 = 10 интервалов (как в коде).
Фридман–Диаконис: IQR ≈ 1,35 · 8 = 10,8 см; n^(1/3) = 500^(1/3) ≈ 7,94.
h = 2 · 10,8 / 7,94 ≈ 2,7 см.
Размах около 170 ± 3σ, то есть ~48 см → 48 / 2,7 ≈ 18 интервалов.
FD показывает больше деталей; с ростом n правило Стёрджеса добавляет интервалы очень медленно.
fᵢ = nᵢ / (n · h) ∑ fᵢ · h = 1fᵢ = nᵢ / (n · h) ∑ fᵢ · h = 1
где:
  • nᵢнаблюдения в i-м интервале
  • fᵢвысота столбца при density=True (плотность)
  • hширина интервала

ax.hist(x, density=True) делает сумму площадей столбцов равной 1 — такую гистограмму можно сравнивать с кривой плотности вероятности.

Пример 2: гистограмма плотности

200 измерений разбиты на интервалы ширины h = 2. В одном интервале 30 измерений. Какова высота этого столбца при density=True? Что показывает его площадь?

Показать решение
f = 30 / (200 · 2) = 0,075.
Площадь = f · h = 0,075 · 2 = 0,15, то есть в этом интервале 15% измерений (30 / 200).
Сумма площадей всех столбцов равна 1 (100%).

Несколько графиков на одном рисунке

plt.subplots(2, 2) возвращает массив Axes размера 2 × 2; к каждому графику обращаются через axes[строка, столбец]. layout='constrained' не даёт заголовкам и осям налезать друг на друга, а sharex=True делает оси общими.

Python
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(0)
x = np.linspace(0, 2 * np.pi, 100)
fig, axes = plt.subplots(2, 2, figsize=(8, 6), layout='constrained')
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('Line: sin x')
axes[0, 1].bar(['A', 'B', 'C'], [5, 3, 7])
axes[0, 1].set_title('Bar')
axes[1, 0].scatter(rng.random(30), rng.random(30))
axes[1, 0].set_title('Scatter')
axes[1, 1].hist(rng.normal(size=300), bins=15)
axes[1, 1].set_title('Histogram')
fig.suptitle('Four basic chart types')
print(axes.shape)
plt.show()
▸ Ожидаемый результат
(2, 2)

В pandas есть собственный метод .plot(), построенный на matplotlib: каждый столбец DataFrame становится рядом, а индекс уходит на ось X. Метод возвращает Axes, поэтому график можно доработать обычными командами matplotlib.

Python
import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'month': ['Jan', 'Feb', 'Mar'],
                   'Baku': [279, 153, 426],
                   'Ganja': [150, 306, 168]}).set_index('month')
ax = df.plot(kind='bar', figsize=(6, 3.5), rot=0, title='Revenue by month, AZN')
ax.set_ylabel('AZN')
print(df.sum())
plt.show()
▸ Ожидаемый результат
Baku     858
Ganja    624
dtype: int64

Правила хорошего графика

  1. Один график — одна мысль. Пиши вывод в заголовке: «В марте лидирует бакинский филиал».
  2. На столбчатой диаграмме ось Y должна начинаться с 0, потому что глаз сравнивает длины столбцов.
  3. Цвет должен нести смысл: выделяемому ряду — яркий цвет, остальным — серый.
  4. Избегай 3D-эффектов, теней и круговых диаграмм со множеством секторов — они искажают значения.
Python
import matplotlib.pyplot as plt

branches = ['Baku', 'Ganja']
satisfied = [96, 98]
fig, (bad, good) = plt.subplots(1, 2, figsize=(8, 3.5), layout='constrained')
bad.bar(branches, satisfied, color=['gray', 'orange'])
bad.set_ylim(95, 98.5)
bad.set_title('Misleading: axis starts at 95')
good.bar(branches, satisfied, color=['gray', 'orange'])
good.set_ylim(0, 100)
good.set_title('Honest: axis starts at 0')
for ax in (bad, good):
    ax.set_ylabel('Satisfied customers, %')
plt.show()
Слева столбец Гянджи кажется втрое длиннее бакинского, хотя разница всего 2 процентных пункта.
Задание

Построй горизонтальную столбчатую диаграмму (ax.barh) по словарю revenue, задай заголовок 'Revenue by product' и подпиши ось X как 'AZN'. Затем выведи на отдельных строках: 1) заголовок графика (ax.get_title()); 2) число столбцов на графике (len(ax.patches)); 3) товар с наибольшей выручкой.

Задание · Python
import matplotlib.pyplot as plt

revenue = {'Coffee': 564, 'Cake': 540, 'Tea': 378, 'Juice': 212, 'Sandwich': 305}
fig, ax = plt.subplots()
# draw the bars, set the title and the X label, then print the three results
▸ Ожидаемый результат
Revenue by product
5
Coffee
Задание

Построй гистограмму баллов за тест с 5 интервалами. ax.hist возвращает три объекта: количества, границы интервалов и столбцы. Выведи на отдельных строках: 1) количество в каждом интервале списком целых чисел; 2) ширину одного интервала, округлённую до 2 знаков после запятой.

Задание · Python
import matplotlib.pyplot as plt

scores = [55, 62, 68, 70, 71, 75, 78, 80, 82, 85, 88, 90, 94, 97]
fig, ax = plt.subplots()
# counts, edges, _ = ax.hist(...)
▸ Ожидаемый результат
[2, 3, 3, 3, 3]
8.4

Главное

  • Figure — всё изображение, Axes — график внутри него; начинай с fig, ax = plt.subplots().
  • Время → линия, категории → столбцы, две числовые переменные → рассеяние, распределение → гистограмма.
  • На каждом графике нужны заголовок, подписи осей с единицами и при необходимости легенда.
  • Интервалы: Стёрджес k = ⌈log₂ n⌉ + 1, Фридман–Диаконис h = 2 · IQR · n^(−1/3).
  • Ось столбчатой диаграммы начинается с 0; для цветовой шкалы выбирай viridis.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Какой график лучше всего подходит для сравнения годовой выручки пяти филиалов?