Проверка гипотезы о неизвестном матожидании (известная дисперсия)

Разбираем сюжет с жирафами в трех вариантах (длина шеи взрослых особей — порядка \(1.8\)\(2.4\) м): - левосторонний тест; - правосторонний тест; - двусторонний тест.

Наблюдаемые \(\bar x\) правдоподобны, а консервативные \(\mu_0\) слегка гротескны, но не настолько как рассматривали в лекции, чтобы получились какие-то осязаемые числа. Вы можете подставить гротескные числа консервативных гипотез из лекций и посмотреть, что будет.

Общие условия применимости \(Z\)-теста для среднего: - наблюдения независимы; - либо распределение исследуемой величины нормальное, либо выборка достаточно большая; - известна истинная дисперсия: \(\sigma = 0.35\) м (одна и та же для всех трёх сюжетов); - уровень значимости фиксирован: \(\alpha = 0.05\).

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# Общая σ для всех сюжетов
SIGMA = 0.35


def _fill_tail(ax, x, y, mask, **kwargs):
    if mask.any():
        ax.fill_between(x[mask], y[mask], **kwargs)


def plot_hypothesis_test(mu0, sigma, n, x_bar, alpha=0.05, kind='left', title=''):
    from matplotlib.patches import ConnectionPatch

    se = sigma / np.sqrt(n)
    z_score = (x_bar - mu0) / se
    z_abs = abs(z_score)

    fig, (ax_top, ax_bottom) = plt.subplots(2, 1, figsize=(8, 9), height_ratios=[1, 1])
    fig.subplots_adjust(hspace=0.38)

    x_grid = np.linspace(mu0 - 4 * se, mu0 + 4 * se, 1000)
    y_top = norm.pdf(x_grid, loc=mu0, scale=se)
    z_grid = np.linspace(-4, 4, 1000)
    y_bot = norm.pdf(z_grid)

    ax_top.plot(x_grid, y_top, lw=2, color='0.25', label=r'$\bar{X} \mid H_0$')
    ax_bottom.plot(z_grid, y_bot, lw=2, color='0.25', label=r'$Z$')

    def link(x_val, z_val, color):
        con = ConnectionPatch(
            xyA=(x_val, 0),
            coordsA=ax_top.get_xaxis_transform(),
            xyB=(z_val, 0),
            coordsB=ax_bottom.get_xaxis_transform(),
            color=color,
            ls='--',
            lw=1.8,
            axesA=ax_top,
            axesB=ax_bottom,
        )
        ax_bottom.add_artist(con)

    p_label = None

    if kind == 'left':
        z_crit = norm.ppf(alpha)
        K = mu0 + z_crit * se
        p_value = norm.cdf(z_score)
        reject = x_bar < K
        print(f'K = {K:.3f}')
        print('Решение (граница K):', 'Отклоняем H0' if reject else 'Не отклоняем H0')
        ax_top.axvline(K, color='tomato', ls='--', lw=2, label=rf'$K={K:.3f}$')
        ax_bottom.axvline(z_crit, color='tomato', ls='--', lw=2, label=rf'$z_\alpha={z_crit:.3f}$')
        link(K, z_crit, 'tomato')
        _fill_tail(ax_top, x_grid, y_top, x_grid <= K, color='tomato', alpha=0.35, label=r'Критическая зона ($\alpha$)')
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid <= z_crit, color='tomato', alpha=0.35)
        p_label = rf'p-value = {p_value:.4f}'
        _fill_tail(ax_top, x_grid, y_top, x_grid <= x_bar, color='steelblue', alpha=0.4, label=p_label)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid <= z_score, color='steelblue', alpha=0.4)

    elif kind == 'right':
        z_crit = norm.ppf(1 - alpha)
        K = mu0 + z_crit * se
        p_value = 1 - norm.cdf(z_score)
        reject = x_bar > K
        print(f'K = {K:.3f}')
        print('Решение (граница K):', 'Отклоняем H0' if reject else 'Не отклоняем H0')
        ax_top.axvline(K, color='tomato', ls='--', lw=2, label=rf'$K={K:.3f}$')
        ax_bottom.axvline(z_crit, color='tomato', ls='--', lw=2, label=rf'$z_\alpha={z_crit:.3f}$')
        link(K, z_crit, 'tomato')
        _fill_tail(ax_top, x_grid, y_top, x_grid >= K, color='tomato', alpha=0.35, label=r'Критическая зона ($\alpha$)')
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid >= z_crit, color='tomato', alpha=0.35)
        p_label = rf'p-value = {p_value:.4f}'
        _fill_tail(ax_top, x_grid, y_top, x_grid >= x_bar, color='steelblue', alpha=0.4, label=p_label)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid >= z_score, color='steelblue', alpha=0.4)

    elif kind == 'two-sided':
        z_half = norm.ppf(1 - alpha / 2)
        K_L = mu0 - z_half * se
        K_R = mu0 + z_half * se
        z_crit_l, z_crit_r = -z_half, z_half
        p_value = 2 * (1 - norm.cdf(z_abs))
        reject = abs(z_score) > z_half
        x_cut_l = mu0 - z_abs * se
        x_cut_r = mu0 + z_abs * se
        print(f'K_L = {K_L:.3f}, K_R = {K_R:.3f}')
        print('Решение (границы):', 'Отклоняем H0' if reject else 'Не отклоняем H0')
        ax_top.axvline(K_L, color='tomato', ls='--', lw=2, label=rf'$K_L={K_L:.3f}$')
        ax_top.axvline(K_R, color='tomato', ls='--', lw=2, label=rf'$K_R={K_R:.3f}$')
        ax_bottom.axvline(z_crit_l, color='tomato', ls='--', lw=2, label=rf'$-z_{{\alpha/2}}={z_crit_l:.3f}$')
        ax_bottom.axvline(z_crit_r, color='tomato', ls='--', lw=2, label=rf'$+z_{{\alpha/2}}={z_crit_r:.3f}$')
        link(K_L, z_crit_l, 'tomato')
        link(K_R, z_crit_r, 'tomato')
        # границы p-value (симметричные относительно mu0)
        ax_top.axvline(x_cut_l, color='steelblue', ls=':', lw=1.2)
        ax_top.axvline(x_cut_r, color='steelblue', ls=':', lw=1.2)
        ax_bottom.axvline(-z_abs, color='steelblue', ls=':', lw=1.2)
        ax_bottom.axvline(z_abs, color='steelblue', ls=':', lw=1.2)
        _fill_tail(ax_top, x_grid, y_top, x_grid <= K_L, color='tomato', alpha=0.35, label=r'Критическая зона ($\alpha$)')
        _fill_tail(ax_top, x_grid, y_top, x_grid >= K_R, color='tomato', alpha=0.35)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid <= z_crit_l, color='tomato', alpha=0.35)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid >= z_crit_r, color='tomato', alpha=0.35)
        p_label = rf'p-value = {p_value:.4f} (два хвоста)'
        # два отдельных хвоста — иначе fill_between «склеивает» середину
        _fill_tail(ax_top, x_grid, y_top, x_grid <= x_cut_l, color='steelblue', alpha=0.4, label=p_label)
        _fill_tail(ax_top, x_grid, y_top, x_grid >= x_cut_r, color='steelblue', alpha=0.4)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid <= -z_abs, color='steelblue', alpha=0.4)
        _fill_tail(ax_bottom, z_grid, y_bot, z_grid >= z_abs, color='steelblue', alpha=0.4)
    else:
        raise ValueError("kind должен быть 'left', 'right' или 'two-sided'")

    ax_top.axvline(mu0, color='gray', ls=':', lw=1)
    ax_bottom.axvline(0, color='gray', ls=':', lw=1)
    ax_top.axvline(x_bar, color='navy', lw=2.5, label=rf'$\bar{{x}}={x_bar:.3f}$')
    ax_bottom.axvline(z_score, color='navy', lw=2.5, label=rf'$z_{{score}}={z_score:.3f}$')
    link(x_bar, z_score, 'navy')

    ax_top.set_title('Исходная шкала')
    ax_bottom.set_title('Стандартная нормальная шкала')
    ax_top.set_xlabel(r'$\bar{X}$')
    ax_bottom.set_xlabel(r'$Z$')
    ax_top.set_ylabel('Плотность')
    ax_bottom.set_ylabel('Плотность')
    ax_top.legend(loc='best', fontsize=9)
    ax_bottom.legend(loc='best', fontsize=9)
    ax_top.grid(alpha=0.2)
    ax_bottom.grid(alpha=0.2)
    if title:
        fig.suptitle(title, y=1.01, fontsize=12)

    print(f'$z_{{score}}$ = {z_score:.4f}')
    print(f'$p$-value = {p_value:.6f}')
    print('Решение (score / p-value):', 'Отклоняем H0' if p_value < alpha else 'Не отклоняем H0')
    plt.show()

1. Левосторонний тест

Условия задачи: - \(X\) — длина шеи взрослого жирафа (м); в природе типичный диапазон примерно \(1.8\)\(2.4\) м; - независимая выборка объема \(n = 20\); - наблюдаемое среднее \(\bar x = 2.1\); - консервативная нулевая гипотеза: \(\mu_0 = 2.38\) — исследователи уверены, что в среднем у жирафов большие шеи; - проверяем при \(\alpha = 0.05\): предоставляют ли полученные данные достаточно доказательств в сторону альтернативной гипотезы (\(H_1: \mu < \mu_0\)).

Теория: - гипотезы: \(H_0: \mu = \mu_0\) против \(H_1: \mu < \mu_0\); - при \(H_0\): \[ \bar X \sim \mathcal N\!\left(\mu_0, \frac{\sigma^2}{n}\right); \] - решающая граница задается условием: \[ P_{H_0}(\bar X < K) = \alpha; \] - стандартизуем и делаем замену переменной: \[ P\!\left(\frac{\bar X-\mu_0}{\sigma/\sqrt n} < \frac{K-\mu_0}{\sigma/\sqrt n}\right)=\alpha \Rightarrow P(Z<z_\alpha)=\alpha; \]

  • нужно понимать, что так как \(\alpha\) по своей логике должна быть небольшой вероятностью и по здравому смыслу меньше \(0.5\), то в уравнении выше эта точка по факту будет отрицательной. Можно обозначить ее \((-|z_{\alpha}|)\), чтобы не было сомнений.
  • восстанавливаем границу: \[ \frac{K-\mu_0}{\sigma/\sqrt n}=-|z_{\alpha}| \Rightarrow K=\mu_0 - |z_\alpha| \frac{\sigma}{\sqrt n}; \]
  • правило: отклоняем \(H_0\), если \(\bar x < K\).

\(z\)-score и \(p\)-value (левосторонний тест)

\(z\)-score — наблюдаемое значение в стандартизованной шкале: \[ z_{\text{score}} = \frac{\bar{x}-\mu_0}{\sigma/\sqrt{n}}. \] Граница \(K\) переходит в критическую точку \(z_\alpha\) (квантиль уровня \(\alpha\)): \[ \frac{K-\mu_0}{\sigma/\sqrt{n}} = z_\alpha. \] Такое преобразования сохраняет отношение порядка: \(\bar{x} < K \Leftrightarrow z_{\text{score}} < z_\alpha\).

Правило через score: отклоняем \(H_0\), если \(z_{\text{score}} < z_\alpha\) (для \(\alpha<0.5\), \(z_\alpha\) - это отрицательное число).

\(p\)-value — вероятность при \(H_0\) получить статистику не менее экстремальную, чем наблюдаемая (для левого теста — не больше \(\bar{x}\)): \[ p\text{-value} = P_{H_0}(\bar{X} \le \bar{x}) = P_{H_0}(Z \le z_{\text{score}}). \]

Правило через \(p\)-value: отклоняем \(H_0\), если \(p\text{-value} < \alpha\).

# Левосторонний тест
mu0 = 2.38
sigma = SIGMA
n = 20
x_bar = 2.1
alpha = 0.05

plot_hypothesis_test(
    mu0=mu0,
    sigma=sigma,
    n=n,
    x_bar=x_bar,
    alpha=alpha,
    kind='left',
    title='Левосторонний тест',
)

2. Правосторонний тест

Условия задачи: - \(X\) — длина шеи взрослого жирафа (м); - независимая выборка объема \(n = 20\); - наблюдаемое среднее \(\bar x = 2.1\); - консервативная нулевая гипотеза: \(\mu_0 = 1.88\) — исследователи уверены, что в среднем у жирафов маленькие шеи; - проверяем при \(\alpha = 0.05\): предоставляют ли полученные данные достаточно доказательств в сторону альтернативной гипотезы (\(H_1: \mu > \mu_0\)).

Теория: - гипотезы: \(H_0: \mu = \mu_0\) против \(H_1: \mu > \mu_0\); - при \(H_0\): \[ \bar X \sim \mathcal N\!\left(\mu_0, \frac{\sigma^2}{n}\right); \] - решающая граница задается условием: \[ P_{H_0}(\bar X > K) = \alpha; \] - стандартизуем и делаем замену переменной: \[ P\!\left(\frac{\bar X-\mu_0}{\sigma/\sqrt n} > \frac{K-\mu_0}{\sigma/\sqrt n}\right)=\alpha \Rightarrow P(Z>z_{\alpha})=\alpha; \] - снова так как \(\alpha\) по своей логике должна быть небольшой вероятностью и по здравому смыслу меньше \(0.5\), то в уравнении выше эта точка должна быть положительной. Тут не возникнет путаницы, можно продолжать без знака модуля.

  • восстанавливаем границу: \[ \frac{K-\mu_0}{\sigma/\sqrt n}=z_{\alpha} \Rightarrow K=\mu_0+z_{\alpha}\frac{\sigma}{\sqrt n}; \]
  • правило: отклоняем \(H_0\), если \(\bar x > K\).

\(z\)-score и \(p\)-value (правосторонний тест)

\(z\)-score: \[ z_{\text{score}} = \frac{\bar{x}-\mu_0}{\sigma/\sqrt{n}}. \] Решающая граница \(K\) соответствует критической точке \(z_\alpha\): \[ \frac{K-\mu_0}{\sigma/\sqrt{n}} = z_\alpha. \] Такое преобразования сохраняет отношение порядка: \(\bar{x} > K \Leftrightarrow z_{\text{score}} > z_\alpha\).

Правило через score: отклоняем \(H_0\), если \(z_{\text{score}} > z_\alpha\).

\(p\)-value для правостороннего теста — вероятность при \(H_0\) получить значение не менее экстремальное вправо: \[ p\text{-value} = P_{H_0}(\bar{X} \ge \bar{x}) = P_{H_0}(Z \ge z_{\text{score}}). \]

Правило через \(p\)-value: отклоняем \(H_0\), если \(p\text{-value} < \alpha\).

# Правосторонний тест
mu0 = 1.88
sigma = SIGMA
n = 20
x_bar = 2.1
alpha = 0.05

plot_hypothesis_test(
    mu0=mu0,
    sigma=sigma,
    n=n,
    x_bar=x_bar,
    alpha=alpha,
    kind='right',
    title='Правосторонний тест',
)

3. Двусторонний тест

Условия задачи: - \(X\) — длина шеи взрослого жирафа (м); - независимая выборка объема \(n = 20\); - наблюдаемое среднее \(\bar x = 2.1\); - консервативная нулевая гипотеза: \(\mu_0 = 2.3\);

Теория: - гипотезы: \(H_0: \mu = \mu_0\) против \(H_1: \mu \neq \mu_0\); - при \(H_0\): \[ \bar X \sim \mathcal N\!\left(\mu_0, \frac{\sigma^2}{n}\right); \] - границы задаются условиями: \[ P_{H_0}(\bar X < K_L)=\frac{\alpha}{2},\qquad P_{H_0}(\bar X > K_R)=\frac{\alpha}{2}; \] - стандартизуем и делаем замену переменной: \[ P\!\left(\frac{K_L-\mu_0}{\sigma/\sqrt n} < Z < \frac{K_R-\mu_0}{\sigma/\sqrt n}\right)=1-\alpha \Rightarrow P(-z_{\alpha/2}<Z<z_{\alpha/2})=1-\alpha; \] - восстанавливаем границы: \[ K_L=\mu_0-z_{\alpha/2}\frac{\sigma}{\sqrt n},\qquad K_R=\mu_0+z_{\alpha/2}\frac{\sigma}{\sqrt n}; \] - правило: отклоняем \(H_0\), если \(\bar x < K_L\) или \(\bar x > K_R\).

\(z\)-score и \(p\)-value (двусторонний тест)

\(z\)-score считается так же: \[ z_{\text{score}} = \frac{\bar{x}-\mu_0}{\sigma/\sqrt{n}}. \] Границы \(K_L, K_R\) переходят в \(\pm z_{\alpha/2}\): \[ K_L = \mu_0 - z_{\alpha/2}\frac{\sigma}{\sqrt{n}}, \qquad K_R = \mu_0 + z_{\alpha/2}\frac{\sigma}{\sqrt{n}}. \]

Правило через score: отклоняем \(H_0\), если \(|z_{\text{score}}| > z_{\alpha/2}\).

\(p\)-value для двустороннего теста — суммарная вероятность обоих хвостов, столь же или более удалённых от \(\mu_0\), чем наблюдение: \[ p\text{-value} = 2\,P_{H_0}(Z \ge |z_{\text{score}}|). \]

Правило через \(p\)-value: отклоняем \(H_0\), если \(p\text{-value} < \alpha\).

# Двусторонний тест
mu0 = 2.3
sigma = SIGMA
n = 20
x_bar = 2.1
alpha = 0.05

plot_hypothesis_test(
    mu0=mu0,
    sigma=sigma,
    n=n,
    x_bar=x_bar,
    alpha=alpha,
    kind='two-sided',
    title='Двусторонний тест',
)