Сюжет для скрипки определяется в Википедии следующим образом:

Скрипичный график — это метод построения числовых данных. Это похоже на коробчатую диаграмму с добавлением повернутого графика плотности ядра с каждой стороны. Диаграммы-скрипки аналогичны блочным диаграммам, за исключением того, что они также показывают плотность вероятности данных при разных значениях.
Диаграмма-скрипка более информативна, чем обычная блочная диаграмма. В то время как ящичковая диаграмма показывает только сводную статистику, такую ​​как среднее/медиана и межквартильный диапазон, скрипичная диаграмма показывает полное распределение данных. Эта разница особенно полезна, когда распределение данных мультимодальное (более одного пика). В этом случае график скрипки показывает наличие различных пиков, их положение и относительную амплитуду.

Этот тип диаграммы статистики становится все более популярным среди специалистов по данным и используется во многих отчетах. В этой короткой статье я объясню, как полностью настроить скрипичные сюжеты с помощью Matplotlib в Python.

Установите и импортируйте необходимые пакеты

Вы можете установить библиотеку matplotlib (для скрипичных сюжетов требуется Matplotlib 1.4 или более поздней версии) с помощью менеджеров пакетов pip или conda из командной строки. Кроме того, мы использовали модуль Numpy для создания и управления данными для построения графиков в примерах, поэтому, если он не установлен, вы также можете его установить.

pip install matplotlib 
pip install numpy

После того, как они установлены, нам нужно импортировать их в наш код.

import matplotlib.pyplot as plt
import numpy as np

Пример 1: Сюжет для скрипки по умолчанию

Начнем с прорисовки скрипичного сюжета без особых параметров. Для этого в библиотеке Matplotlib определена встроенная функция violinplot.

import matplotlib.pyplot as plt
import numpy as np

# Make data
np.random.seed(0)
D = np.random.normal((3, 5, 4.5), (1.00, 0.75, 1.25), (100, 3))

# Plot
fig, ax = plt.subplots()
vp = ax.violinplot(D)
plt.show()

Результат такой простой, но если мы сравним его с коробчатой ​​диаграммой, мы поймем их разный подход к визуализации данных. Блочная диаграмма сосредоточена на статистических параметрах, но график скрипки дает четкое представление о распределении данных. Кроме того, ящичная диаграмма не может показать пробелы в распределении данных и вызвать недопонимание, в то время как у скрипичных диаграмм этой проблемы нет.

Пример 2: Использование параметров скрипичного сюжета

Документация скрипки описывает некоторые параметры для настройки этих графиков, которые мы протестировали отдельно в следующих кодах.

positions: Он управляет положением скрипок. В следующем коде я изменил расположение и сортировку скрипок с помощью списка. По умолчанию используется список: [1, 2, …, n]
widths: контролирует максимальную ширину каждой скрипки. Значение по умолчанию равно 0,5 и может быть вектором, подобным следующему коду, или скаляром.
vert: если true, создается вертикальный график скрипки. В противном случае получается горизонтальный скрипичный сюжет.

import matplotlib.pyplot as plt
import numpy as np

# Make data
np.random.seed(0)
D = np.random.normal((3, 5, 4.5), (1.00, 0.75, 1.25), (100, 3))

# Plot
fig, axs = plt.subplots(2, 2)
fig.set_size_inches(8, 8, forward=True)

vp1 = axs[0,0].violinplot(D)
axs[0,0].set_title('Default violin plot')

# positions default: [1, 2, ..., n]
vp2 = axs[0,1].violinplot(D, positions=[2,4,2.75])
axs[0,1].set_title('Using positions parameter')

# widths default: 0.5, it can be an array-like
vp3 = axs[1,0].violinplot(D, widths=[2,0.5,3]) 
axs[1,0].set_title('Using widths parameter')

# vert default: True
vp4 = axs[1,1].violinplot(D, vert=False) 
axs[1,1].set_title('Using vert parameter')

plt.show()

Мы можем добавлять или удалять индикаторы средних значений, экстремумов и медиан данных в скрипках, используя параметры showmeansbool, showextremabool и showmedians соответственно.

import matplotlib.pyplot as plt
import numpy as np

# Make data
np.random.seed(0)
D = np.random.normal((3, 5, 4.5), (1.00, 0.75, 1.25), (100, 3))

# Plot
fig, axs = plt.subplots(2, 2)
fig.set_size_inches(8, 8, forward=True)

vp1 = axs[0,0].violinplot(D)
axs[0,0].set_title('Default violin plot')

# showmeans default: False
vp2 = axs[0,1].violinplot(D, showmeans=True)
axs[0,1].set_title('Using showmeans=True parameter')

# showextrema default: True
vp3 = axs[1,0].violinplot(D, showextrema=False)
axs[1,0].set_title('Using showextrema=False parameter')

# veshowmediansrt default: False
vp4 = axs[1,1].violinplot(D, showmedians=True) 
axs[1,1].set_title('Using showmedians=True parameter')

plt.show()

Еще один параметр скрипичного сюжета — points. Наши данные имеют 100 точек, но с помощью этого параметра мы можем определить количество точек, которые будут использоваться для оценки каждой из оценок плотности ядра Гаусса. Вы можете увидеть влияние этого на скрипки на следующем графике.
Кроме того, функция violinplot имеет bw_method в качестве входных данных, которые вычисляют пропускную способность оценщика. Как указано в документации matplotlib, это может быть scott, silverman, скалярная константа или вызываемый объект. Для получения дополнительной информации об этом параметре вы можете прочитать эта ссылка и эта. В целом, с увеличением его значения форма распределения становится более плавной.

import matplotlib.pyplot as plt
import numpy as np

# Make data
np.random.seed(0)
D = np.random.normal((3, 5, 4.5), (1.00, 0.75, 1.25), (100, 3))

# Plot
fig, axs = plt.subplots(2, 2)
fig.set_size_inches(8, 8, forward=True)

vp1 = axs[0,0].violinplot(D)
axs[0,0].set_title('Default violin plot')

# points default: 100
vp2 = axs[0,1].violinplot(D, points=3)
axs[0,1].set_title('Using points parameter')

# bw_method default: 'scott'
vp3 = axs[1,0].violinplot(D, bw_method=0.1)
axs[1,0].set_title('Using bw_method=0.1 parameter')

# bw_method default: 'scott'
vp4 = axs[1,1].violinplot(D, bw_method=1.5) 
axs[1,1].set_title('Using bw_method=1.5 parameter')

plt.show()

Пример 3: изменение выходных данных скрипичного сюжета (изменение графики)

Обычно мы можем изменить цвета наших графиков с помощью их входных данных, например, параметра color в функции plot. Мы описываем входные параметры виолончелей в matplotlib, но, как видите, цвет на всех графиках одинаковый: голубой. Итак, что мы можем сделать, если мы болельщики Манчестер Юнайтед?

На основе документации matplotlib ответ находится в выходном словаре. На самом деле, вывод vp в Примере 1# приводит к выводу следующего словаря:

>>{'bodies': [<matplotlib.collections.PolyCollection object at 0x000001D5AE9E75B0>,
      <matplotlib.collections.PolyCollection object at 0x000001D5AE9E7910>, 
      <matplotlib.collections.PolyCollection object at 0x000001D5AE9E7C70>],
   'cmaxes': <matplotlib.collections.LineCollection object at 0x000001D5AE9E7550>,
   'cmins': <matplotlib.collections.LineCollection object at 0x000001D5B068C370>, 
   'cbars': <matplotlib.collections.LineCollection object at 0x000001D5B068C730>}

В этом словаре ключ bodies представляет собой список экземпляров PolyCollection, содержащих заполненную область каждой скрипки. В нашем примере у нас было три скрипки, поэтому его значения имеют три члена. Для изменения цвета скрипки мы можем использовать эту клавишу, чтобы изменить цвет скрипки и стиль ее края. Кроме того, при использовании метода set_alpha изменяется прозрачность скрипок. В следующем коде я меняю цвета трех разных скрипок с разным распределением.

import matplotlib.pyplot as plt
import numpy as np

# Make data:
data = [np.random.standard_normal(size = 10000),
        np.random.standard_t(20,10000),
        np.random.uniform(-5, 5, 10000)]

# Plot
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 4),sharey=True)

axes[0].set_title('Default violin plot')
axes[0].set_ylabel('Observed values')
axes[0].violinplot(data)

axes[1].set_title('Customized violin plot')
parts = axes[1].violinplot(data, showmeans=False, 
        showmedians=False,showextrema=False)

colors = ['forestgreen', 'tomato', 'orange']
for pc, color in zip(parts['bodies'],colors):
    pc.set_facecolor(color)
    pc.set_edgecolor('black')
    pc.set_alpha(1)

plt.show()

Теперь у нас есть цвет в нашей модели! Если вы не хотите удалять строки, вы можете установить другие элементы в выходном словаре. cmins и cmaxes — это экземпляры LineCollection, которые отмечают нижнее и верхнее значения каждого из распределений скрипки соответственно. cbars — это экземпляр LineCollection, который отмечает центры распределения каждой скрипки. И, наконец, если вы установите showmeansbool и showmedians True в violinplot, в выходном словаре будут cmeans и cmedians, которые являются LineCollection экземплярами для средних и медианных значений.

С помощью входного параметра positions мы могли бы изменить расположение скрипок по оси x. Более того, с помощью функций Matplotlib мы можем изменить метки оси X.

# set style for the X axes
labels = ['Normal', 'T-Student', 'Uniform']
for ax in [axes[0], axes[1]]:
    ax.set_xticks(np.arange(1, len(labels) + 1), labels=labels)
    ax.set_xlabel('Distribution Name')

Мы можем добавить больше деталей к нашим скрипкам. Некоторые люди используют коробочные сюжеты и скрипичные сюжеты вместе. Таким образом, вы можете сразу увидеть и проанализировать распределение и статистику данных. Для этой цели можно использовать функции блочной диаграммы, которая в разных аспектах похожа на скрипичную диаграмму.
В одном примере документации Matplotlib используется другой способ. В этом примере медиана данных построена с использованием функций scatter, а остальные статистические данные показаны вертикальными линиями. Их описание выходит за рамки нашего обсуждения в этом посте, но если мы просто воспользуемся этим подходом, наш окончательный код и результат будут следующими:

import matplotlib.pyplot as plt
import numpy as np

def adjacent_values(vals, q1, q3):
    upper_adjacent_value = q3 + (q3 - q1) * 1.5
    upper_adjacent_value = np.clip(upper_adjacent_value, q3, vals[-1])

    lower_adjacent_value = q1 - (q3 - q1) * 1.5
    lower_adjacent_value = np.clip(lower_adjacent_value, vals[0], q1)
    return lower_adjacent_value, upper_adjacent_value


# Make data:
data = [np.random.standard_normal(size = 10000),
        np.random.standard_t(20,10000),
        np.random.uniform(-5, 5, 10000)]

# Plot
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 4),sharey=True)

axes[0].set_title('Default violin plot')
axes[0].set_ylabel('Observed values')
axes[0].violinplot(data)

axes[1].set_title('Customized violin plot')
parts = axes[1].violinplot(data, showmeans=False, 
        showmedians=False,showextrema=False)

# set colors of violins 
colors = ['forestgreen', 'tomato', 'orange']
for pc, color in zip(parts['bodies'],colors):
    pc.set_facecolor(color)
    pc.set_edgecolor('black')
    pc.set_alpha(1)

# set style for the X axes
labels = ['Normal', 'T-Student', 'Uniform']
for ax in [axes[0], axes[1]]:
    ax.set_xticks(np.arange(1, len(labels) + 1), labels=labels)
    ax.set_xlabel('Distribution Name')

quartile1, medians, quartile3 = np.percentile(data, [25, 50, 75], axis=1)
whiskers = np.array([
    adjacent_values(sorted_array, q1, q3)
    for sorted_array, q1, q3 in zip(data, quartile1, quartile3)])
whiskers_min, whiskers_max = whiskers[:, 0], whiskers[:, 1]

inds = np.arange(1, len(medians) + 1)
axes[1].scatter(inds, medians, marker='o', color='white', s=30, zorder=3)
axes[1].vlines(inds, quartile1, quartile3, color='k', linestyle='-', lw=5)
axes[1].vlines(inds, whiskers_min, whiskers_max, color='k', linestyle='-', lw=1)

plt.show()

В этом посте я постарался описать большинство особенностей построения скрипичного сюжета с помощью модулей Python и Matplotlib. Я надеюсь, что вы нашли это полезным. Спасибо за чтение.