Диаграммы рассеяния scatter: ищем связи между величинами
scatter для поиска связей между величинами: форма облака точек, цвет и размер как третье измерение, alpha против оверплоттинга и кейс с 300 измерениями.
Редакция Питоники
Перед вами выгрузка из CRM: 80 квартир, у каждой — площадь и цена. Среднее посчитано, медиана тоже, а вопрос заказчика «метр в новостройках правда дороже?» таблица не отвечает: связь двух величин в отдельных столбцах не видна в принципе. Диаграмма рассеяния отвечает мгновенно: каждая квартира становится точкой, и облако точек показывает связь раньше, чем вы дочитаете заголовок отчёта.
Это четвёртый урок курса Matplotlib. Линейные графики мы разбирали в уроке про plot, цвета и подписи, столбчатые диаграммы и гистограммы — в уроке про bar и hist. Сегодня scatter — главный инструмент разведочного анализа: если две величины в данных как-то связаны, первым это покажет именно он.
Как построить диаграмму рассеяния: plt.scatter
Нажмите «Запустить» — код исполнится настоящим Python прямо в браузере, и график появится под редактором. Первый запуск загружает библиотеку — до минуты, повторные срабатывают мгновенно:
import matplotlib.pyplot as plt
# Площадь (кв. м) и цена (млн руб.) восьми квартир
area = [28, 34, 39, 45, 52, 58, 63, 71]
price = [4.2, 5.1, 5.6, 6.9, 7.4, 8.8, 9.1, 11.2]
fig, ax = plt.subplots()
ax.scatter(area, price)
ax.set_title("Квартиры: площадь и цена")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
plt.show()
График отображается под редактором
У scatter два обязательных аргумента: координаты по X и по Y. Длины списков обязаны совпадать — каждой площади соответствует своя цена. Дальше шаблон, знакомый по первым урокам: set_title, подписи осей, show(). Теперь главное — форма облака. Точки вытянулись из левого нижнего угла в правый верхний: больше площадь — дороже квартира. Мы только что увидели связь двух величин, не вычислив ни одного числа.
Чем scatter отличается от plot
plot() принимает те же два списка — и соединит точки линией по порядку. Но порядка здесь нет: квартира 39 м² не находится «в процессе» между 34 и 45 м², это просто отдельные объекты. Линия будет утверждать то, чего в данных нет, и читатель поверит в плавный переход между несуществующими квартирами:
import matplotlib.pyplot as plt
area = [28, 34, 39, 45, 52, 58, 63, 71]
price = [4.2, 5.1, 5.6, 6.9, 7.4, 8.8, 9.1, 11.2]
fig, ax = plt.subplots()
ax.plot(area, price) # соединит точки линией
ax.set_title("plot придумывает переходы между точками")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
plt.show()
График отображается под редактором
Правило простое: plot — для последовательностей с естественным порядком (время, глубина, расстояние), scatter — для независимых пар измерений. Гистограмма из третьего урока показывала одну величину, scatter показывает две одновременно — это самый информативный тип графика на этапе знакомства с данными.
Корреляция глазами: читаем форму облака точек
Сила и направление связи считываются с силуэта облака. Четыре классических формы стоит узнавать с первого взгляда:
- Вытянуто из левого нижнего в правый верхний угол — положительная связь: растёт одна величина, растёт и другая. Площадь и цена.
- Вытянуто из левого верхнего в правый нижний — отрицательная связь: одна величина растёт, другая падает. Цена и спрос, скорость и время в пути.
- Бесформенное круглое пятно — связи нет: знание одной величины ничего не говорит о другой. Рост человека и номер его телефона.
- Изгиб, парабола, кольцо — связь есть, но нелинейная: прямая её опишет плохо, а вот глаз увидит сразу.
| Силуэт облака | Что это значит | Пример из жизни |
|---|---|---|
| Восходящая полоса | Положительная линейная связь | Квадратные метры и цена |
| Нисходящая полоса | Отрицательная линейная связь | Цена товара и продажи |
| Круглое пятно | Связи нет | Возраст и размер обуви взрослого |
| Дуга, парабола | Нелинейная связь | Скорость и расход топлива |
| Точка в стороне | Выброс: ошибка или аномалия | Одна квартира втрое дороже остальных |
Коэффициент корреляции как подстраховка
Глаз — быстрый оценщик, но для отчёта иногда нужно число. Стандартная мера линейной связи — коэффициент корреляции Пирсона: значение от -1 до +1, где 0 означает отсутствие линейной связи, а +1 — точки лежат на восходящей прямой. В NumPy его считает np.corrcoef:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
y = np.array([2, 4, 5, 4, 5])
r = np.corrcoef(x, y)[0, 1]
print(round(r, 2))
0.77
0.77 — заметная положительная связь, но до идеальной прямой далеко. Оговорка, которую забывают: коэффициент улавливает только линейную связь. Данные Анскомба из первого урока — четыре набора с одинаковым r и совершенно разными картинками — навсегда снимают вопрос, чем пользоваться первым. Сначала scatter, потом число. Обратный порядок гарантирует, что параболу вы назовёте «связи нет».
Несколько групп на одном scatter и легенда
Настоящие данные всегда распадаются на группы: районы города, сегменты клиентов, модели устройств. Вызывайте scatter по разу на каждую группу и подписывайте её через label — ax.legend() соберёт подписи в легенду:
import matplotlib.pyplot as plt
# Площадь и цена квартир в двух районах
area_center = [30, 36, 42, 48, 55, 62]
price_center = [6.5, 7.2, 8.4, 9.1, 10.6, 11.8]
area_outskirt = [30, 36, 42, 48, 55, 62]
price_outskirt = [3.9, 4.3, 4.8, 5.6, 6.1, 6.9]
fig, ax = plt.subplots()
ax.scatter(area_center, price_center, label="Центр")
ax.scatter(area_outskirt, price_outskirt, label="Окраина")
ax.set_title("Одинаковые площади, разные районы")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
ax.legend()
plt.show()
График отображается под редактором
На картинке два параллельных облака с одинаковым наклоном, но разной высотой: связь «площадь-цена» внутри района своя, и тот же метр в центре стоит вдвое дороже. Именно так scatter отвечает на вопросы, которые средние числа прячут. Цвета групп Matplotlib раздаёт сам из стандартного цикла; если групп много и цвета важны, задавайте color= явно — палитры и доводка легенды разбираются в уроке про оформление графиков.
Цвет и размер точек как третье измерение
У точки есть не только положение, но и внешний вид — Matplotlib позволяет закодировать им ещё две переменные. Параметр s задаёт размер каждой точки (можно списком, по одному значению на точку), параметр c — цвет. Добавим к квартирам число комнат:
import matplotlib.pyplot as plt
area = [28, 34, 39, 45, 52, 58, 63, 71]
price = [4.2, 5.1, 5.6, 6.9, 7.4, 8.8, 9.1, 11.2]
rooms = [1, 1, 2, 2, 3, 3, 4, 5] # число комнат
fig, ax = plt.subplots()
# размер пропорционален квадрату числа комнат
ax.scatter(area, price, s=[r ** 2 * 60 for r in rooms], alpha=0.7)
ax.set_title("Размер точки показывает число комнат")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
plt.show()
График отображается под редактором
Одна картинка теперь несёт три величины: площадь по X, цена по Y, комнаты — размером. Однушки кучкуются слева внизу, пятёрки — справа вверху, а между ними пусто: малометражных двушек в выборке нет. Так scatter превращается в мини-дашборд из одного вызова.
Цветом удобнее показывать непрерывную величину — возраст дома, расстояние до метро, доход. Передайте список чисел в c, выберите палитру cmap, и fig.colorbar() добавит шкалу-легенду:
import matplotlib.pyplot as plt
area = [28, 34, 39, 45, 52, 58, 63, 71]
price = [4.2, 5.1, 5.6, 6.9, 7.4, 8.8, 9.1, 11.2]
age = [35, 22, 18, 12, 30, 8, 5, 3] # возраст дома, лет
fig, ax = plt.subplots()
points = ax.scatter(area, price, c=age, cmap="viridis", s=90)
fig.colorbar(points, label="Возраст дома, лет")
ax.set_title("Цвет точки = возраст дома")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
plt.show()
График отображается под редактором
Шкала справа расшифровывает цвета: тёмные точки — старый фонд, светлые — новостройки. Заодно видна закономерность, которой не было ни в одном среднем: за одну цену в старом доме предлагают больше метров. Палитру viridis можно заменить на plasma, coolwarm или Greens — каталог cmap в документации Matplotlib.
Подпись конкретной точки: annotate
Выбросы — причина, по которой scatter-анализ не отдаю на аутсорс формулам: одна аномальная точка оказывается либо ошибкой ввода, либо самой ценной находкой недели. Подпишите её, чтобы читатель не гадал:
import matplotlib.pyplot as plt
area = [28, 34, 39, 45, 52, 58, 63, 66]
price = [4.2, 5.1, 5.6, 6.9, 7.4, 8.8, 9.1, 12.8]
fig, ax = plt.subplots()
ax.scatter(area, price)
ax.annotate("Элитная отделка", xy=(66, 12.8), xytext=(48, 11.6),
arrowprops=dict(arrowstyle="->"))
ax.set_title("Выброс помечен подписью")
ax.set_xlabel("Площадь, кв. м")
ax.set_ylabel("Цена, млн руб.")
plt.show()
График отображается под редактором
ax.annotate берёт текст, координаты точки xy, координаты текста xytext и рисует стрелку через arrowprops. Если точек сотни, подписывать каждую бессмысленно — ставьте аннотации только на выбросы и ключевые точки: у рисования scatter plot с подписями точек нет встроенной автоматики, и это к лучшему, иначе график превратился бы в телефонную книгу.
Оверплоттинг: что делать с тысячами точек
На восьми квартирах scatter безупречен. Увеличьте выборку в двести с лишним раз — 2000 клиентов, секунд логов, измерений датчика — и картинка изменится до неузнаваемости:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(7)
x = np.random.normal(0, 1, 2000)
y = np.random.normal(0, 1, 2000)
fig, ax = plt.subplots()
ax.scatter(x, y) # все точки непрозрачные
ax.set_title("2000 точек без alpha")
plt.show()
График отображается под редактором
Классический оверплоттинг: точки легли друг на друга, центр слипся в чёрное пятно, и извлечь из него нечего. Хуже того — плотность на глаз не определить: пять наложившихся точек и пятьдесят выглядят одинаково чёрно. Лечение — сделать точки мельче и полупрозрачными. Параметр alpha принимает прозрачность от 0 (невидимая) до 1 (непрозрачная):
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(7)
x = np.random.normal(0, 1, 2000)
y = np.random.normal(0, 1, 2000)
fig, ax = plt.subplots()
ax.scatter(x, y, s=12, alpha=0.3, color="tab:blue")
ax.set_title("Те же 2000 точек с alpha=0.3")
plt.show()
График отображается под редактором
alpha=0.3 вместе с s=12 превращают пятно в карту плотности: где точки наслоились, там темнее — глаз читает это как третье измерение. Начинайте с alpha от 0.2 до 0.4 и подстраивайте по глазам. Для очень больших объёмов есть приёмы дальше: jitter (небольшой случайный сдвиг), гексбины и вообще переход к агрегатам.
Кейс: рост и вес 300 человек
Соберём всё в один шаблон. Сгенерируем правдоподобные данные NumPy (формула «вес = (рост − 100) × 0.9» плюс нормальный шум), посчитаем корреляцию числом и нарисуем читаемый scatter. Такой набор из трёхсот измерений уже не отличить от реального опроса:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(3)
height = np.random.normal(172, 9, 300) # рост, см
weight = (height - 100) * 0.9 + np.random.normal(0, 7, 300)
r = np.corrcoef(height, weight)[0, 1]
fig, ax = plt.subplots()
ax.scatter(height, weight, s=14, alpha=0.4)
ax.set_title("Рост и вес: r = " + str(round(r, 2)))
ax.set_xlabel("Рост, см")
ax.set_ylabel("Вес, кг")
plt.show()
График отображается под редактором
Три детали, которые делают график годным к показу. np.random.seed фиксирует случайные числа: картинка воспроизведётся у любого читателя до пикселя. alpha=0.4 со s=14 спасают от оверплоттинга на трёхстах точках. Коэффициент r выведен прямо в заголовок — одна строка текста вместо абзаца. Число получится близким к 0.8: рост и вес связаны сильно, но не идеально. Поменяйте seed — связь сохранится, а число слегка поплывёт: так выглядит устойчивая закономерность в отличие от подгонки.
Шпаргалка по параметрам scatter
| Параметр | За что отвечает | Пример |
|---|---|---|
| s | Размер точки — площадь в квадратных пунктах | s=20 |
| c | Цвет: имя, список цветов или числа для палитры | c="tab:red" |
| cmap | Палитра, если c — числа | cmap="viridis" |
| alpha | Прозрачность от 0 до 1 | alpha=0.3 |
| marker | Форма маркера: o, s, ^, + | marker="s" |
| edgecolors | Цвет контура точки | edgecolors="white" |
| label | Подпись группы для легенды | label="Центр" |
Минимальный добор поверх этой таблицы: ax.grid(True) помогает считывать значения по сетке, ax.set_xlim() и ax.set_ylim() обрезают пустые поля, если данные лежат далеко от нуля. Полный контроль над подписями и легендой — тема следующего урока про оформление.
Что дальше
Вы научились читать форму облака точек, показывать третье измерение цветом и размером, спасать график от оверплоттинга и подписывать выбросы стрелкой. Остался вопрос, который звучит на каждом разборе аналитики: как показать на одной странице четыре таких графика сразу. Ответ — subplots, сетка панелей на общем холсте; вместе с круговыми диаграммами и их границами применимости это тема урока про несколько графиков на одном полотне. А когда данные уже лежат в таблице pandas, половину рутины съест встроенный метод .plot() — scatter там вызывается одной строкой. Курс на этом не кончается: scatter — четвёртый урок из десяти, а весь маршрут — самоучитель Matplotlib.
Сначала предскажи ответ в голове — это главный навык программиста.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
points = ax.scatter([1, 2, 3], [10, 20, 15])
print(type(points).__name__)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter([1, 2], [3, 4], label="Ноябрь")
ax.scatter([2, 3], [5, 6], label="Декабрь")
legend = ax.legend()
print(len(legend.get_texts()))
1. Какая функция рисует диаграмму рассеяния?
2. Облако точек вытянуто из левого верхнего угла в правый нижний. Какая это связь?
3. Что означает параметр s=100 в вызове ax.scatter()?
4. 2000 точек слились в чёрное пятно. Что сделать в первую очередь?
5. Как показать две группы на одном scatter с легендой?
Постройте диаграмму рассеяния для двух районов на одном графике: данные в заготовке. Подпишите группы через label и выведите легенду, поставьте прозрачность alpha=0.8, добавьте заголовок «Квартиры: центр и окраина» и подписи осей «Площадь, кв. м» и «Цена, млн руб.».
Чем scatter отличается от plot?
plot() соединяет точки линией по порядку и годится для последовательностей (время, расстояние). scatter() рисует отдельные точки и показывает связь двух независимых величин — цену и площадь, рост и вес.
Как построить scatter plot с подписями точек?
В цикле по данным вызывайте ax.annotate(имя, xy=(x, y)) или ax.text(x, y, имя). При сотнях точек подписывайте только выбросы и ключевые точки, иначе подписи сольются.
Какой параметр делает точки прозрачными?
alpha — от 0 (полностью прозрачная) до 1 (непрозрачная). При тысячах точек берите alpha 0.2–0.4 вместе с уменьшенным s: пятно превращается в карту плотности.
Что показывает диаграмма рассеяния?
Форму связи двух величин: направление (вверх или вниз), силу (плотность полосы), нелинейность и выбросы. Численной подстраховкой служит коэффициент корреляции Пирсона — np.corrcoef в NumPy.
Что такое оверплоттинг на диаграмме рассеяния?
Наложение точек друг на друга при их большом числе: центр графика слипается в чёрное пятно, и плотность на глаз не определить. Лечение — s поменьше (10–30) и alpha 0.2–0.4, дальше — агрегация данных: гистограмма или тепловая карта.
Понравился урок? Сошлитесь на него
«Выводы о причинах требуют эксперимента, а не картинки.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
Matplotlib / Seaborn · Урок 2
Линейные графики в Matplotlib: plot, цвета, стили и подписи
plt.plot() под микроскопом: цвета, маркеры, стили линий, легенды, диапазоны осей и кейс с двумя температурными кривыми за месяц.
Matplotlib / Seaborn · Урок 3
Столбчатые диаграммы и гистограммы в Matplotlib: bar и hist
bar и barh для сравнения категорий, hist для распределений, параметр bins, план против факта и подписи значений над столбцами.
Matplotlib / Seaborn · Урок 5
Круговые диаграммы и subplots: несколько графиков на одном полотне
pie и donut с честными границами применимости, сетка графиков через plt.subplots, общий заголовок suptitle и мини-дашборд 2 на 2 в финале.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
openpyxl · Урок 12
Диаграммы в Excel через openpyxl: BarChart и LineChart
Собираем настоящие диаграммы Excel из кода: BarChart по продажам недели и LineChart по выручке — Reference, add_data, категории, якорь и размеры в сантиметрах.
openpyxl диаграммадиаграмма excel python
Pandas · Урок 1
Что такое Pandas и как установить через pip: первые Series и DataFrame
Первая таблица DataFrame в библиотеке pandas, умный столбец Series и быстрый осмотр данных через head, info и describe — старт сквозного анализа данных интернет-магазина.
разница между series и dataframeструктуры данных pandas
Matplotlib / Seaborn · Урок 10
Проект по визуализации: дашборд данных о фильмах на Matplotlib и Seaborn
Финальный проект курса: датасет из 200 фильмов, EDA, дашборд 2x2 с трендом, жанрами, гистограммой и scatter — плюс чек-лист «график готов к показу» и итоги десяти уроков.
проект визуализация pythonдашборд matplotlib
Проверьте знания по Matplotlib / Seaborn
В челлендже — 20 задач по Matplotlib / Seaborn, по 2 из каждого урока этого раздела. Формат: фрагмент кода и четыре варианта — что напечатает. После ответа — вердикт и объяснение со ссылкой на урок-источник.
Тест по Matplotlib / Seaborn: 20 задач