Распределения: гистограмма, boxplot, violin
Среднее лжёт о форме. Гистограмма, boxplot и violin показывают, как данные на самом деле распределены.
«Никогда не доверяйте одному числу там, где можно посмотреть на распределение».
Распределение отвечает на вопрос «как разбросаны значения?». Гистограмма группирует данные по бинам и показывает частоту. Boxplot (ящик с усами) сжимает распределение до медианы, квартилей и выбросов — удобно сравнивать группы. Violin добавляет к boxplot форму плотности.
Почему это так важно на практике: одно сводное число почти всегда лжёт о форме. Две страны с одинаковой средней зарплатой могут иметь совершенно разное общество — в одной доходы кучкуются вокруг среднего, в другой расслоены на бедных и богатых с провалом посередине. Среднее в обоих случаях совпадает, а гистограммы выглядят как небо и земля. Поэтому распределения — обязательный первый шаг анализа: они показывают асимметрию (длинный хвост вправо у доходов и времён отклика), мультимодальность (несколько пиков — признак смешанных подгрупп) и тяжёлые хвосты, из-за которых среднее и стандартное отклонение становятся ненадёжными, и приходится опираться на медиану и квартили.
У трёх инструментов разная «плотность сжатия» информации. Гистограмма показывает форму максимально детально, но занимает место и неудобна для сравнения многих групп. Boxplot ужимает распределение до пяти чисел и поэтому идеально ложится в ряд: десяток групп бок о бок читаются мгновенно — но ценой потери формы. Violin — компромисс: он сохраняет компактность boxplot и при этом возвращает форму плотности, обнажая скрытые пики. Выбор между ними — это всегда баланс между детальностью и числом сравниваемых групп: чем больше групп на одной фигуре, тем сильнее приходится сжимать каждое распределение.
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].hist(values, bins=20)
axes[0].set_title("Гистограмма")
axes[1].boxplot([group_a, group_b], labels=["A", "B"])
axes[1].set_title("Boxplot")
# violin есть в matplotlib и красивее в seaborn
axes[2].violinplot([group_a, group_b])
axes[2].set_title("Violin")
BOXPLOT — что показывает ящик с усами
выброс o
|
ус ------+------ максимум в пределах 1.5*IQR
ящик [ Q1 | медиана | Q3 ] <- 50% данных
ус ------+------ минимум в пределах 1.5*IQR
|
выброс o
Как работает под капотом
Гистограмма делит диапазон на бины и считает, сколько значений попало в каждый — высота столбика. Число бинов сильно меняет вид: мало — теряем детали, много — шум. Boxplot вычисляет квартили: Q1 (25-й перцентиль), медиана (50-й), Q3 (75-й); усы тянутся до 1.5·IQR, остальное — выбросы. Violin оценивает плотность (KDE) и зеркалит её.
Ключевой параметр гистограммы — не только число бинов, но и их границы: один и тот же датасет при сдвиге границ бинов может показать то один пик, то два. Поэтому существуют формальные правила выбора ширины: правило Стёрджеса (k ≈ 1 + log2 n) хорошо для небольших симметричных выборок, а правило Фридмана–Дьякониса (ширина ∝ IQR / n^(1/3)) устойчивее к выбросам и большим объёмам, потому что опирается на робастный IQR, а не на размах. Альтернатива бинам вовсе — KDE: вместо подсчёта по ячейкам он кладёт на каждую точку маленький «колокол» (ядро) и суммирует их, получая гладкую кривую плотности. Тут свой параметр — ширина ядра (bandwidth): узкая даёт изрезанную кривую с ложными пиками, широкая — переглаженную, размывающую реальную структуру. Это та же дилемма «детали против шума», что и с числом бинов.
У boxplot правило усов «1.5·IQR» — это конвенция Тьюки, а не закон природы: для нормального распределения за усами оказывается лишь около 0.7% значений, но для тяжелохвостых данных «выбросов» будет много, и это не ошибка, а свойство данных. Важно помнить и то, что усы тянутся до последнего фактического наблюдения внутри границы 1.5·IQR, а не до самой границы — поэтому усы у разных групп бывают разной длины. Violin же опирается на тот же KDE, что и гистограмма, и наследует его слабости: на малой выборке плотность оценивается ненадёжно, а ядро может «вылезти» за физически возможный диапазон (например, показать отрицательное время), поэтому при малом n честнее наложить сами точки.
Посчитаем квартили и IQR вручную — это и есть «начинка» boxplot.
# Квартили, медиана и IQR вручную — начинка boxplot
data = sorted([7, 2, 9, 4, 11, 6, 3, 8, 5, 10, 1, 12])
def quantile(sorted_data, q):
pos = q * (len(sorted_data) - 1)
lo = int(pos)
frac = pos - lo
if lo + 1 < len(sorted_data):
return sorted_data[lo] + frac * (sorted_data[lo + 1] - sorted_data[lo])
return sorted_data[lo]
q1 = quantile(data, 0.25)
med = quantile(data, 0.50)
q3 = quantile(data, 0.75)
iqr = q3 - q1
print("Q1 =", q1, " медиана =", med, " Q3 =", q3)
print("IQR =", iqr)
print("Границы выбросов:", q1 - 1.5 * iqr, "..", q3 + 1.5 * iqr)
«Попробуй сам ▶» — Q1, медиана, Q3 и границы выбросов — ровно те линии, что рисует boxplot.
Частые ошибки
Слишком мало или много бинов в гистограмме — искажают форму. Boxplot скрывает бимодальность (два пика выглядят как один разброс) — тут спасает violin. Сравнивать группы разного размера boxplot без указания n. Считать выбросами всё за усами без анализа.
Best practices
- Гистограмма: подберите бины (правило Стёрджеса или Фридмана–Дьякониса).
- Для сравнения многих групп — boxplot; при подозрении на мультимодальность — violin.
- Накладывайте сырые точки (strip/swarm) на boxplot при малых выборках.
- Всегда смотрите распределение перед расчётом среднего.
Итог: вы умеете показывать форму данных. Дальше — графики состава: stacked bar и осторожный pie.