violinplot в Seaborn: форма распределения вместо ящика
Скрипка показывает распределение целиком: где масса наблюдений, где хвосты и есть ли второй горб. Сравниваем с boxplot, режем скрипку параметром split.
Редакция Питоники
У boxplot из прошлого урока есть слепое пятно: он показывает квартили, но прячет форму. Две группы с одинаковым ящиком могут быть устроены принципиально по-разному — одна горкой вокруг медианы, другая двумя стаями по краям. violinplot («скрипичная диаграмма») рисует форму целиком: зеркальная фигура, толщина которой в каждом месте пропорциональна плотности наблюдений.
Построим первую скрипку, сравним её с ящиком бок о бок, разрежем пополам для двух групп и разберёмся с единственной ловушкой метода — сглаживание KDE иногда рисует то, чего в данных нет.
Первая скрипка: оценки двух групп
Сквозной пример — баллы за итоговый тест в двух группах курса. Группа А училась ровно, группа Б рассыпалась на отставших и отличников:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
scores = pd.DataFrame({
"group": ["А"] * 6 + ["Б"] * 6,
"score": [60, 65, 70, 72, 75, 78, 55, 58, 60, 88, 90, 92],
})
sns.set_theme()
ax = sns.violinplot(data=scores, x="group", y="score")
ax.set_title("Баллы за тест: группа А и группа Б")
ax.set_xlabel("Группа")
ax.set_ylabel("Баллы")
plt.show()
График отображается под редактором
Скрипка группы А — один ровный бугор: масса баллов собрана вокруг середины. Скрипка группы Б — два горба с провалом между ними: студентов «на середняка» просто нет. Внутри каждой скрипки прячется мини-boxplot — белая точка это медиана, толстая линия квартили. Ширина скрипки — это плотность: где скрипка толще, там лежит больше наблюдений, а узкие места и хвосты говорят о редких значениях.
import pandas as pd
scores = pd.DataFrame({
"group": ["А"] * 6 + ["Б"] * 6,
"score": [60, 65, 70, 72, 75, 78, 55, 58, 60, 88, 90, 92],
})
print(scores.groupby("group")["score"].mean().round(1).tolist())
print(scores["score"].min(), scores["score"].max())
[70.0, 73.8] 55 92
Средние групп почти равны — 70.0 против 73.8, и диапазон один: от 55 до 92. Сводные числа уверяют, что группы похожи; скрипка показывает, что устроены они противоположно. Это тот же урок, что с ящиком с усами, но острее: двугорбость не увидеть ни в среднем, ни в квартилях.
boxplot против violinplot: одна картина дважды
Лучшая привычка — рисовать оба графика рядом: ящик даёт точные пятичисловые сводки, скрипка — форму. Seaborn рисует на общие оси через subplots Matplotlib:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
scores = pd.DataFrame({
"group": ["А"] * 6 + ["Б"] * 6,
"score": [60, 65, 70, 72, 75, 78, 55, 58, 60, 88, 90, 92],
})
sns.set_theme()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
sns.boxplot(data=scores, x="group", y="score", ax=ax1)
sns.violinplot(data=scores, x="group", y="score", ax=ax2)
ax1.set_title("boxplot: квартили и выбросы")
ax2.set_title("violinplot: форма распределения")
plt.show()
График отображается под редактором
Ящик группы Б честно широк — но выглядит как «просто большой разброс»; только скрипка справа показывает, что разброс двугорбый. Обратная ситуация бывает при малых выборках: ящик из десяти чисел надёжен, а скрипка тех же десяти чисел может льстить глазу плавными изгибами. Правило: boxplot — для сводки и групп поменьше, violinplot — когда важна форма и наблюдений хотя бы двадцать на группу.
split=True: две группы в одной скрипке
Если в hue ровно две группы, скрипку можно разрезать: каждая половина получит свою группу. Это разделение — параметр split=True, и он экономит полширины графика, когда группы естественно парные:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
lessons = pd.DataFrame({
"course": ["Английский"] * 6 + ["Математика"] * 6,
"time": ["утро", "вечер"] * 6,
"score": [72, 65, 78, 61, 84, 70, 81, 58, 76, 62, 88, 66],
})
sns.set_theme()
ax = sns.violinplot(data=lessons, x="course", y="score",
hue="time", split=True)
ax.set_title("split=True: утро слева, вечер справа")
ax.set_xlabel("Курс")
ax.set_ylabel("Баллы")
plt.show()
График отображается под редактором
Каждая скрипка теперь зеркальная пара половин: утренние группы слева, вечерние справа. Читается мгновенно — если половины совпадают, время занятий ни на что не влияет; если расходятся, как здесь у математики, вечерние группы проседают. Без split Seaborn нарисовал бы две отдельные скрипки на курс — тоже рабочий вариант, просто вдвое шире.
cut=0: не выходить за пределы данных
KDE-сглаживание не знает о смысле данных: для величин, которые не бывают отрицательными — время, цена, балл от нуля — кривая на краях может уйти в минус. Параметр cut= запрещает ей отходить от крайних значений:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
queue = pd.DataFrame({"minutes": [2, 3, 3, 4, 4, 4, 5, 5, 6, 20]})
sns.set_theme()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
sns.violinplot(y=queue["minutes"], ax=ax1)
sns.violinplot(y=queue["minutes"], cut=0, ax=ax2)
ax1.set_title("cut по умолчанию: хвост ушёл в минус")
ax2.set_title("cut=0: скрипка кончается на данных")
plt.show()
График отображается под редактором
Слева скрипка обещает «возможны отрицательные минуты ожидания» — этого не бывает. Справа фигура обрезана по реальным данным и не выдаёт желаемое за действительное. Тот же контроль даёт inner=: значение "quart" рисует внутри линии квартилей, "box" — мини-ящик (по умолчанию), "point" — точки, None убирает внутренности вовсе.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
scores = pd.DataFrame({
"group": ["А"] * 6 + ["Б"] * 6,
"score": [60, 65, 70, 72, 75, 78, 55, 58, 60, 88, 90, 92],
})
sns.set_theme()
ax = sns.violinplot(data=scores, x="group", y="score", inner="quart")
ax.set_title("Квартили видны, форма не спрятана")
ax.set_xlabel("Группа")
ax.set_ylabel("Баллы")
plt.show()
График отображается под редактором
Такой вариант — золотая середина: скрипка показывает форму, а линии внутри несут точные квартили. Для отчётов, где читатель привык к ящикам, inner="quart" часто мягче вводит в скрипичную диаграмму, чем мини-бокс по умолчанию.
Что дальше
Скрипка закрыла слабое место ящика: теперь видна форма, горбы и хвосты. Дальше переходим от одной переменной к матрицам связей: следующий урок — heatmap, тепловая карта, где цветом загорается каждая ячейка таблицы — от корреляций до активности по дням и часам.
Ширина скрипки — это плотность: где скрипка толще, там лежит больше наблюдений, а узкие места и хвосты говорят о редких значениях.
Сначала предскажи ответ в голове — это главный навык программиста.
import pandas as pd
s = pd.Series([2, 2, 2, 8, 8, 8])
print(s.mean())
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.DataFrame({
"g": ["x", "y", "z", "x", "y", "z"],
"v": [1, 2, 3, 4, 5, 6],
})
ax = sns.violinplot(data=df, x="g", y="v")
print(len(ax.collections))
import pandas as pd
df = pd.DataFrame({
"g": ["a", "a", "b", "b"],
"v": [1, 3, 5, 7],
})
print(df.groupby("g")["v"].median().tolist())
1. Что означает ширина скрипки в violinplot?
2. Что прячется внутри скрипки по умолчанию?
3. Что делает split=True в sns.violinplot с hue из двух групп?
4. Данные — время ожидания в минутах, оно не бывает отрицательным. Какой параметр violinplot уберёт вымышленный хвост ниже нуля?
5. Когда честнее показать boxplot, а не violinplot?
В редакторе — время сборки заказа двумя сотрудниками (минуты). Постройте violinplot по сотрудникам с линиями квартилей внутри (inner="quart") и заголовком «Время сборки заказа».
Что показывает violinplot и чем он отличается от boxplot?
Violinplot («скрипичная диаграмма») рисует форму распределения целиком: ширина фигуры — оценка плотности, поэтому двугорбость и хвосты видны сразу. Boxplot сжимает данные до пяти чисел и форму прячет. Внутри скрипки обычно остаётся мини-ящик, так что медиана и квартили никуда не деваются.
Урок 11: violinplot — форма распределенияКак сравнить две группы в одной скрипке?
Передайте hue с двумя значениями и добавьте split=True: каждая группа получит свою половину фигуры. Утро слева, вечер справа — совпадающие половины означают, что фактор времени ни на что не влияет.
Почему на скрипке появляются отрицательные значения, которых нет в данных?
Это сглаживание KDE: кривая плотности не знает о смысле данных и дорисовывает хвост за крайними значениями. Параметр cut=0 запрещает ей выходить за пределы наблюдений — обязательно включайте его для неотрицательных величин вроде времени и цены.
Когда violinplot вводит в заблуждение?
На малых выборках: KDE сгладит даже три наблюдения в безупречно гладкую фигуру, и читатель принимает форму за факт. Для групп из нескольких значений честнее boxplot или точки; скрипка раскрывается на выборках от двадцати наблюдений.
Понравился урок? Сошлитесь на него
«Ширина скрипки — это плотность: где скрипка толще, там лежит больше наблюдений, а узкие места и хвосты говорят о редких значениях.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
Seaborn · Урок 10
boxplot в Seaborn: как читать ящик с усами и сравнивать группы
Один символ — и вся форма распределения перед глазами: медиана, квартили, разброс и выбросы. Учимся читать ящик с усами, сравнивать группы и добавлять hue.
Seaborn · Урок 12
heatmap в Seaborn: тепловая карта матрицы и корреляции
Матрица превращается в картинку: корреляции признаков и активность по дням-часам загораются цветом. annot, fmt, cmap, vmin и vmax — четыре ручки тепловой карты.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Matplotlib / Seaborn · Урок 8
Гистограмма и ящик с усами в Matplotlib: hist и boxplot
plt.hist и plt.boxplot на одних данных: бины и density, чтение формы распределения, медиана, квартили, усы 1.5 × IQR и выбросы — плюс сравнение двух групп обоими способами. Весь код запускается на странице.
matplotlib boxplotсравнение распределений python
Matplotlib / Seaborn · Урок 10
Проект по визуализации: дашборд данных о фильмах на Matplotlib и Seaborn
Финальный проект курса: датасет из 200 фильмов, EDA, дашборд 2x2 с трендом, жанрами, гистограммой и scatter — плюс чек-лист «график готов к показу» и итоги десяти уроков.
matplotlib seabornстолбчатая диаграмма python
Seaborn · Урок 4
histplot в Seaborn: распределения, bins и kde
Гистограмма отвечает на вопрос «как распределились числа», а не «каково их среднее». bins задаёт ширину карманов, kde=True добавляет гладкую форму — и бимодальность больше не спрячется.
seaborn histplotраспределение python seaborn