Нормальное распределение данных
Нормальное распределение в Python: PDF, CDF, Z-оценки, эмпирическое правило, проверка нормальности и логарифмические преобразования.
Нормальное распределение (также называемое распределением Гаусса) — это наиболее важное вероятностное распределение в статистике и машинном обучении. Глубокое понимание его — не просто узнавание формы колокола, но знание того, как его измерить, проверить и работать с ним в Python — даёт прочную основу для подготовки данных, выбора моделей и интерпретации результатов.
В этой главе рассматривается:
- Что такое нормальное распределение и почему его форма важна
- Эмпирическое правило (правило 68–95–99,7) и способы его проверки
- Генерация нормально распределённых данных с помощью NumPy
- Оценка функции плотности вероятности (PDF) и функции кумулятивного распределения (CDF) с помощью SciPy
- Вычисление и интерпретация Z-оценок
- Проверка нормальности распределения данных
- Что делать, если данные не нормальны
Что такое нормальное распределение?
Вероятностное распределение описывает, насколько вероятно появление каждого значения в наборе данных. Нормальное распределение — это непрерывное, симметричное, колоколообразное распределение, определяемое двумя параметрами:
- Среднее (μ) — центр колокола; место расположения пика
- Стандартное отклонение (σ) — ширина или узость колокола; большее σ рассредотачивает значения дальше от центра
Поскольку кривая симметрична относительно среднего, среднее, медиана и мода равны для идеально нормального распределения.
Формула функции плотности вероятности:
f(x) = (1 / (σ√(2π))) × e^(−(x−μ)² / (2σ²))Вам не нужно применять эту формулу вручную — stats.norm из SciPy справляется с этим — но полезно знать, что форма зависит только от μ и σ.
Эмпирическое правило (68–95–99,7)
Одно из наиболее практичных свойств нормального распределения — эмпирическое правило, также называемое правилом 68–95–99,7:
| Диапазон от среднего | Доля значений |
|---|---|
| В пределах ±1 стандартного отклонения | ~68% |
| В пределах ±2 стандартных отклонений | ~95% |
| В пределах ±3 стандартных отклонений | ~99,7% |
Это означает, что в нормально распределённом наборе данных почти все значения находятся в пределах трёх стандартных отклонений от среднего. Значения за пределами ±3σ действительно редки — примерно 1 из 370 наблюдений.
Следующий пример моделирует 10 000 значений из стандартного нормального распределения (μ=0, σ=1) и проверяет правило:
import numpy as np
rng = np.random.default_rng(seed=42)
mu, sigma = 0, 1
data = rng.normal(loc=mu, scale=sigma, size=10000)
w1 = np.mean(np.abs(data - mu) < 1 * sigma) * 100
w2 = np.mean(np.abs(data - mu) < 2 * sigma) * 100
w3 = np.mean(np.abs(data - mu) < 3 * sigma) * 100
print(f"Within 1 std: {w1:.1f}% (expected ~68%)")
print(f"Within 2 std: {w2:.1f}% (expected ~95%)")
print(f"Within 3 std: {w3:.1f}% (expected ~99.7%)")Вывод:
Within 1 std: 67.8% (expected ~68%)
Within 2 std: 95.4% (expected ~95%)
Within 3 std: 99.7% (expected ~99.7%)Эмпирическое правило сразу же применимо на практике: если результат теста более чем на два стандартных отклонения выше среднего, он попадает в топ ~2,5% всех результатов.
Генерация нормально распределённых данных с NumPy
Генератор случайных чисел NumPy производит выборки, аппроксимирующие нормальное распределение. Метод rng.normal() принимает среднее (loc), стандартное отклонение (scale) и количество выборок (size):
import numpy as np
rng = np.random.default_rng(seed=7)
# Simulate IQ scores: mean=100, std=15
samples = rng.normal(loc=100, scale=15, size=1000)
print(f"Sample mean: {samples.mean():.1f}")
print(f"Sample std: {samples.std():.1f}")
print(f"Min: {samples.min():.1f}")
print(f"Max: {samples.max():.1f}")
print(f"Sample size: {len(samples)}")Вывод:
Sample mean: 98.9
Sample std: 14.1
Min: 51.2
Max: 138.6
Sample size: 1000Выборочное среднее и стандартное отклонение близки — но не точно равны — 100 и 15, поскольку конечная выборка имеет естественную случайную вариацию. При 10 000 выборок оценки сходятся ближе к истинным параметрам.
Зачем использовать seed? Передача seed=7 в default_rng делает результаты воспроизводимыми. Любой, кто запускает один и тот же код, получает одинаковые числа, что важно для отладки и обмена результатами. Интерфейс default_rng (введённый в NumPy 1.17) предпочтительнее старого np.random.normal(), так как исключает использование общего глобального состояния.
Оценка PDF и CDF с помощью SciPy
Функция плотности вероятности (PDF)
PDF показывает относительную вероятность значения. Более высокое значение PDF в точке x означает, что x с большей вероятностью появится. Для нормального распределения пик находится в точке среднего:
from scipy import stats
mu, sigma = 0, 1
x_values = [-2, -1, 0, 1, 2]
for x in x_values:
pdf = stats.norm.pdf(x, loc=mu, scale=sigma)
print(f" pdf({x:2d}) = {pdf:.4f}")Вывод:
pdf(-2) = 0.0540
pdf(-1) = 0.2420
pdf( 0) = 0.3989
pdf( 1) = 0.2420
pdf( 2) = 0.0540PDF симметрична: pdf(-1) равна pdf(1), а pdf(0) — максимальное значение (пик колоколообразной кривой). Само значение PDF не является вероятностью — это плотность. Чтобы получить вероятность, нужно проинтегрировать PDF по диапазону, что и делает CDF.
Функция кумулятивного распределения (CDF)
CDF в точке x даёт вероятность того, что случайно выбранное наблюдение меньше или равно x. Используйте stats.norm.cdf() для ответа на практические вопросы о вероятностях:
from scipy import stats
mu, sigma = 170, 10 # adult heights in cm
# Probability that a randomly chosen person is shorter than 180 cm
p_below_180 = stats.norm.cdf(180, loc=mu, scale=sigma)
print(f"P(height < 180 cm) = {p_below_180:.4f}")
# Probability of being taller than 185 cm
p_above_185 = 1 - stats.norm.cdf(185, loc=mu, scale=sigma)
print(f"P(height > 185 cm) = {p_above_185:.4f}")
# Probability of falling between 160 cm and 180 cm
p_range = stats.norm.cdf(180, loc=mu, scale=sigma) - stats.norm.cdf(160, loc=mu, scale=sigma)
print(f"P(160 < height < 180 cm) = {p_range:.4f}")Вывод:
P(height < 180 cm) = 0.8413
P(height > 185 cm) = 0.0668
P(160 < height < 180 cm) = 0.6827Третий результат подтверждает эмпирическое правило: диапазон μ±σ (160–180 см) содержит около 68% наблюдений.
Z-оценки: стандартизация распределения
Z-оценка (также называемая стандартной оценкой) измеряет, на сколько стандартных отклонений значение отстоит от среднего:
Z = (x − μ) / σZ-оценки позволяют сравнивать значения из распределений с разными средними и стандартными отклонениями по общей шкале. Z-оценка +2.0 означает, что значение на два стандартных отклонения выше среднего, независимо от исходных единиц измерения.
import numpy as np
from scipy import stats
heights = np.array([171.3, 168.7, 176.4, 171.0, 164.6, 173.6, 183.0, 179.5])
# Calculate Z-scores manually
mean = heights.mean()
std = heights.std(ddof=1) # ddof=1 for the sample standard deviation
z_manual = (heights - mean) / std
print("Z-scores (manual):", np.round(z_manual, 2))
# Or use scipy directly
z_scipy = stats.zscore(heights, ddof=1)
print("Z-scores (scipy): ", np.round(z_scipy, 2))Вывод:
Z-scores (manual): [-0.37 -0.81 0.49 -0.42 -1.5 0.01 1.59 1.01]
Z-scores (scipy): [-0.37 -0.81 0.49 -0.42 -1.5 0.01 1.59 1.01]Человек ростом 183,0 см имеет Z-оценку +1.59, что означает: он на 1,59 стандартного отклонения выше среднего. В нормальном распределении это помещает его примерно в топ 6% населения.
Когда Z-оценки полезны
- Обнаружение выбросов: значения с
|Z| > 3почти наверняка являются выбросами в нормальном распределении. - Масштабирование признаков: преобразование признаков в Z-оценки (нулевое среднее, единичная дисперсия) называется стандартизацией и требуется для алгоритмов, чувствительных к масштабу признаков, таких как SVM и k-ближайших соседей. Смотрите главу о масштабировании признаков, чтобы узнать, как применять это с помощью
StandardScalerиз scikit-learn. - Сравнение разнородных величин: если вы хотите сравнить результат студента по математике (среднее 70, std 10) с результатом по чтению (среднее 50, std 5), Z-оценки обеспечивают справедливое сравнение.
Проверка нормальности распределения данных
Перед применением алгоритмов, предполагающих нормальность, проверьте это допущение. Два наиболее распространённых подхода — тест Шапиро-Уилка (для выборок до ~5 000) и быстрая визуальная проверка.
Тест Шапиро-Уилка
Тест Шапиро-Уилка возвращает статистику W и p-значение. P-значение выше 0,05 означает, что недостаточно доказательств для отклонения нормальности. P-значение на уровне или ниже 0,05 указывает на то, что данные скорее всего не являются нормальными.
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=42)
# Normally distributed sample
normal_sample = rng.normal(loc=0, scale=1, size=50)
stat_n, p_n = stats.shapiro(normal_sample)
print(f"Normal sample — W={stat_n:.3f}, p={p_n:.3f}")
if p_n > 0.05:
print(" => Cannot reject normality.")
else:
print(" => Reject normality.")
# Skewed sample (exponential distribution)
skewed_sample = rng.exponential(scale=1, size=50)
stat_s, p_s = stats.shapiro(skewed_sample)
print(f"Skewed sample — W={stat_s:.3f}, p={p_s:.3f}")
if p_s > 0.05:
print(" => Cannot reject normality.")
else:
print(" => Reject normality.")Вывод:
Normal sample — W=0.984, p=0.730
=> Cannot reject normality.
Skewed sample — W=0.808, p=0.000
=> Reject normality.Нормальная выборка легко проходит тест (p=0.730). Асимметричная выборка явно его не проходит (p≈0).
Когда проверка нормальности важна
Не каждому алгоритму нужны нормально распределённые признаки. Модели на основе деревьев (деревья решений, случайные леса, градиентный бустинг) и нейронные сети полностью не зависят от распределения. Нормальность наиболее важна для:
- Параметрических статистических тестов (t-тест, ANOVA, корреляция Пирсона) — p-значения этих тестов действительны только при приближённо нормальном распределении данных.
- Линейного дискриминантного анализа (LDA) — предполагает, что каждый класс нормально распределён.
- Гауссовского наивного байесовского классификатора — явно моделирует каждый признак как гауссовский.
- Доверительных интервалов и интервалов предсказания в линейной регрессии — выводятся из нормальности остатков.
Когда данные не нормальны: логарифмическое преобразование
Распространённым средством для данных с правосторонней асимметрией (например, доходы, цены на жильё, суммы транзакций) является логарифмическое преобразование, которое сжимает большие значения и растягивает малые. Результат часто близок к нормальному:
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=7)
# Simulate log-normally distributed incomes (a realistic pattern)
incomes = rng.lognormal(mean=10.5, sigma=0.5, size=1000)
log_incomes = np.log(incomes)
sk_before = stats.skew(incomes)
sk_after = stats.skew(log_incomes)
stat_before, p_before = stats.shapiro(incomes[:50])
stat_after, p_after = stats.shapiro(log_incomes[:50])
print(f"Before transform — skewness: {sk_before:.2f}, Shapiro p={p_before:.3f}")
print(f"After log transform — skewness: {sk_after:.2f}, Shapiro p={p_after:.3f}")Вывод:
Before transform — skewness: 1.44, Shapiro p=0.000
After log transform — skewness: 0.01, Shapiro p=0.940После логарифмического преобразования асимметрия снижается с 1.44 почти до нуля, а p-значение Шапиро-Уилка возрастает с 0.000 до 0.940 — преобразованные данные легко проходят тест на нормальность.
Важные замечания:
- Логарифмическое преобразование требует, чтобы все значения были строго положительными. Если данные содержат нули, используйте
np.log(x + 1)(преобразование логарифм-плюс-один). - Если данные имеют левостороннюю асимметрию (длинный хвост слева), попробуйте вместо этого квадратное или зеркальное преобразование.
- Всегда применяйте одно и то же преобразование как к обучающей выборке, так и к новым данным при выводе модели.
Почему нормальное распределение важно в машинном обучении
Нормальное распределение встречается повсюду в ML, часто неявно:
- Центральная предельная теорема: среднее большой случайной выборки приближённо нормально распределено, независимо от исходного распределения. Это лежит в основе доверительных интервалов и проверки гипотез для метрик моделей.
- Анализ остатков: остатки хорошо подогнанной линейной регрессии должны быть приближённо нормальными. Отклонения сигнализируют о неправильной спецификации модели.
- Инициализация весов в нейронных сетях: схемы Xavier и He инициализируют начальные веса из нормальных распределений, чтобы предотвратить затухание или взрыв градиентов.
- Гауссовские процессы: семейство вероятностных моделей, задающих нормальное распределение над функциями; применяются в байесовской оптимизации и квантификации неопределённости.
- Обнаружение выбросов: во многих областях данные за пределами ±3σ от среднего помечаются как аномалии.
Понимание того, где предполагается нормальное распределение — и когда это допущение нарушается — помогает избежать скрытых ошибок моделирования.
Резюме
- Нормальное распределение определяется средним (μ) и стандартным отклонением (σ); его колоколообразная кривая симметрична относительно среднего.
- Эмпирическое правило гласит, что 68%, 95% и 99,7% значений находятся в пределах 1, 2 и 3 стандартных отклонений от среднего соответственно.
- Используйте
numpy.random.default_rng().normal()для генерации нормально распределённых выборок иscipy.stats.norm.pdf()/.cdf()для оценки вероятностей. - Z-оценки стандартизируют значения по общей шкале; они необходимы для обнаружения выбросов и масштабирования признаков.
- Используйте
scipy.stats.shapiro()для формальной проверки нормальности — но помните, что многие современные алгоритмы ML этого не требуют. - Когда данные имеют правостороннюю асимметрию, логарифмическое преобразование часто делает их приближённо нормальными.
Для более широкого сравнения типов распределений (равномерное, асимметричное, мультимодальное) смотрите главу Распределение данных. О том, как измерять разброс и центр, смотрите в главах Стандартное отклонение и Среднее, медиана и мода.