scipy.stats: распределения и описательная статистика
scipy.stats — это «статистический пакет» внутри SciPy: десятки распределений и инструментов описать данные одним числом.
scipy.stats — подмодуль для вероятностных распределений, описательной статистики и статистических тестов.
Зачем отдельный статистический подмодуль
Данные почти всегда содержат случайность, и наука требует её описывать строго: какое распределение у ошибок измерений, насколько правдоподобна гипотеза, какова доверительная вероятность. В нашем отдельном курсе статистики мы разбираем теорию; здесь — как этим пользоваться в научном Python. scipy.stats даёт готовые «объекты-распределения» и тесты, чтобы не выводить формулы вручную.
Описательная статистика «руками»
Прежде чем звать SciPy, поймём, что считается. Среднее, дисперсия, стандартное отклонение — на чистом stdlib (модуль statistics):
import statistics
sample = [4, 8, 15, 16, 23, 42]
print("Среднее :", statistics.mean(sample))
print("Медиана :", statistics.median(sample))
print("Дисперсия :", round(statistics.pvariance(sample), 3))
print("Ст. откл. :", round(statistics.pstdev(sample), 3))
Вывод:
Среднее : 18 Медиана : 15.5 Дисперсия : 151.667 Ст. откл. : 12.315
Распределения как объекты
В scipy.stats каждое распределение — объект с единым набором методов. Для нормального распределения norm:
| Метод | Что даёт |
.pdf(x) | плотность вероятности в точке x |
.cdf(x) | P(X ≤ x) — функция распределения |
.ppf(q) | квантиль: x, при котором cdf = q (обратная к cdf) |
.rvs(n) | сгенерировать n случайных значений |
.mean(), .std() | теоретические среднее и отклонение |
from scipy.stats import norm
print(norm.cdf(0)) # 0.5 — половина массы левее нуля
print(norm.ppf(0.975)) # 1.959963... — знаменитое "1.96" для 95%
print(norm.pdf(0)) # 0.3989... — пик плотности
sample = norm.rvs(size=1000, loc=0, scale=1) # 1000 случайных из N(0,1)
Единый интерфейс — мощь подмодуля: тот же набор методов есть у expon, binom, poisson, t, chi2 и десятков других.
Считаем «правило 1.96» руками
Откуда берётся 1.96? Это квантиль 0.975 стандартной нормали. Аппроксимируем функцию распределения через math.erf (она есть в stdlib!) и проверим:
import math
def normal_cdf(x):
# CDF стандартной нормали через функцию ошибок
return 0.5 * (1 + math.erf(x / math.sqrt(2)))
print("P(Z <= 1.96) =", round(normal_cdf(1.96), 4))
print("P(Z <= 0) =", round(normal_cdf(0.0), 4))
print("Доля в [-1.96, 1.96] =",
round(normal_cdf(1.96) - normal_cdf(-1.96), 4))
Вывод:
P(Z <= 1.96) = 0.975 P(Z <= 0) = 0.5 Доля в [-1.96, 1.96] = 0.95
Вот почему доверительный интервал «±1.96 σ» накрывает 95% — мы только что вывели это руками.
Как работает под капотом
Все непрерывные распределения в scipy.stats наследуются от общего класса rv_continuous. Если у распределения задана плотность pdf, библиотека умеет автоматически (численно) получить из неё cdf (интегрированием), ppf (обращением cdf поиском корня), моменты (интегрированием) и генерацию rvs. Поэтому добавить своё распределение — это часто просто задать одну функцию плотности. А «быстрые» распределения вроде нормального имеют аналитические формулы (через erf из scipy.special), чтобы не интегрировать численно каждый раз.
Частые ошибки
- Путать pdf и cdf. Плотность
pdfсама по себе не вероятность; вероятность — это площадь, то естьcdf. - Выборочная vs генеральная дисперсия.
statistics.pvarianceделит на n,variance— на n−1; SciPy и pandas по умолчанию используют разные соглашения (параметрddof). - loc/scale. В SciPy сдвиг и масштаб задаются параметрами
locиscale, а не как аргументы — частая путаница.
Итог
scipy.stats— распределения как объекты с единым набором методов (pdf/cdf/ppf/rvs).- Описательную статистику можно считать и на stdlib (
statistics), и в SciPy. - «Правило 1.96» — это квантиль 0.975 нормали; выводится через
erf. - Под капотом непрерывные распределения порождают cdf/ppf/моменты из одной плотности.