W3docs

Нормальное распределение данных

Нормальное распределение в 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.0540

PDF симметрична: 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 этого не требуют.
  • Когда данные имеют правостороннюю асимметрию, логарифмическое преобразование часто делает их приближённо нормальными.

Для более широкого сравнения типов распределений (равномерное, асимметричное, мультимодальное) смотрите главу Распределение данных. О том, как измерять разброс и центр, смотрите в главах Стандартное отклонение и Среднее, медиана и мода.

Was this page helpful?