import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as st

pd.set_option('display.float_format', '{:g}'.format)
sns.set()

Parametry úlohy

K = 16  # den narození reprezentanta skupiny (1-31)
L = 3   # počet písmen v příjmení reprezentanta
M = ((K + L) * 47) % 11 + 1
pd.DataFrame([K, L, M], index=["K", "L", "M"], columns=[""])
K 16
L 3
M 3

Volba datového souboru

M Dataset Popis
3 case0201 délka humeru dle přežití vrabců

Úvod

V úvodní části načtěme datový soubor a rozdělíme sledovanou proměnnou na příslušné dvě pozorované skupiny. Stručně popíšeme data a zkoumaný problém. Pro každou skupinu zvlášť odhadneme střední hodnotu, rozptyl a medián příslušného rozdělení.

data = pd.read_csv("case0201.csv")

S = data.loc[data["Status"] == "Survived", "Humerus"]
P = data.loc[data["Status"] == "Perished", "Humerus"]

Zkoumaný problém

Po neobvykle silné zimní bouři někteří vrabci přežili a někteří zahynuli.

Zjišťujeme, zda smrt ptáků mohla být způsobena malou délkou pažní kostí.

Popis dat

Údaje v tomto datasetu se týkají délky pažních kostí 59 dospělých vrabčích samců, z nichž 24 zahynulo a 35 přežilo.

Příznak Popis Hodnoty
Humerus Délka pažní kosti dospělých samců vrabců (v setinách palcích). 733, 736, ...
Status Proměnná udávající, zda vrabec zahynul nebo přežil. survived / perished

Bodové odhady

Střední hodnotu odhadneme výběrovým průměrem

$$ \bar{X}_n = \frac{1}{n} \sum_{i=1}^{n} X_i $$

rozptyl výběrovým rozptylem

$$ s_n^2 = \frac{1}{n-1} \sum_{i=1}^{n} \left( X_i - \bar{X}_n \right)^2 $$

a směrodatnou odchylku výběrovou směrodatnou odchylkou

$$ s_n = \sqrt{s_n^2} $$
S_n = S.shape[0]
S_mean = np.mean(S)
S_var = np.var(S, ddof=1)
S_std = np.sqrt(S_var)
S_med = np.median(S)

P_n = P.shape[0]
P_mean = np.mean(P)
P_var = np.var(P, ddof=1)
P_std = np.sqrt(P_var)
P_med = np.median(P)


pd.DataFrame({
    "Četnost": [S_n, P_n],
    "Výběrový průměr": [S_mean, P_mean],
    "Výběrový rozptyl": [S_var, P_var],
    "Výběrová směrodatná odchylka": [S_std, P_std],
    "Medián": [S_med, P_med],
}, index=["Survived", "Perished"]).round(4).T
Survived Perished
Četnost 35 24
Výběrový průměr 738 727.917
Výběrový rozptyl 393.588 554.254
Výběrová směrodatná odchylka 19.8391 23.5426
Medián 736 733.5

Odhad distribuční funkce

Pro každou skupinu zvlášť odhadneme hustotu a distribuční funkci pomocí histogramu a empirické distribuční funkce.

Histogram

Hustotu odhadneme histogramem na daném rozsahu dat pomocí zvolených $k$ přihrádek.

$$ \hat{f}(x) = \frac{\text{počet pozorování ve stejné přihrádce jako } x}{\text{počet všech pozorování}} $$

Empirická distribuční funkce

Distribuční funkci odhadneme empirickou distribuční funkcí.

$$ \hat{F}(x) = \frac{1}{n} \sum_{i=1}^{n} \mathbb{1}_{\{X \le x\}} $$
def process_bins(vals, bins, normalize=True):
    density_est = []
    my_bins = []
    
    for i in range(len(vals)):
        if i == len(vals) - 1:
            bin = f"[{bins[i]:.0f}, {bins[i + 1]:.0f}]"
        else:
            bin = f"[{bins[i]:.0f}, {bins[i + 1]:.0f})"
        my_bins.append(bin)

    for val in vals:
        if normalize:
            val = val / np.sum(vals)
        density_est.append(val)

    return density_est, my_bins

def histogram(data, bin_count):
    fig, axes = plt.subplots(1, 2, figsize=(10, 5), layout="constrained")
    
    counts, bins = np.histogram(data, bins=bin_count)
    bins = bins.round()
    
    ax = axes[0]
    sns.kdeplot(data, ax=ax, linewidth=1, color="black", alpha=0.3)
    sns.histplot(data, bins=bins, stat="density", ax=ax)
    sns.rugplot(data, ax=ax, color="black")
    ax.set_title("Histogram")
    
    ax = axes[1]
    sns.ecdfplot(data, stat="proportion", ax=ax, linewidth=1.5)
    sns.kdeplot(data, cumulative=True, ax=ax, linewidth=1, color="black", alpha=0.3)
    sns.rugplot(data, ax=ax, color="black", height=0.015)
    ax.set_title("Empirical distribution function")

    plt.show()

    density_est, my_bins =  process_bins(counts, bins)
    density_table = pd.DataFrame([my_bins, np.round(density_est, 4)], index=["Přihrádka", "Odhad $f(x)$"], columns=[""]*len(my_bins))

    ecdf = st.ecdf(data)
    distrib_est, my_bins =  process_bins(ecdf.cdf.probabilities[:-1], ecdf.cdf.quantiles, normalize=False)
    distrib_table = pd.DataFrame([my_bins, np.round(distrib_est, 4)], index=["Přihrádka", "Odhad $F(x)$"], columns=[""]*len(my_bins))
    
    return density_table, distrib_table

Survived

density_table, distrib_table = histogram(S, 9)

Odhad hustoty

Z histogramu odhadneme hustotu

$$ \hat{f}_{\text{survived}}(x) = \begin{cases} \text{příslušná hodnota v tabulce,} &\quad x \in \left[ 687, 780 \right] \\ 0 \text{,} &\quad x \notin \left[ 687, 780 \right] \\ \end{cases} $$
density_table
Přihrádka [687, 697) [697, 708) [708, 718) [718, 728) [728, 739) [739, 749) [749, 759) [759, 770) [770, 780]
Odhad $f(x)$ 0.0286 0.0286 0.0571 0.2 0.2 0.1714 0.1714 0.0857 0.0571

Odhad distribuční funkce

Z empirické distribuční funkce odhadneme distribuční funkci

$$ \hat{F}_{\text{survived}}(x) = \begin{cases} \text{0,} &\quad x \lt 687\\ \text{příslušná hodnota v tabulce,} &\quad x \in \left[ 687, 780 \right] \\ \text{1,} &\quad x \gt 780\\ \end{cases} $$
display(distrib_table.iloc[:, :9])
display(distrib_table.iloc[:, 9:18])
display(distrib_table.iloc[:, 18:])
Přihrádka [687, 703) [703, 709) [709, 715) [715, 721) [721, 723) [723, 726) [726, 728) [728, 729) [729, 730)
Odhad $F(x)$ 0.0286 0.0571 0.0857 0.1143 0.1429 0.2 0.2286 0.3143 0.3429
Přihrádka [730, 733) [733, 735) [735, 736) [736, 739) [739, 741) [741, 743) [743, 749) [749, 751) [751, 752)
Odhad $F(x)$ 0.4 0.4571 0.4857 0.5143 0.5429 0.6571 0.6857 0.7143 0.7429
Přihrádka [752, 755) [755, 756) [756, 766) [766, 767) [767, 769) [769, 770) [770, 780]
Odhad $F(x)$ 0.8 0.8286 0.8571 0.8857 0.9143 0.9429 0.9714

Perished

density_table, distrib_table = histogram(P, 6)

Odhad hustoty

Z histogramu odhadneme hustotu

$$ \hat{f}_{\text{perished}}(x) = \begin{cases} \text{příslušná hodnota v tabulce,} &\quad x \in \left[ 659, 765 \right] \\ 0 \text{,} &\quad x \notin \left[ 659, 765 \right] \\ \end{cases} $$
density_table
Přihrádka [659, 677) [677, 694) [694, 712) [712, 730) [730, 747) [747, 765]
Odhad $f(x)$ 0.0417 0.0417 0.125 0.25 0.375 0.1667

Odhad distribuční funkce

Z empirické distribuční funkce odhadneme distribuční funkci

$$ \hat{F}_{\text{perished}}(x) = \begin{cases} \text{0,} &\quad x \lt 659\\ \text{příslušná hodnota v tabulce,} &\quad x \in \left[ 659, 765 \right] \\ \text{1,} &\quad x \gt 765\\ \end{cases} $$
display(distrib_table.iloc[:, :7])
display(distrib_table.iloc[:, 7:14])
display(distrib_table.iloc[:, 14:21])
Přihrádka [659, 689) [689, 702) [702, 703) [703, 709) [709, 713) [713, 720) [720, 726)
Odhad $F(x)$ 0.0417 0.0833 0.125 0.1667 0.2083 0.25 0.3333
Přihrádka [726, 729) [729, 731) [731, 736) [736, 737) [737, 738) [738, 739) [739, 743)
Odhad $F(x)$ 0.4167 0.4583 0.5 0.5417 0.5833 0.6667 0.7083
Přihrádka [743, 744) [744, 745) [745, 752) [752, 754) [754, 765]
Odhad $F(x)$ 0.75 0.7917 0.8333 0.9167 0.9583

Statistický model

Pro každou skupinu zvlášť najdeme nejbližší rozdělení (odhadneme parametry normálního, exponenciálního a rovnoměrného rozdělení). Zaneseme příslušné hustoty s odhadnutými parametry do grafů histogramu a diskutujeme, které z rozdělení odpovídá pozorovaným datům nejlépe.

Odhad parametrů

Parametry exponenciálního a rovnoměrného rozdělení odhadneme metodou momentů.

Momenty odhadneme výběrovými momenty.

$$ \widehat{EX^k} = m_k = \frac{1}{n} \sum_{i=1}^{n} X_i^k $$
S_m1 = 1 / S_n * np.sum(S)
S_m2 = 1 / S_n * np.dot(S, S)

P_m1 = 1 / P_n * np.sum(P)
P_m2 = 1 / P_n * np.dot(P, P)

pd.DataFrame({
    "Survived": [S_m1, S_m2],
    "Perished": [P_m1, P_m2],
}, index=["Výběrový moment $m_1$", "Výběrový moment $m_2$"]).round(4)
Survived Perished
Výběrový moment $m_1$ 738 727.917
Výběrový moment $m_2$ 545026 530394

Dobrý (nestranný, konzistentní) odhad parametrů normálního rozdělení máme z předchozích úloh spočítané.

Normální rozdělení $\mathcal{N}(\mu, \sigma^2)$

$ f(x) = \frac{1}{\sqrt{2\pi \sigma^2}} e^{-\frac{\left(x - \mu\right)^2}{2 \sigma^2}} \quad \text{pro } x \in \mathbb{R} $

$$\hat{\mu} = \bar{X}_n$$$$\hat{\sigma}^2 = s_n^2$$

Parametry zbylých dvou statistických modelů vyjádříme jako funkce momentů.

Exponenciální rozdělení $\text{Exp}(\lambda, L)$

$ f(x) = \begin{cases} \lambda e^{-\lambda \left(x - L\right)} & \text{pro } x \geq 0 \\ 0 &\text{jinak} \end{cases} $

$$\hat{\lambda} = \frac{1}{\sqrt{m_2 - m_1^2}}$$$$\hat{L} = m_1 - \sqrt{m_2 - m_1^2}$$

Rovnoměrné rozdělení $\mathcal{U}(a, b)$

$ f(x) = \begin{cases} \frac{1}{b-a} &\quad \text{pro } a \leq x \leq b \\ 0 &\quad \text{jinak} \end{cases} $

$$\hat{a} = m_1 - \sqrt{3 \cdot \left( m_2 - m_1^2 \right)}$$$$\hat{b} = m_1 + \sqrt{3 \cdot \left( m_2 - m_1^2 \right)}$$
def add_est(data, m1, m2, bin_count, mean, std, title, ax):
    X = np.linspace(data.min()-10, data.max()+10, 1000)
    counts, bins = np.histogram(data, bins=bin_count)
    bins = bins.round()
    
    lamb_hat = 1 / np.sqrt(m2 - m1*m1)
    l_hat = m1 - np.sqrt(m2 - m1*m1)
    
    a_hat = m1 - np.sqrt(3 * (m2 - m1*m1))
    b_hat = m1 + np.sqrt(3 * (m2 - m1*m1))

    Y_norm = st.norm.pdf(X, loc=mean, scale=std)
    
    X_exp = np.linspace(l_hat, data.max()+10, 1000)
    Y_exp = st.expon.pdf(X_exp, loc=l_hat, scale=1/lamb_hat)
    
    sns.histplot(data, bins=bins, stat="density", ax=ax, color="grey", alpha=0.3)
    
    sns.lineplot(x=X, y=Y_norm, ax=ax, label=f"N ( {mean:.0f}, {std*std:.0f} )", linewidth=2.5, color="royalblue")
    
    sns.lineplot(x=X_exp, y=Y_exp, ax=ax, label=f"Exp ( {lamb_hat:.4f}, {l_hat:.0f} )", linewidth=2.5, color="darkorange")
    sns.lineplot(x=[X.min(), l_hat], y=0.0003, ax=ax, linewidth=2.5, color="darkorange")
    
    sns.lineplot(x=[a_hat, b_hat], y=1 / (b_hat - a_hat), ax=ax, label=f"U ( {a_hat:.0f}, {b_hat:.0f} )", linewidth=2.5, color="green")
    sns.lineplot(x=[X.min(), a_hat], y=0.0003, ax=ax, linewidth=2.5, color="green")
    sns.lineplot(x=[b_hat, X.max()], y=0.0003, ax=ax, linewidth=2.5, color="green")
    
    ax.legend(loc="upper right")
    ax.set_title(title)

fig, axes = plt.subplots(1, 2, figsize=(10, 5), layout="constrained", sharey=True)
add_est(S, S_m1, S_m2, 9, S_mean, S_std, "Survived", axes[0])
add_est(P, P_m1, P_m2, 6, P_mean, P_std, "Perished", axes[1])

Závěr

V obou skupinách vrabců je zřejmé, že pozorovaným datům nejlépe odpovídá normální rozdělení.


Simulace

Pro každou skupinu zvlášť vygenerujeme náhodný výběr o 100 hodnotách z rozdělení s parametry odhadnutými v předchozím bodě. Porovnáváme histogram simulovaných hodnot s pozorovanými daty.

def plot_norm(data, mean, std, bin_count):
    fig, axes = plt.subplots(1, 2, figsize=(10, 5), layout="constrained", sharey=True)

    counts, bins = np.histogram(data, bins=bin_count)
    bins = bins.round()
    w = bins[1] - bins[0]
    
    ax = axes[0]
    sns.histplot(data, bins=bins, stat="density", ax=ax)
    ax.set_title("Original data")

    X = np.linspace(data.min()-10, data.max()+10, 1000)
    while X.max() > bins.max():
        bins = np.append(bins, [bins.max() + w])
    while X.min() < bins.min():
        bins = np.insert(bins, 0, bins.min() - w)
    
    ax = axes[1]
    new_data = st.norm.rvs(mean, std, 100, random_state=42)
    sns.histplot(new_data, bins=bins, stat="density", ax=ax, color="tomato")
    ax.set_title("Simulated data")
    ax.set_xlabel("Humerus")

Survived

plot_norm(S, S_mean, S_std, 9)

Perished

plot_norm(P, P_mean, P_std, 6)

Závěr

Histogramy jsou v obou případech podobné.


Interval spolehlivosti

Nyní pro každou skupinu zvlášť spočítáme oboustranný 95% konfidenční interval pro střední hodnotu.

Oboustranný $100 \cdot (1 − \alpha)\%$ interval spolehlivosti pro $\mu$ při neznámém rozptylu počítáme jako

$$ \left( \bar{X}_n - t_{\alpha / 2, n-1} \frac{s_n}{\sqrt{n}}, \bar{X}_n + t_{\alpha / 2, n-1} \frac{s_n}{\sqrt{n}} \right) $$
def interval(data, n, mean, std, alpha):
    t_a_half = st.t.isf(alpha / 2, n - 1)
    delta = t_a_half * std / np.sqrt(n)
    return np.array([mean - delta, mean + delta])

S_ci = interval(S, S_n, S_mean, S_std, 0.05).round(4)
P_ci = interval(P, P_n, P_mean, P_std, 0.05).round(4)

pd.DataFrame({
    "Survived": [f"( {S_ci[0]} , {S_ci[1]} )"],
    "Perished": [f"( {P_ci[0]} , {P_ci[1]} )"],
}, index=["Oboustranný 95% konfidenční interval pro $\\mu$"]).T
Oboustranný 95% konfidenční interval pro $\mu$
Survived ( 731.185 , 744.815 )
Perished ( 717.9755 , 737.8578 )

Test střední hodnoty

Pro každou skupinu zvlášť otestujeme na hladině významnosti 5% hypotézu, zda je střední hodnota rovná hodnotě K (parametr úlohy), proti oboustranné alternativě.

pd.DataFrame([K], index=["K"], columns=[""])
K 16

Survived

Návrh testu

Na hladině významnosti $\alpha = 5\%$ testujeme hypotézu

$$ H_0 : \quad \mu = 16 $$$$ H_A : \quad \mu \ne 16 $$

Provedení testu

Příslušný interval spolehlivosti je

$$ (L, U) = (731.19 , 744.81)$$

Testovaná hodnota $\mu_0 = 16$ v intervalu neleží.

Závěr testu

Na hladině významnosti $5\%$ nulovou hypotézu zamítáme ve prospěch alternativy, že skutečná střední délka pažní kosti vrabců, kteří zimu přežili, není $0.16 \text{in} \approx 4 \text{mm}$.

Perished

Návrh testu

Na hladině významnosti $\alpha = 5\%$ testujeme hypotézu

$$ H_0 : \quad \mu = 16 $$$$ H_A : \quad \mu \ne 16 $$

Provedení testu

Příslušný interval spolehlivosti je

$$ (L, U) = (717.98 , 737.86)$$

Testovaná hodnota $\mu_0 = 16$ v intervalu neleží.

Závěr testu

Na hladině významnosti $5\%$ nulovou hypotézu zamítáme ve prospěch alternativy, že skutečná střední délka pažní kosti vrabců, kteří zimu nepřežili, není $0.16 \text{in} \approx 4 \text{mm}$.


Test rovnosti středních hodnot

Na hladině významnosti 5% otestujeme, jestli mají pozorované skupiny stejnou střední hodnotu.

Dvouvýběrový t-test

pd.DataFrame([np.round(S_std / P_std, 4)], index=["$\text{Podíl }s_{\text{survived}} / s_{\text{perished}}$"], columns=[""])
$\text{Podíl }s_{\text{survived}} / s_{\text{perished}}$ 0.8427

Můžeme předpokládat, že rozptyly obou skupin vrabců jsou stejné/podobné:

$$ \frac{s_1}{s_2} \approx 0.8427 $$$$ \frac{1}{2} \lt \frac{s_1}{s_2} \lt 2 $$

Pro porovnání použijeme dvouvýběrový t-test normálního rozdělení.

Test provedeme porovnáním testové statistiky $T$ s příslušnou kritickou hodnotou Studentova $t$-rozdělení:

$$ T = \frac{\bar{X}_1 - \bar{X}_2}{s_{12}} \cdot \sqrt{ \frac{n_1 n_2}{n_1 + n_2}} $$

kde

$$ s_{12} = \sqrt{ \frac{ \left( n_1 - 1 \right) s_1^2 + \left( n_2 - 1 \right) s_2^2 }{ n_1 + n_2 - 2 } } $$
SP_std = np.sqrt(( (S_n - 1) * S_var + (P_n - 1) * P_var )/( S_n + P_n - 2 ))
T_stat = (S_mean - P_mean) / SP_std * np.sqrt((S_n * P_n)/(S_n + P_n))
T_crit_05_57 = st.t.isf(0.05, 57)
T_crit_025_57 = st.t.isf(0.025, 57)

pd.DataFrame(np.array([T_stat, T_crit_05_57]).round(4),
             index=["$T$", "$t_{0.05, 57}$"], columns=["Hodnota"])
Hodnota
$T$ 1.777
$t_{0.05, 57}$ 1.672

Návrh testu

Na hladině významnosti $\alpha = 5\%$ testujeme hypotézu

$$ H_0 \colon \quad \mu_{\text{survived}} = \mu_{\text{perished}} $$$$ H_A \colon \quad \mu_{\text{survived}} > \mu_{\text{perished}} $$

Provedení testu

Příslušná testová statistika vychází $T = 1.777$.

Protože

$$ 1.777 = T \gt t_{\alpha, n_1 + n_2 - 2} = t_{0.05, 57} = 1.672 $$

hypotézu shody na hladině významnosti $5\%$ zamítáme.


Závěr

Na základě našich dat lze prokázat významný rozdíl ve střední délce pažní kosti mezi vrabci, kteří zimu přežili, a vrabci, kteří v zimě zahynuli. Ti, kteří zimní bouři přežili, měli v průměru delší pažní kost.


Alternativní závěr

pd.DataFrame(np.array([T_stat, T_crit_025_57]).round(4),
             index=["$T$", "$t_{0.025, 57}$"], columns=["Hodnota"])
Hodnota
$T$ 1.777
$t_{0.025, 57}$ 2.0025

Kdybychom zkoumali pouze shodnost/nerovnost středních hodnot

$$ H_0 \colon \quad \mu_{\text{survived}} = \mu_{\text{perished}} $$$$ H_A \colon \quad \mu_{\text{survived}} > \mu_{\text{perished}} $$$$ 1.777 = |T| \lt t_{\alpha / 2, n_1 + n_2 − 2} = t_{0.025, 57} = 2.0025 $$

vyšla by $p$-hodnota pro oboustranný test větší než $5\%$, v takovém případě by $H_A$ bylo statisticky nevýznamné.

p_greater = st.mstats.ttest_ind(S, P, equal_var=True, alternative="greater")
p_two_sided = st.mstats.ttest_ind(S, P, equal_var=True, alternative="two-sided")
pd.DataFrame(np.array([p_greater, p_two_sided]).round(4),
             index=["Jednostranný test", "Oboustranný test"], columns=["T statistika", "p-hodnota"])
T statistika p-hodnota
Jednostranný test 1.777 0.0405
Oboustranný test 1.777 0.0809