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

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

Начать обучение
Урок 9 из 10 Средний 40 мин 150 XP

Тепловые карты и pairplot: heatmap и матрицы диаграмм

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

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

На почту упал файл: десять квартир, пять параметров — площадь, комнаты, этаж, цена, минуты до метро. Заказчик просит «понять, от чего зависит цена». Классический разведочный анализ (EDA, exploratory data analysis) — это десять диаграмм рассеяния и пара часов возни. Seaborn сжимает его до пяти строк: sns.heatmap покажет все связи чисел одной картинкой, sns.pairplot — каждым столбиком против каждым. В этом уроке собираем протокол «первый взгляд на датасет», который вы будете применять к каждой новой таблице.

Корреляционная матрица: df.corr() одной строкой

Корреляция Пирсона r — число от −1 до 1, которое меряет линейную связь двух величин: r = 1 — растут строго вместе, r = −1 — одна растёт, другая падает синхронно, r = 0 — линейной связи не видно. У pandas есть метод corr(): вызываете его на таблице — и он посчитает r для каждой пары числовых столбцов. Вот наш файл с квартирами:

Корреляционная матрица apartments
import pandas as pd

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})

corr = apts.corr(numeric_only=True)
print(corr.round(2))
Вывод
           area  rooms  floor  price  metro_min
area       1.00   0.98  -0.05   0.99      -0.50
rooms      0.98   1.00  -0.13   0.97      -0.55
floor     -0.05  -0.13   1.00  -0.09      -0.18
price      0.99   0.97  -0.09   1.00      -0.43
metro_min -0.50  -0.55  -0.18  -0.43       1.00
Первый запуск загружает библиотеки — до минуты

Матрица отвечает на вопрос заказчика ещё до всяких графиков: цена почти идеально ходит за площадью (r = 0.99) и комнатами (r = 0.97), минуты до метро давят на цену в обратную сторону (r = −0.43 у пары с ценой), а этаж — посторонний (r = −0.09). Диагональ сплошных единиц — это каждый столбец с самим собой, матрица симметрична: r площади с ценой равен r цены с площадью.

Числа есть. Но 25 ячеек глазами сравнивать утомительно, а на двадцати колонках их будет четыреста. Тут и приходит тепловая карта.

heatmap: тепловая карта корреляций с аннотациями

sns.heatmap заливает каждую ячейку таблицы цветом: большие значения — одним концом палитры, малые — другим. Четыре параметра делают её читаемой: annot=True рисует сами числа в ячейках, fmt=".2f" оставляет два знака после запятой, cmap="coolwarm" берёт расходимую палитру (минус — синий, плюс — красный, ноль — белый), а vmin=−1 и vmax=1 фиксируют шкалу ровно на диапазоне корреляций.

Как построить тепловую карту в Python?

Тепловая карта корреляций
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})
corr = apts.corr(numeric_only=True)

sns.set_theme()
ax = sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm",
                 vmin=-1, vmax=1, square=True, linewidths=0.5)
ax.set_title("Корреляции параметров квартир")
plt.show()
Вывод
График отображается под редактором

Теперь матрица читается за секунду: красный блок площадь-комнаты-цена кричит о сильной прямой связи, синяя полоска metro_min — об обратной, этаж выглядит серо-белым — связи нет. square=True держит ячейки квадратными, linewidths=0.5 разделяет их тонкими линиями. Цветовая шкала справа (colorbar) пришла автоматически — она же служит легендой.

Как выбрать cmap и зачем vmin и vmax

Палитры делятся на два семейства. Расходимые (diverging): coolwarm, RdBu_r, PiYG — с явным центром посередине; их место — корреляции и любые отклонения от нуля, потому что «ниже нуля» и «выше нуля» честно красятся в разные цвета. Последовательные (sequential): viridis, rocket, Blues — от тёмного к светлому, для величин, у которых нет отрицательных значений: население, выручка, частоты. Расходимую палитру на частотах и последовательную на корреляциях использовать можно, но читатель потратит лишние секунды на расшифровку.

vmin и vmax — это якоря шкалы. Без них Seaborn растянет палитру от минимума до максимума именно ваших данных, и две тепловые карты — до рефакторинга и после — окажутся в неравных условиях: 0.5 на одной будет ярким, на другой блёклым. Для корреляций диапазон известен заранее, −1 и 1, — фиксируйте его всегда.

Бонус-приём: срежьте у матрицы один столбец — и получите «рейтинг влияния» на целевую переменную:

Корреляции с ценой: одна колонка матрицы
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})
corr = apts.corr(numeric_only=True)

sns.set_theme()
ax = sns.heatmap(corr[["price"]].drop(index="price"),
                 annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1)
ax.set_title("Корреляции с ценой: кто влияет")
plt.show()
Вывод
График отображается под редактором

corr[["price"]] оставляет одну колонку матрицы, drop(index="price") убирает тривиальную единицу цены с самой собой. Такая вертикальная полоска — быстрый ответ на вопрос «какие признаки вообще смотреть»: площадь и комнаты — да, этаж — мимо.

На презентациях половину матрицы можно спрятать: верхний треугольник повторяет нижний зеркально. Параметр mask принимает булеву маску той же формы — там, где True, ячейка не рисуется. Собирается маска двумя numpy-функциями:

Маска: рисуем только нижний треугольник
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})
corr = apts.corr(numeric_only=True)
mask = np.triu(np.ones_like(corr, dtype=bool), k=1)

sns.set_theme()
ax = sns.heatmap(corr, mask=mask, annot=True, fmt=".2f",
                 cmap="coolwarm", vmin=-1, vmax=1, square=True,
                 linewidths=0.5)
ax.set_title("Корреляции: только нижний треугольник")
plt.show()
Вывод
График отображается под редактором

ones_like построил решётку единиц той же формы, что и corr, np.triu превратил её в верхний треугольник, а k=1 срезал ещё и диагональ с её бессмысленными единицами. Остались десять уникальных пар — читатель не тратит взгляд на дубли.

pairplot: весь датасет на одном рисунке

Тепловая карта говорит, что связи есть, но не показывает их форму — а форма бывает коварной. sns.pairplot рисует матрицу диаграмм: по диагонали — распределение каждой переменной, вне диагонали — диаграмма рассеяния для каждой пары. Пять колонок — 25 панелей, и всё это два вызова функций:

pairplot по всем колонкам
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})

sns.set_theme()
g = sns.pairplot(apts, diag_kind="kde", plot_kws={"s": 40, "alpha": 0.7})
g.figure.suptitle("Параметры квартир: матрица диаграмм", y=1.02)
plt.show()
Вывод
График отображается под редактором

Читать матрицу просто: строка area, колонка price — это облако точек «площадь против цены», и оно вытянуто в линию: r = 0.99 подтверждается глазами. Диагональ мы переключили с гистограмм на kde-кривые параметром diag_kind="kde" — так форма распределения видна лучше. Верхняя половина зеркальна нижней, оси подписаны именами колонок, plot_kws передал размеры точек и прозрачность внутрь каждой панели.

Параметры pairplot, которые экономят время

  • vars=["area", "price", "metro_min"] — рисовать только выбранные колонки: с 20 переменных снимает 400-панельную стену.
  • hue="district" — раскрасить все панели по категориальному столбцу, легенда появится сама.
  • diag_kind="kde" — на диагонали кривые плотности вместо гистограмм.
  • corner=True — оставить только нижний треугольник: зеркальные пары не несут новой информации.
  • plot_kws={"s": 20, "alpha": 0.4} — мелкие полупрозрачные точки против оверплоттинга на больших данных.
pairplot с раскраской по району
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
    "district": ["Центр", "Спальный", "Центр", "Спальный", "Центр",
                 "Центр", "Спальный", "Спальный", "Центр", "Спальный"],
})

sns.set_theme()
g = sns.pairplot(apts, vars=["area", "price", "metro_min"],
                 hue="district", diag_kind="kde", corner=True)
plt.show()
Вывод
График отображается под редактором

hue сработал как в седьмом уроке про основы Seaborn: каждый scatter и каждая кривая расщепились по районам, легенда встроилась сама. Если цвет разделил облака — переменная полезна для классификации; если цвета перемешались — признаки различают группы слабо. Это уже машинное обучение на минималях, а выглядит как три строки кода.

jointplot: пара переменных крупным планом

Когда pairplot указал на интересную пару, переходите к jointplot: scatter в центре плюс гистограммы каждой переменной по краям — связь и распределения на одном рисунке. Три строки:

jointplot: площадь против цены
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
})

sns.set_theme()
g = sns.jointplot(data=apts, x="area", y="price", height=4.5)
g.ax_joint.set_title("Площадь и цена: пара крупным планом")
plt.show()
Вывод
График отображается под редактором

Края рассказывают то, что теряется в чистом scatter: справа — распределение площади, сверху — цены. Параметр kind меняет центральную панель: kind="hex" складывает плотность точек в шестиугольники (спасение от оверплоттинга), kind="kde" рисует уровни плотности, как изолинии на карте, kind="reg" добавляет прямую регрессии.

EDA за пять строк: протокол разведочного анализа

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

  1. df.head() и df.describe() — что за колонки, какие масштабы, есть ли пропуски.
  2. corr = df.corr(numeric_only=True) — матрица связей числовых колонок.
  3. sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1) — связи одной картинкой.
  4. sns.pairplot(df, vars=[3-4 самые связанные колонки], corner=True) — форма связей глазами.
  5. sns.jointplot для пары-чемпиона — детали с распределениями по краям.
Шаг протокола: корреляции с целевой переменной
import pandas as pd

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "rooms": [1, 2, 3, 3, 4, 5, 3, 1, 5, 2],
    "floor": [3, 7, 2, 5, 9, 4, 1, 8, 6, 11],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
    "metro_min": [14, 9, 21, 16, 7, 8, 24, 19, 5, 23],
})

target = apts.corr(numeric_only=True)["price"].round(2).sort_values()
print(target)
Вывод
metro_min   -0.43
floor       -0.09
rooms        0.97
area         0.99
price        1.00
Name: price, dtype: float64

Отсортированный столбец corr()["price"] — готовый шорт-лист для модели и для отчёта: цена держится на площади и комнатах, метро тянет вниз слабо, этаж не влияет вообще. Пять строк, а заказчик уже получил черновой ответ на свой вопрос.

Корреляция не причинность: как heatmap помогает врать

Красная ячейка r = 0.99 так и провоцирует вывод «площадь влияет на цену». Иногда так и есть. Но корреляция фиксирует только синхронность: обе величины могут быть следствием третьей (комнаты растут с площадью — и обе с ценой), совпасть случайно на короткой выборке или искажаться выбросом. Самое опасное — r, посчитанный по грязным данным:

Один выброс сдвинул корреляцию с 0.99 до 0.83
import pandas as pd

apts = pd.DataFrame({
    "area": [28, 42, 55, 60, 75, 88, 64, 35, 96, 51],
    "price": [8.5, 11.0, 15.5, 14.0, 19.5, 23.0, 18.0, 9.5, 24.0, 14.5],
})

print(apts["area"].corr(apts["price"]).round(2))
apts.loc[8, "price"] = 52.0   # выброс: срочная продажа элитного пентхауса
print(apts["area"].corr(apts["price"]).round(2))
Вывод
0.99
0.83

Одна ячейка таблицы — и корреляция просела на 16 сотых, а на маленькой выборке она бы рухнула куда сильнее. Обратная ловушка: у нелинейных связей r бывает равен нулю при очевидной зависимости — парабола U-формы симметрична, и линейная корреляция её не видит. Для монотонных, но нелинейных связей берите method="spearman": df.corr(method="spearman") меряет не прямую линию, а согласованность рангов — «растут ли величины вместе, пусть и с разной скоростью». И не забывайте про третью переменную: классика учебников — продажи мороженого и число утоплений ходят в паре с r около 0.7, но топит не мороженое, а жара, которая гонит людей и в воду, и за мороженым.

Поэтому протокол и заканчивается pairplot, а не heatmap: числа показывают силу, глаза — форму. Если ячейка красная, а облако точек — хаос, верьте облаку; если ячейка серая, а облако выгнулось дугой — у вас нелинейная связь, и r её спрятал.

Корреляция — это две величины, танцующие в паре. Причинность — когда один ведёт. По матрице видно только танец.

Что дальше

Арсенал разведочного анализа собран: распределения показывают каждую переменную, heatmap и pairplot — связи между ними. Осталось собрать всё в презентабельную форму: в десятом, финальном уроке строим дашборд 2×2 по данным о фильмах — четыре панели, suptitle, чек-лист «график готов к показу заказчику». Пришли из поиска за одной heatmap? Полный маршрут из десяти уроков — самоучитель Matplotlib, и связи между переменными здесь девятая ступень. Возьмите с этого урока привычку: любая новая таблица сначала проходит пять строк EDA, потом обсуждаются выводы. Цветная матрица экономит часы споров — там, где вчера спорили на ощупь, теперь красная ячейка.

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

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

import pandas as pd

df = pd.DataFrame({"x": [1, 2, 3, 4], "y": [10, 20, 30, 40]})
print(round(df["x"].corr(df["y"]), 2))
import pandas as pd
import seaborn as sns

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [2, 4, 5, 9], "c": [3, 1, 4, 2]})
g = sns.pairplot(df, vars=["a", "b"])
print(g.axes.shape)
Проверь себя
0 / 5

1. Что рисуется на диагонали матрицы pairplot?

2. Корреляция r = −0.9 между минутами до метро и ценой. Что это значит?

3. Зачем в heatmap корреляций указывать vmin=-1 и vmax=1?

4. Что делает параметр annot=True в sns.heatmap?

5. Сколько панелей рисует sns.pairplot(df, vars=["a", "b", "c", "d"])?

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

В редакторе — таблица квартир: area (метры), rooms, price (млн руб.), metro_min (минуты до метро). Постройте тепловую карту корреляций: corr = df.corr(numeric_only=True), затем heatmap с annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1, заголовком «Корреляции параметров квартир» и square=True. По карте ответьте: какой параметр сильнее всех связан с ценой?

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

Что показывает тепловая карта (heatmap) в Seaborn?

Таблицу, где каждая ячейка окрашена по значению: чем «горячее» цвет, тем больше число. Чаще всего её строят по корреляционной матрице, но подойдёт любая двумерная таблица — продажи по месяцам и регионам, посещаемость по дням недели и часам.

Как построить heatmap по корреляционной матрице pandas?

Две строки: corr = df.corr(numeric_only=True) и sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1). Аннотации показывают числа, шкала от −1 до 1 делает картинку сравнимой с другими графиками.

Что показывает pairplot в Seaborn?

Матрицу диаграмм: на диагонали — распределение каждой переменной, вне диагонали — диаграммы рассеяния всех пар. Это способ за один взгляд увидеть форму всех связей в датасете; на широких таблицах сужайте набор параметром vars=.

Что означает отрицательная корреляция и может ли r быть равен нулю?

Отрицательный r — обратная связь: одна величина растёт, другая падает (минуты до метро и цена). r = 0 означает отсутствие именно линейной связи — нелинейные зависимости вроде параболы корреляция Пирсона может не заметить, поэтому матрицу дополняют pairplot.

Когда использовать method="spearman" вместо Пирсона в pandas?

Пирсон меряет прямую линию, Спирман — согласованность рангов. Если связь монотонная, но не по прямой (растут вместе, но с разной скоростью), передайте df.corr(method="spearman"): параболу U-формы Пирсон может оценить нулём, а ранги её увидят. Матрицу всё равно дополняйте pairplot: если ячейка красная, а облако точек — хаос, верьте облаку.

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

«Корреляция — это две величины, танцующие в паре. Причинность — когда один ведёт. По матрице видно только танец.»

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

TelegramVK

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

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

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

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

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