Нормировка плотности и ядерная оценка (KDE)
Чтобы сравнивать распределения разного размера, гистограмму превращают в плотность.
Нормировка плотности — деление высот гистограммы так, чтобы суммарная площадь столбцов равнялась 1; тогда высота — это оценка плотности вероятности, а не число точек.
Зачем нормировать
Если у вас две выборки — 100 и 10000 наблюдений — их «сырые» гистограммы (по числу точек) несравнимы: вторая просто в сто раз выше. Чтобы сравнить форму распределений, переходят к плотности: высота бина равна $\dfrac{n_i}{n \cdot h}$, где $n_i$ — число точек в бине, $n$ — общее число, $h$ — ширина бина. Тогда площадь под гистограммой равна 1 независимо от размера выборки, и две формы можно наложить друг на друга.
Считаем плотность
Проверим, что после нормировки площадь равна 1.
data = [4,5,5,6,6,6,7,7,7,7,8,8,8,9,9,10,11,12]
k = 4
lo, hi = min(data), max(data)
h = (hi - lo) / k
n = len(data)
counts = [0]*k
for v in data:
counts[min(int((v-lo)/h), k-1)] += 1
density = [c / (n * h) for c in counts]
area = sum(d * h for d in density) # площадь = сумма (плотность * ширина)
for i, dns in enumerate(density):
print(f"бин {i}: плотность = {dns:.3f}")
print("суммарная площадь =", round(area, 3))Вывод:
бин 0: плотность = 0.083 бин 1: плотность = 0.194 бин 2: плотность = 0.139 бин 3: плотность = 0.083 суммарная площадь = 1.0
Площадь ровно 1 — теперь это оценка плотности вероятности, сравнимая между выборками любого размера.
KDE: гладкая альтернатива
Гистограмма зависит от положения границ бинов — сдвиньте их, и форма поменяется. Ядерная оценка плотности (KDE) снимает эту проблему: вместо столбцов на каждую точку кладётся маленький гладкий «холмик» (ядро, обычно гауссиан), и они суммируются в плавную кривую. Формула: $\hat{f}(x) = \dfrac{1}{n h}\sum_{i=1}^{n} K\!\left(\dfrac{x - x_i}{h}\right)$, где $K$ — ядро, $h$ — ширина полосы (bandwidth), аналог ширины бина.
import math
data = [4,5,6,6,7,7,7,8,8,9,10,12]
h = 1.0 # ширина полосы (bandwidth)
def gauss(u):
return math.exp(-0.5*u*u) / math.sqrt(2*math.pi)
def kde(x):
n = len(data)
return sum(gauss((x - xi)/h) for xi in data) / (n*h)
for x in range(4, 13):
val = kde(x)
print(f"x={x:2d} плотность={val:.3f} {'*'*int(val*40)}")Вывод:
x= 4 плотность=0.064 ** x= 5 плотность=0.108 **** x= 6 плотность=0.161 ****** x= 7 плотность=0.190 ******* x= 8 плотность=0.161 ****** x= 9 плотность=0.108 **** x=10 плотность=0.068 ** x=11 плотность=0.046 * x=12 плотность=0.038 *
Получилась гладкая оценка плотности без зависимости от границ бинов. Пик у $x=7$ соответствует моде данных.
Как работает под капотом
KDE — это свёртка эмпирического распределения (набора дельта-функций в точках данных) с ядром. Ширина полосы $h$ управляет гладкостью так же, как ширина бина в гистограмме: малое $h$ даёт изрезанную кривую (переобучение под шум), большое — переглаживает и сливает моды. Выбор $h$ — тот же компромисс смещение–дисперсия. Правило Сильвермана даёт разумную автоматическую оценку $h$.
Граничный эффект и выбор ядра
У KDE есть коварная ловушка на ограниченных величинах. Если данные неотрицательны (время, концентрация, длина) и есть точки около нуля, гауссово ядро «протечёт» в область отрицательных значений, нарисовав плотность там, где данные физически невозможны. На графике это выглядит как мягкий «хвост» влево от нуля — артефакт метода, а не свойство данных. Решения: отражать данные относительно границы, использовать ядра с ограниченным носителем или просто честно обрезать кривую на границе и предупредить читателя.
Выбор формы ядра (гаусс, Епанечников, прямоугольное) на практике влияет слабо — куда важнее ширина полосы. Гауссово ядро популярно за гладкость, ядро Епанечникова оптимально в смысле минимума ошибки, но разница между ними обычно незаметна глазом. А вот ошибка в bandwidth видна сразу: слишком малый превращает плотность в частокол пиков по числу точек, слишком большой сливает реальные моды в один холм. Поэтому, как и с бинами, проверяйте несколько значений ширины.
Частые ошибки
- Сравнивать сырые гистограммы выборок разного размера — нужна нормировка плотности.
- KDE с дефолтным bandwidth — может выдумать или стереть моды; проверяйте 2–3 значения.
- KDE на ограниченной величине (например, неотрицательной) — ядро «протекает» за границу в физически невозможную область.
- Подмена данных гладкой кривой — KDE скрывает дискретность и реальное число точек; на малых выборках показывайте и сами точки (rug plot).
Итог
- Нормировка делает площадь равной 1 — формы сравнимы между выборками.
- KDE — гладкая оценка плотности, не зависящая от границ бинов.
- Bandwidth играет роль ширины бина: тот же компромисс гладкости.
- На малых выборках дополняйте KDE точками данных.