Многосегментные графики

При анализе данных часто бывает полезным построение графиков одного и того же типа, например, графиков разброса, которые содержат разные подмножества из этих данных. При таком подходе получается много графиков, но все они, в совокупности, дают хорошее представление об особенностях в данных - помогают найти направление в котором надо "копать". Seaborn содержит функции, которые как раз и позволяют строить несколько графиков (областей Axes) на одной картинке (области Figure). Получаемый в результате многосегментный график в точности соответствует структуре данных.

Но что бы построение многосегментных графиков было возможным данные должны быть представлены в виде объектов DataFrame библиотеки Pandas и иметь "длинную" форму. Под длинной формой понимается форма при которой каждая переменная (признак) представлен отдельным столбцом, а каждое наблюдение - строкой.

Как обычно, прежде чем двигаться дальше, давайте выполним все необходимые импорты:

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

А заодно установим стиль графиков, что бы улучшить их восприятие:

sns.set(style="ticks")

Фасетные графики

Если в данных есть числовые значения, но все они могут быть разбиты на подмножества, то самым оптимальным вариантом анализа является их визуализация в контексте этих подмножеств. Для решения данной задачи существует класс FacetGrid, который позволяет выделять подмножества по трем измерениям: строкам, столбцам и оттенкам. Давайте загрузим данные для выполнения примеров:

penguins = sns.load_dataset('penguins')
penguins.head()
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 Male
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 Female
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 Female
3 Adelie Torgersen NaN NaN NaN NaN NaN
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 Female

Теперь мы можем создать экземпляр класса FacetGrid:

g = sns.FacetGrid(data=penguins, col='island', row='sex')

Использование класса FacetGrid в Seaborn

В данном случае, экземпляр класса просто настроил область Figure и расположил на ней сетку из областей Axes, но ничего на них не рисует. А вот что бы что-то нарисовать нужно воспользоваться методом map():

g = sns.FacetGrid(data=penguins, col='island', row='sex')
g.map(plt.hist, 'body_mass_g');

Использование метода map класса FacetGrid в Seaborn

Такой интерфейс может показаться немного странным и даже неудобным, но вся его задача заключается в настройке сетки графиков. И на самом деле все довольно просто: методу map() мы передали функцию библиотеки Matplotlib plt.hist, а далее указали все аргументы в том порядке в котором они передаются данной функции, а точнее всего один - имя столбца таблицы penguins. Мы можем построить и другие типы графиков для тех же данных:

g = sns.FacetGrid(data=penguins,
                  col='island',    # одна колонка - один остров
                  row='sex',       # пол особи в отдельной строке
                  hue='species')   # цветом выделяем вид пингвинов

g.map(plt.scatter,             # указываем нужную функцию matplotlib
      'body_mass_g',           # значения по оси X
      'flipper_length_mm',     # значения по оси Y
      s=60, alpha=0.3);        # ключевые аргументы функции 

Использование метода map класса FacetGrid для построения графика разброса в Seaborn

В методе map мы указали функцию plt.scatter что бы построить графики разброса в контексте тех переменных, которые мы указали при создании сетки.

Поскольку FacetGrid работает с областями Figure и Axes, то он так же предоставляет множество методов для их более тонкой настройки с помощью соответствующих методов библиотеки Matplotlib. Например, можно без труда установить заголовки для осей и всей сетки графиков:

g = sns.FacetGrid(data=penguins,
                  col='island',    # одна колонка - один остров
                  row='sex',       # пол особи в отдельной строке
                  hue='species')   # цветом выделяем вид пингвинов

g.map(plt.scatter,             # указываем нужную функцию matplotlib
      'body_mass_g',           # значения по оси X
      'flipper_length_mm',     # значения по оси Y
      s=60, alpha=0.3);        # ключевые аргументы функции 

# Устанавливаем подписи осей:
g.set_axis_labels('Масса тела (г)',
                  'Длина крыла (мм)');

# Устанавливаем общий заголовок
g.fig.suptitle('Зависимость длины крыльев от массы тела',
               y = 1.03,
               fontsize = 20);

Тонкая настройка FacetGrid в Seaborn

Посмотреть все методы предоставляемые созданным экземпляром класса FacetGrid можно с помощью функции dir():

dir(g)

Когда вы выполните эту команду, то увидите, что имеете доступ ко всем элементам графика. Поскольку на области Figure создается сетка из областей Axes, мы можем работать с каждым ее подграфиком отдельно, получив к нему доступ по соответствующему индексу, точно так же как мы это делаем в Matplotlib:

g = sns.FacetGrid(data=penguins,
                  col='island',    # одна колонка - один остров
                  row='sex',       # пол особи в отдельной строке
                  hue='species')   # цветом выделяем вид пингвинов

g.map(plt.scatter,             # указываем нужную функцию matplotlib
      'body_mass_g',           # значения по оси X
      'flipper_length_mm',     # значения по оси Y
      s=60, alpha=0.3);        # ключевые аргументы функции 

# Рисуем прямую линию на отдельном подграфике:
g.axes[0, 1].plot([3000, 6000], [170, 230], c='r', lw=3);

Работа с отдельными подграфиками сетки FacetGrid в Seaborn

Вы даже можете использовать свои собственные функции для рисования графиков:

from scipy import stats

# Создаем свою функцию для рисования графика:
def gaus_kde(data, **kwargs):
    x = np.linspace(data.min(), data.max(), 100)
    kde = stats.gaussian_kde(data, bw_method='silverman')
    plt.plot(x, kde(x), **kwargs)

# Применяем созданную функцию:
g = sns.FacetGrid(penguins, col='island', row='sex', hue='species')
g.map(gaus_kde, 'body_mass_g');

Использование собственных функций для рисования графиков на FacetGrid в Seaborn

Однако, создаваемые функции должны удовлетворять следующим требованиям. Во первых, такие функции должны работать только для активных областей Axes. Во вторых, функции должны принимать позиционные аргументы в которых FacetGrid передает им данные. И в третьих, функции должны принимать ключевые аргументы (**kwargs), которые позволяют управлять элементами графика:

g = sns.FacetGrid(penguins, col='island', row='sex', hue='species')
g.map(gaus_kde, 'body_mass_g', lw=3, ls='--');

Использование собственных функций для рисования графиков на FacetGrid с настройками внешнего вида элементов в Seaborn


Визуализация парных соотношений

Класс FacetGrid позволяет рассматривать одну или две переменные в контексте их разных подмножеств. Но не менее часто нужно рассмотреть парные отношения всех переменных в наборе данных. Для этих целей можно воспользоваться функцией pairplot():

sns.pairplot(penguins);

График pairplot в Seaborn

Эта функция очень удобна для быстрой визуализации парных отношений, но она не предоставляет всей гибкости класса PairGrid, который она использует для создания области Figure и сетки подграфиков (областей Axes). Поскольку верхний и нижний треугольники сетки PairGrid являются симметричными, можно задать разные типы графиков для них и для главной диагонали в том числе:

g = sns.PairGrid(data=penguins, hue='species')
g.map_upper(sns.scatterplot)
g.map_diag(plt.hist, alpha=0.6)
g.map_lower(sns.kdeplot);

Использование класса PairGrid в Seaborn

Приведенный код создает не самый презентабельный график, но он демонстрирует схожесть классов PairGrid и FacetGrid. Мы видим, что методы map_upper(), map_diag() и map_lower() устроены точно так же, как метод map() в FacetGrid, т.е. им могут быть переданы функции построения графиков на уровне Axes как из Matplotlib, так и из Seaborn. Разумеется, собственные функции отрисовки графиков тоже могут переданы данным методам.

По умолчанию, при создании сетки класс PairGrid использует все числовые поля из набора данных, но можно ограничиться только теми, которые являются необходимыми:

g = sns.PairGrid(data=penguins,
                 vars=['bill_length_mm', 'bill_depth_mm'],
                 hue='species')
g.map_upper(sns.scatterplot)
g.map_diag(sns.ecdfplot)
g.map_lower(sns.kdeplot);

Использование класса PairGrid для нескольких переменных в Seaborn

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

При этом, так же как и класс FacetGrid, класс PairGrid предоставляет интерфейс для более тонкой работы с графиками:

g = sns.PairGrid(data=penguins,
                 x_vars=['bill_length_mm', 'bill_depth_mm'],
                 y_vars=['body_mass_g'],
                 hue='species',
                 height=5)

g.map(sns.scatterplot);

# Устанавливаем общий заголовок
g.fig.suptitle('Зависимость длины и ширины клюва \n от массы тела',
               y = 1.1,
               fontsize = 20)


g.axes[0, 0].plot([30, 60], [2500, 6000], c='r', lw=3);

Настройка графиков на основе класса PairGrid в Seaborn


Совместное распределение двух переменных

Если некоторые переменные заинтересовали больше всего, то сконцентрироваться на них можно спомощью класса JointGrid. Для быстрого использования данного класса существует функция jointplot():

sns.jointplot(data=penguins,
              x='body_mass_g',
              y='flipper_length_mm',
              hue='species');

график jointplot в Seaborn

Но данная функция не предоставляет той гибкости, на которую способен класс JointGrid:

g = sns.JointGrid(data=penguins,
                  x='bill_length_mm',
                  y='bill_depth_mm')

g.plot_joint(sns.scatterplot, s=10)
g.plot_joint(sns.kdeplot, alpha=0.3)
g.plot_marginals(sns.histplot, kde=True)

g.ax_joint.set_ylim(12, 22)
g.ax_joint.set_xlim(30, 60)

g.fig.suptitle('Заголовок',
               y = 1.1,
               fontsize = 20);

график построенный на основе сетки JointGrid, построенный в Seaborn

Как видите, интерфуйс очень похож на два предыдущих класса. Узнать все методы можно, выполнив команду dir(g), а за более подробной информацией обратитесь к срправочной документации.