{ "cells": [ { "cell_type": "markdown", "id": "34e797dc", "metadata": {}, "source": [ "# Przedziały ufności: konstrukcja i interpretacja\n", "\n", "W tym notatniku skupiamy się na **przedziałach ufności (PU)** oraz na tym, skąd one się biorą (logika „powtarzalnych prób”).\n", "\n", "Ważne: przedział ufności nie jest zdaniem o tym, że „z prawdopodobieństwem 95% prawdziwe $\\mu$ leży w środku”. To jest zdanie o **procedurze**, która w długim szeregu powtórzeń (dla ustalonego poziomu ufności, np. 95%) w ok. 95% prób obejmie prawdziwy parametr.\n" ] }, { "cell_type": "markdown", "id": "6f2bb192", "metadata": {}, "source": [ "## Cele\n", "\n", "- Umieć **zbudować** 95% przedział ufności wokół średniej: krok po kroku.\n", "- Rozumieć **interpretację częstotliwościową**: *pokrycie* w powtarzalnych próbach.\n", "- Zobaczyć, dlaczego przy nieznanym $\\sigma$ pojawia się **rozkład t**.\n", "- Umieć powiązać PU z testem: w teście dwustronnym $\\alpha=0.05$ wartość $\\mu_0$ jest „zgodna z danymi”, jeśli leży w 95% PU.\n" ] }, { "cell_type": "markdown", "id": "48b36323", "metadata": {}, "source": [ "## Wymagania wstępne\n", "\n", "- Intuicja CTG (Centralne Twierdzenie Graniczne) i pojęcie błędu standardowego.\n", "- Podstawy rozkładu normalnego.\n", "- Podstawy `numpy` i prostych wykresów w `matplotlib`.\n", "- (Opcjonalnie) Uruchamianie widżetów w notatniku Jupyter (pakiet `ipywidgets`).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "98c45656", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:35.690510Z", "iopub.status.busy": "2026-01-17T02:07:35.690358Z", "iopub.status.idle": "2026-01-17T02:07:36.723329Z", "shell.execute_reply": "2026-01-17T02:07:36.722483Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "from scipy import stats\n", "\n", "from IPython.display import display\n", "\n", "SEED = 20250116\n", "rng = np.random.default_rng(SEED)\n", "\n", "sns.set_theme(style=\"whitegrid\")\n", "\n", "try:\n", " import ipywidgets as widgets\n", "\n", " WIDGETY_DOSTEPNE = True\n", "except ImportError:\n", " widgets = None\n", " WIDGETY_DOSTEPNE = False\n" ] }, { "cell_type": "markdown", "id": "c27eb8f6", "metadata": {}, "source": [ "## Centralne Twierdzenie Graniczne - przypomnienie\n", "\n", "Proste symulacje pokażą nam to, co teoretycznie wiemy z Centralnego Twierdzenia Granicznego:\n", "\n", "- że średnie z prób losowych oscylują wokół średniej w populacji,\n", "- a w szczególności, że niezależnie od rozkładu zmiennej w populacji średnie z prób losowych rozkładają się normalnie wokół tej średniej,\n", "- że ich rozproszenie jest tym mniejsze, im mniejsze jest rozproszenie zmiennej w populacji,\n", "- i tym mniejsze, im większa jest liczebność prób,\n", "- a dokładniej, że odchylenie standardowe średnich z prób losowych równe jest odchyleniu standardowemu w populacji podzielonemu przez pierwiastek liczebności prób.\n", "\n", "Taki rozkład teoretyczny $N(\\mu, \\sigma^2/N)$, który nazywamy **rozkładem średniej z próby**, jest rozkładem prawdopodobieństwa, który pozwala nam szacować, jak prawdopodobne jest wylosowanie z danej populacji próby o takiej czy innej średniej.\n", "\n", "Odchylenie standardowe tego rozkładu nazywa się **błędem standardowym średniej**.\n" ] }, { "cell_type": "markdown", "id": "f4df6b53", "metadata": {}, "source": [ "### Przykład z kognitywistyki: czasy reakcji w zadaniu 2AFC\n", "\n", "W wielu badaniach kognitywistycznych mierzymy **czas reakcji** — czas od pojawienia się bodźca do odpowiedzi.\n", "\n", "Jednym z najprostszych paradygmatów jest zadanie **wymuszonego wyboru z dwóch opcji** (skrót: 2AFC): w każdej próbie osoba badana musi wybrać jedną z dwóch odpowiedzi, np. „bodziec był po lewej” albo „bodziec był po prawej”.\n", "\n", "Czasy reakcji w pojedynczych próbach często mają rozkład **prawoskośny** (rzadkie, ale bardzo długie reakcje tworzą „ogon” po prawej). To nie przeszkadza w tym, żeby **średnia z próby** miała rozkład zbliżony do normalnego dla umiarkowanych liczebności `n` — i to jest praktyczna treść CTG.\n", "\n", "Poniżej tworzymy sztuczną „populację” czasów reakcji (w ms) i porównujemy rozkład pojedynczych czasów z rozkładem średnich z prób o różnej liczebności." ] }, { "cell_type": "code", "execution_count": null, "id": "328acbcd", "metadata": {}, "outputs": [], "source": [ "rng = np.random.default_rng(SEED + 10)\n", "\n", "mu_ms = 520\n", "sd_ms = 80\n", "\n", "# Zabawka dydaktyczna: prawoskośny rozkład \"podobny\" do czasów reakcji.\n", "raw = rng.lognormal(mean=0.0, sigma=0.35, size=200_000)\n", "rt_populacja = (raw - raw.mean()) / raw.std() * sd_ms + mu_ms\n", "\n", "repeats = 5_000\n", "sample_sizes = [5, 20, 100]\n", "\n", "sample_means = {}\n", "for n in sample_sizes:\n", " samples = rng.choice(rt_populacja, size=(repeats, n), replace=True)\n", " sample_means[n] = samples.mean(axis=1)\n", "\n", "fig, axes = plt.subplots(1, 1 + len(sample_sizes), figsize=(15, 4))\n", "\n", "sns.histplot(rt_populacja, bins=60, ax=axes[0], color='lightgrey')\n", "axes[0].set_title('Pojedyncze czasy reakcji (\"populacja\")')\n", "axes[0].set_xlabel('czas reakcji (ms)')\n", "axes[0].set_ylabel('liczność')\n", "\n", "for i, n in enumerate(sample_sizes, start=1):\n", " sns.histplot(sample_means[n], bins=60, ax=axes[i], color='steelblue')\n", " axes[i].set_title(f'Średnia z próby (n={n})')\n", " axes[i].set_xlabel('średnia (ms)')\n", " axes[i].set_ylabel('liczność')\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "0ea149dc", "metadata": {}, "source": [ "#### Interaktywnie (opcjonalnie)\n", "\n", "Widżet poniżej pozwala zmieniać liczebność próby `n` i porównać dwa przypadki:\n", "\n", "- rozkład prawoskośny (model „czasów reakcji”),\n", "- rozkład normalny (dla kontrastu).\n", "\n", "Wykres po lewej pokazuje pojedyncze obserwacje, a po prawej — rozkład średniej z próby." ] }, { "cell_type": "code", "execution_count": null, "id": "44159ef5", "metadata": {}, "outputs": [], "source": [ "if not WIDGETY_DOSTEPNE:\n", " print('Widżety nie są dostępne: zainstaluj pakiet ipywidgets, aby korzystać z części interaktywnych.')\n", "else:\n", " MU_MS = 520.0\n", " SD_MS = 80.0\n", "\n", " rng_pop = np.random.default_rng(SEED + 100)\n", " raw = rng_pop.lognormal(mean=0.0, sigma=0.35, size=200_000)\n", " POP_RT = (raw - raw.mean()) / raw.std() * SD_MS + MU_MS\n", " POP_NORMAL = rng_pop.normal(loc=MU_MS, scale=SD_MS, size=200_000)\n", "\n", " rozklad_dropdown = widgets.Dropdown(\n", " options=[\n", " ('Prawoskośny (model czasów reakcji)', 'rt'),\n", " ('Normalny (dla porównania)', 'normalny'),\n", " ],\n", " value='rt',\n", " description='Rozkład',\n", " )\n", "\n", " n_slider = widgets.IntSlider(\n", " value=20,\n", " min=2,\n", " max=200,\n", " step=1,\n", " description='n',\n", " continuous_update=False,\n", " )\n", "\n", " repeats_slider = widgets.IntSlider(\n", " value=3_000,\n", " min=500,\n", " max=10_000,\n", " step=500,\n", " description='powtórzenia',\n", " continuous_update=False,\n", " )\n", "\n", " def pokaz_ctg(rozklad: str, n: int, repeats: int) -> None:\n", " rng = np.random.default_rng(SEED + 101)\n", " n = int(n)\n", " repeats = int(repeats)\n", "\n", " populacja = POP_RT if rozklad == 'rt' else POP_NORMAL\n", "\n", " # Losujemy powtarzane próby i liczymy średnią w każdej z nich.\n", " samples = rng.choice(populacja, size=(repeats, n), replace=True)\n", " means = samples.mean(axis=1)\n", "\n", " fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n", "\n", " podprobka = populacja[:50_000]\n", " sns.histplot(podprobka, bins=60, ax=axes[0], color='lightgrey')\n", " axes[0].set_title('Pojedyncze obserwacje')\n", " axes[0].set_xlabel('czas reakcji (ms)')\n", " axes[0].set_ylabel('liczność')\n", "\n", " sns.histplot(means, bins=60, ax=axes[1], color='steelblue')\n", " axes[1].set_title(f'Średnia z próby (n={n})')\n", " axes[1].set_xlabel('średnia (ms)')\n", " axes[1].set_ylabel('liczność')\n", "\n", " plt.tight_layout()\n", " plt.show()\n", "\n", " out = widgets.interactive_output(\n", " pokaz_ctg,\n", " {\n", " 'rozklad': rozklad_dropdown,\n", " 'n': n_slider,\n", " 'repeats': repeats_slider,\n", " },\n", " )\n", "\n", " display(widgets.VBox([rozklad_dropdown, n_slider, repeats_slider, out]))\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9355d284", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:36.725041Z", "iopub.status.busy": "2026-01-17T02:07:36.724840Z", "iopub.status.idle": "2026-01-17T02:07:37.530787Z", "shell.execute_reply": "2026-01-17T02:07:37.530080Z" } }, "outputs": [], "source": [ "x = np.linspace(-4, 4, 400)\n", "y = stats.norm.pdf(x)\n", "\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(x, y, color=\"black\")\n", "plt.axvline(0, color=\"grey\", linestyle=\":\", linewidth=2)\n", "\n", "plt.title(\"Rozkład średniej z próby (schemat)\")\n", "plt.xlabel(r\"$\\bar{X}$\")\n", "plt.ylabel(\"gęstość\")\n", "\n", "plt.text(0.15, 0.02, r\"$\\mu$\", fontsize=14)\n", "plt.text(1.9, 0.20, r\"$\\sigma_{\\bar{X}} = \\sigma/\\sqrt{N}$\", fontsize=14)\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "95911d7b", "metadata": {}, "source": [ "**Pytanie**\n", "\n", "Oczywiście zależy nam na tym, żeby błąd standardowy był jak najmniejszy: dlatego dążymy do dużego $N$.\n", "\n", "Z drugiej strony ten zysk na precyzji jest szczególnie duży przy małych wartościach $N$; im większe $N$, tym mniejsza redukcja błędu standardowego.\n", "\n", "Dlaczego? Która z poniższych funkcji oddaje zależność między $N$ a błędem standardowym średniej?\n", "\n", "**Odpowiedź**\n", "\n", "Dlatego że w mianowniku jest pierwiastek. Wykres numer 3.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6026e3ef", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:37.532349Z", "iopub.status.busy": "2026-01-17T02:07:37.532230Z", "iopub.status.idle": "2026-01-17T02:07:37.917665Z", "shell.execute_reply": "2026-01-17T02:07:37.917061Z" } }, "outputs": [], "source": [ "x = np.linspace(1, 50, 300)\n", "\n", "fig, axes = plt.subplots(2, 2, figsize=(10, 8))\n", "axes = axes.ravel()\n", "\n", "axes[0].plot(x, 1 / x**2)\n", "axes[0].set_title(r\"Wykres 1: $y = 1/x^2$\")\n", "\n", "axes[1].plot(x, np.sqrt(x))\n", "axes[1].set_title(r\"Wykres 2: $y = \\sqrt{x}$\")\n", "\n", "axes[2].plot(x, 1 / np.sqrt(x))\n", "axes[2].set_title(r\"Wykres 3: $y = 1/\\sqrt{x}$\")\n", "\n", "axes[3].plot(x, x**2)\n", "axes[3].set_title(r\"Wykres 4: $y = x^2$\")\n", "\n", "for ax in axes:\n", " ax.set_xlabel(\"N\")\n", " ax.set_ylabel(\"wartość\")\n", "\n", "fig.suptitle(\"Jak zmienia się błąd standardowy wraz z N? (intuicja)\")\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "98f5b0a4", "metadata": {}, "source": [ "Potrafiąc skonstruować rozkład średniej z próby, możemy testować prostą hipotezę zerową: **dana próba losowa pochodzi z populacji o takiej i takiej średniej**.\n", "\n", "Jeśli różnica między średnią naszej próby a założoną zgodnie z hipotezą zerową średnią w populacji jest zbyt duża (jeśli nasza średnia wpada w obszar krytyczny), odrzucamy hipotezę zerową.\n", "\n", "Poniżej schematycznie pokazujemy obszar krytyczny dla testu **dwustronnego** przy $\\alpha=0.05$ (po 2.5% w każdym ogonie).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7524da43", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:37.918967Z", "iopub.status.busy": "2026-01-17T02:07:37.918848Z", "iopub.status.idle": "2026-01-17T02:07:38.058609Z", "shell.execute_reply": "2026-01-17T02:07:38.058156Z" } }, "outputs": [], "source": [ "alpha = 0.05\n", "z_low = stats.norm.ppf(alpha / 2)\n", "z_high = stats.norm.ppf(1 - alpha / 2)\n", "\n", "x = np.linspace(-4, 4, 400)\n", "y = stats.norm.pdf(x)\n", "\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(x, y, color=\"black\")\n", "plt.axvline(0, color=\"grey\", linestyle=\":\", linewidth=2)\n", "plt.axvline(z_low, color=\"grey\", linestyle=\"--\", linewidth=2)\n", "plt.axvline(z_high, color=\"grey\", linestyle=\"--\", linewidth=2)\n", "\n", "plt.title(\"Standaryzowany rozkład (Z) i obszar krytyczny dla α = 0.05 (dwustronnie)\")\n", "plt.xlabel(\"Z\")\n", "plt.ylabel(\"gęstość\")\n", "\n", "plt.text(z_low - 0.2, 0.02, f\"{z_low:.2f}\", ha=\"right\")\n", "plt.text(z_high + 0.2, 0.02, f\"{z_high:.2f}\", ha=\"left\")\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "3f06625a", "metadata": {}, "source": [ "Wygodniej może być wystandaryzować rozkład średniej z próby, czyli doprowadzić go do takiej postaci, w której średnia wynosi 0, a odchylenie standardowe --- 1.\n", "\n", "Taki rozkład „działa” zawsze tak samo, niezależnie od oryginalnej skali naszej zmiennej.\n", "\n", "Standaryzacja (gdy znamy $\\sigma$) prowadzi do statystyki $Z$:\n", "\n", "$$\n", "Z = \\frac{\\bar{X} - \\mu}{\\sigma/\\sqrt{N}}.\n", "$$\n" ] }, { "cell_type": "markdown", "id": "ca2ef753", "metadata": {}, "source": [ "**Pytanie**\n", "\n", "Zakładając, że średnia naszej próby to `srednia`, jej liczebność to `N`, odchylenie standardowe w populacji to `sigma`, a przyjęta zgodnie z hipotezą zerową średnia w populacji to `mu0`, jakiego wyrażenia możemy użyć, żeby sprawdzić, czy nasza średnia wpada w pięcioprocentowy obszar krytyczny?\n", "\n", "- `srednia < stats.norm.ppf(0.025, loc=mu0, scale=sigma / np.sqrt(N)) or stats.norm.ppf(0.975, loc=mu0, scale=sigma / np.sqrt(N)) < srednia`\n", "- `srednia < mu0 - stats.norm.ppf(0.975) * sigma / np.sqrt(N) or mu0 + stats.norm.ppf(0.975) * sigma / np.sqrt(N) < srednia`\n", "- `srednia < mu0 + stats.norm.ppf(0.025) * sigma / np.sqrt(N) or mu0 - stats.norm.ppf(0.025) * sigma / np.sqrt(N) < srednia`\n", "- `srednia < mu0 - stats.norm.ppf(0.025) * sigma / np.sqrt(N) or mu0 + stats.norm.ppf(0.975) * sigma / np.sqrt(N) < srednia`\n", "\n", "**Odpowiedź**\n", "\n", "Wszystkie odpowiedzi są prawidłowe, oprócz ostatniej.\n", "\n", "W pierwszej po prostu ustawiamy parametry rozkładu. W drugiej i trzeciej korzystamy z faktu, że możemy przekształcić dowolny rozkład normalny w standardowy rozkład normalny i odwrotnie.\n", "\n", "Trzecia jest w zasadzie identyczna z drugą, tylko odwracamy znak. W czwartej otrzymujemy dwie takie same wartości – alternatywa jest więc bez sensu.\n" ] }, { "cell_type": "markdown", "id": "bb52936d", "metadata": {}, "source": [ "## Przedziały ufności\n", "\n", "Alternatywą dla testowania hipotez (albo ich dopełnieniem; zależy, jak na to patrzeć) jest konstruowanie przedziałów ufności wokół statystyki z próby.\n", "\n", "Podręczniki i kursy, a w konsekwencji badacze w praktyce, skupiają się na tym pierwszym, a niesłusznie, bowiem **przedziały ufności dają nam więcej informacji**.\n", "\n", "Testowanie hipotezy zerowej pomaga jedynie odpowiedzieć na pytanie o jakąś pojedynczą hipotetyczną wartość średniej w populacji.\n", "\n", "Skonstruowanie przedziału ufności pozwala określić cały zakres wartości średniej w populacji, który jest „zgodny z danymi” przy zadanym poziomie ufności.\n" ] }, { "cell_type": "markdown", "id": "48091066", "metadata": {}, "source": [ "Poniżej znajduje się schematyczny rysunek, na którym przedstawione mamy rozkłady średniej z próby dla różnych hipotez zerowych.\n", "\n", "Nie odrzucamy hipotezy zerowej dla wszystkich tych hipotez, dla których średnia populacji mieści się między dolną i górną granicą przedziału (oznaczmy je symbolicznie $\\mu_d$ i $\\mu_g$).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "3faf60d1", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.060219Z", "iopub.status.busy": "2026-01-17T02:07:38.060111Z", "iopub.status.idle": "2026-01-17T02:07:38.232258Z", "shell.execute_reply": "2026-01-17T02:07:38.231644Z" } }, "outputs": [], "source": [ "alpha = 0.05\n", "z_low = stats.norm.ppf(alpha / 2)\n", "z_high = stats.norm.ppf(1 - alpha / 2)\n", "\n", "x = np.linspace(-4, 4, 400)\n", "\n", "plt.figure(figsize=(10, 6))\n", "\n", "means = [z_low, z_high, stats.norm.ppf(0.25), stats.norm.ppf(0.40), stats.norm.ppf(0.75), stats.norm.ppf(0.90)]\n", "colors = [\"black\", \"black\", \"grey\", \"grey\", \"grey\", \"grey\"]\n", "\n", "for mean, color in zip(means, colors, strict=True):\n", " y = stats.norm.pdf(x, loc=mean, scale=1.0)\n", " plt.plot(x, y, color=color, linewidth=2 if color == \"black\" else 1)\n", "\n", "plt.axvline(z_low, color=\"grey\", linestyle=\"--\", linewidth=2)\n", "plt.axvline(z_high, color=\"grey\", linestyle=\"--\", linewidth=2)\n", "\n", "plt.title(\"Przedział ufności jako zbiór hipotez H0, których nie odrzucamy (schemat)\")\n", "plt.xlabel(r\"$\\bar{X}$ (dla uproszczenia: skala standaryzowana)\")\n", "plt.ylabel(\"gęstość\")\n", "\n", "plt.text(z_low, 0.01, r\"$\\mu_d$\", ha=\"center\")\n", "plt.text(z_high, 0.01, r\"$\\mu_g$\", ha=\"center\")\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "92487568", "metadata": {}, "source": [ "### Widżet: przedział ufności a test $t$ (dwustronny)\n", "\n", "Dla testu dwustronnego zachodzi prosta zależność: wartość $\\mu_0$ jest „zgodna z danymi” przy $\\alpha=0.05$ wtedy i tylko wtedy, gdy leży w 95% PU (przy tych samych założeniach).\n", "\n", "Poniżej przyjmujemy dane podsumowane z fikcyjnego badania czasów reakcji w zadaniu 2AFC i zmieniamy $\\mu_0$." ] }, { "cell_type": "code", "execution_count": null, "id": "a3b71b3b", "metadata": {}, "outputs": [], "source": [ "if not WIDGETY_DOSTEPNE:\n", " print('Widżety nie są dostępne: zainstaluj pakiet ipywidgets, aby korzystać z części interaktywnych.')\n", "else:\n", " n = 30\n", " sample_mean = 540.0\n", " sample_sd = 80.0\n", "\n", " mu0_slider = widgets.FloatSlider(\n", " value=520.0,\n", " min=450.0,\n", " max=650.0,\n", " step=1.0,\n", " description='μ0 (ms)',\n", " readout_format='.0f',\n", " continuous_update=False,\n", " )\n", "\n", " conf_slider = widgets.FloatSlider(\n", " value=0.95,\n", " min=0.80,\n", " max=0.99,\n", " step=0.01,\n", " description='poziom ufności',\n", " readout_format='.2f',\n", " continuous_update=False,\n", " )\n", "\n", " def pokaz_powiazanie(mu0: float, confidence_level: float) -> None:\n", " mu0 = float(mu0)\n", " confidence_level = float(confidence_level)\n", "\n", " df = n - 1\n", " se = sample_sd / np.sqrt(n)\n", "\n", " alpha = 1 - confidence_level\n", " t_crit = stats.t.ppf(1 - alpha / 2, df=df)\n", "\n", " ci_low = sample_mean - t_crit * se\n", " ci_high = sample_mean + t_crit * se\n", "\n", " t_stat = (sample_mean - mu0) / se\n", " p_two_sided = 2 * stats.t.sf(abs(t_stat), df=df)\n", "\n", " in_ci = (ci_low <= mu0 <= ci_high)\n", " zgodnosc = 'TAK' if in_ci else 'NIE'\n", "\n", " print(f'n = {n}, df = {df}')\n", " print(f'Średnia w próbie = {sample_mean:.1f} ms, sd w próbie = {sample_sd:.1f} ms')\n", " print(f'Poziom ufności = {confidence_level:.2f} (α = {alpha:.2f})')\n", " print(f'{confidence_level:.0%} PU dla średniej: [{ci_low:.1f}, {ci_high:.1f}] ms')\n", " print()\n", " print(f'H0: μ = {mu0:.0f} ms')\n", " print(f't = {t_stat:.3f}, p (dwustronnie) = {float(p_two_sided):.4f}')\n", " print(f'Czy μ0 leży w PU? {zgodnosc}')\n", "\n", " plt.figure(figsize=(9, 2.0))\n", " plt.plot([ci_low, ci_high], [0, 0], color='steelblue', linewidth=6, label='PU')\n", " kolor = 'seagreen' if in_ci else 'crimson'\n", " plt.scatter([mu0], [0], color=kolor, s=120, zorder=3, label='μ0')\n", " plt.axvline(sample_mean, color='black', linestyle=':', linewidth=2, label='średnia z próby')\n", " plt.yticks([])\n", " plt.xlabel('czas reakcji (ms)')\n", " plt.title('PU a test t (dwustronny)')\n", " plt.legend(loc='upper right')\n", " plt.tight_layout()\n", " plt.show()\n", "\n", " out = widgets.interactive_output(\n", " pokaz_powiazanie,\n", " {\n", " 'mu0': mu0_slider,\n", " 'confidence_level': conf_slider,\n", " },\n", " )\n", "\n", " display(widgets.VBox([mu0_slider, conf_slider, out]))\n" ] }, { "cell_type": "markdown", "id": "ff3d1d83", "metadata": {}, "source": [ "W pewnym sensie konstruowanie przedziału ufności to odwrotność testowania hipotezy zerowej: określamy przedział takich możliwych wartości średniej w populacji, dla których nie odrzucilibyśmy hipotezy zerowej.\n", "\n", "95-procentowy przedział ufności to przedział, który w 95% przypadków obejmie prawdziwą średnią w populacji — w sensie **pokrycia** procedury w powtarzalnych próbach.\n", "\n", "**Przykład (kognitywistyka)**: wyobraźmy sobie bardzo proste zadanie 2AFC (wymuszony wybór z dwóch opcji), np. „lewo/prawo”. Mierzymy **czas reakcji** (w milisekundach). Poniższy kod losuje `k` prób z zadanej „populacji” czasów reakcji i dla każdej z nich rysuje odcinek odpowiadający 95-procentowemu przedziałowi ufności wokół średniej.\n", "\n", "Na razie zakładamy (dla uproszczenia), że znamy odchylenie standardowe w populacji $\\sigma$ — do tego uproszczenia wrócimy za chwilę.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "12993819", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.233565Z", "iopub.status.busy": "2026-01-17T02:07:38.233450Z", "iopub.status.idle": "2026-01-17T02:07:38.364913Z", "shell.execute_reply": "2026-01-17T02:07:38.364296Z" } }, "outputs": [], "source": [ "rng = np.random.default_rng(SEED + 1)\n", "\n", "mu = 520\n", "sigma = 80\n", "sample_size = 20\n", "k = 50\n", "\n", "confidence_level = 0.95\n", "z_crit = stats.norm.ppf((1 + confidence_level) / 2)\n", "half_width = z_crit * sigma / np.sqrt(sample_size)\n", "\n", "plt.figure(figsize=(10, 7))\n", "plt.title(\"Przedziały ufności wokół średniej z próby (σ znane — uproszczenie)\")\n", "plt.xlabel(\"czas reakcji (ms)\")\n", "plt.ylabel(\"numer próby\")\n", "\n", "plt.axvline(mu, color=\"black\", linestyle=\":\", linewidth=2)\n", "\n", "for i in range(k):\n", " sample = rng.normal(loc=mu, scale=sigma, size=sample_size)\n", " sample_mean = float(sample.mean())\n", "\n", " lower = sample_mean - half_width\n", " upper = sample_mean + half_width\n", "\n", " plt.plot([lower, upper], [i + 1, i + 1], color=\"steelblue\", linewidth=4)\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "2f0ad924", "metadata": {}, "source": [ "### Widżet: pokrycie w praktyce\n", "\n", "Poniżej symulujemy `k` powtórzeń tego samego eksperymentu (np. `k` niezależnych replikacji badania). Każda pozioma kreska to PU wyznaczony z jednej próby.\n", "\n", "Możesz porównać trzy procedury:\n", "\n", "- **Z i znane $\\sigma$** (uproszczenie, punkt odniesienia),\n", "- **Z i $s$ z próby** (często zbyt wąski PU dla małych prób),\n", "- **$t$ i $s$ z próby** (standardowy PU dla średniej).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "fdded867", "metadata": {}, "outputs": [], "source": [ "if not WIDGETY_DOSTEPNE:\n", " print('Widżety nie są dostępne: zainstaluj pakiet ipywidgets, aby korzystać z części interaktywnych.')\n", "else:\n", " mu = 520.0\n", "\n", " metoda_dropdown = widgets.Dropdown(\n", " options=[\n", " ('Z i znane σ (uproszczenie)', 'z_sigma'),\n", " ('Z i s z próby (zwykle zbyt wąski)', 'z_s'),\n", " ('t i s z próby (standard)', 't_s'),\n", " ],\n", " value='t_s',\n", " description='Metoda',\n", " )\n", "\n", " n_slider = widgets.IntSlider(\n", " value=20,\n", " min=5,\n", " max=200,\n", " step=1,\n", " description='n',\n", " continuous_update=False,\n", " )\n", "\n", " sigma_slider = widgets.FloatSlider(\n", " value=80.0,\n", " min=20.0,\n", " max=200.0,\n", " step=5.0,\n", " description='σ (ms)',\n", " readout_format='.0f',\n", " continuous_update=False,\n", " )\n", "\n", " conf_slider = widgets.FloatSlider(\n", " value=0.95,\n", " min=0.80,\n", " max=0.99,\n", " step=0.01,\n", " description='poziom ufności',\n", " readout_format='.2f',\n", " continuous_update=False,\n", " )\n", "\n", " k_slider = widgets.IntSlider(\n", " value=60,\n", " min=10,\n", " max=120,\n", " step=1,\n", " description='k',\n", " continuous_update=False,\n", " )\n", "\n", " def pokaz_pokrycie(n: int, sigma: float, confidence_level: float, k: int, metoda: str) -> None:\n", " n = int(n)\n", " sigma = float(sigma)\n", " confidence_level = float(confidence_level)\n", " k = int(k)\n", "\n", " rng = np.random.default_rng(SEED + 20)\n", " samples = rng.normal(loc=mu, scale=sigma, size=(k, n))\n", "\n", " sample_means = samples.mean(axis=1)\n", " sample_sds = samples.std(axis=1, ddof=1)\n", "\n", " alpha = 1 - confidence_level\n", "\n", " if metoda == 'z_sigma':\n", " crit = stats.norm.ppf(1 - alpha / 2)\n", " half_width = crit * sigma / np.sqrt(n)\n", " lower = sample_means - half_width\n", " upper = sample_means + half_width\n", " elif metoda == 'z_s':\n", " crit = stats.norm.ppf(1 - alpha / 2)\n", " half_widths = crit * sample_sds / np.sqrt(n)\n", " lower = sample_means - half_widths\n", " upper = sample_means + half_widths\n", " else:\n", " crit = stats.t.ppf(1 - alpha / 2, df=n - 1)\n", " half_widths = crit * sample_sds / np.sqrt(n)\n", " lower = sample_means - half_widths\n", " upper = sample_means + half_widths\n", "\n", " covered = (lower <= mu) & (mu <= upper)\n", " pokrycie = float(covered.mean())\n", " srednia_szerokosc = float((upper - lower).mean())\n", "\n", " plt.figure(figsize=(10, 7))\n", " plt.axvline(mu, color='black', linestyle=':', linewidth=2)\n", "\n", " for i in range(k):\n", " color = 'seagreen' if covered[i] else 'crimson'\n", " plt.plot([lower[i], upper[i]], [i + 1, i + 1], color=color, linewidth=3, alpha=0.85)\n", "\n", " plt.title(\n", " f'Pokrycie ≈ {pokrycie:.3f} (poziom ufności {confidence_level:.2f}); '\n", " f'średnia szerokość ≈ {srednia_szerokosc:.1f} ms'\n", " )\n", " plt.xlabel('czas reakcji (ms)')\n", " plt.ylabel('numer próby')\n", " plt.tight_layout()\n", " plt.show()\n", "\n", " out = widgets.interactive_output(\n", " pokaz_pokrycie,\n", " {\n", " 'n': n_slider,\n", " 'sigma': sigma_slider,\n", " 'confidence_level': conf_slider,\n", " 'k': k_slider,\n", " 'metoda': metoda_dropdown,\n", " },\n", " )\n", "\n", " display(widgets.VBox([metoda_dropdown, n_slider, sigma_slider, conf_slider, k_slider, out]))\n" ] }, { "cell_type": "markdown", "id": "b1123bf4", "metadata": {}, "source": [ "Oto uproszczona wersja powyższej symulacji: zamiast wykresu zwraca pojedynczą liczbę.\n", "\n", "Każda iteracja zwraca `True` albo `False`, w zależności od tego, czy przedział ufności objął średnią w populacji, czy nie.\n", "\n", "W efekcie uzyskujemy wektor wielu takich wartości logicznych, a średnia z nich to proporcja „trafień” (pokrycie).\n", "\n", "**Pytanie**\n", "\n", "Wokół jakiej wartości oscylują liczby zwracane przez ten kod?\n", "\n", "**Odpowiedź**\n", "\n", "Wokół wartości $0.95$.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "c09f17a3", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.366507Z", "iopub.status.busy": "2026-01-17T02:07:38.366347Z", "iopub.status.idle": "2026-01-17T02:07:38.407046Z", "shell.execute_reply": "2026-01-17T02:07:38.406263Z" } }, "outputs": [], "source": [ "rng = np.random.default_rng(SEED + 2)\n", "\n", "mu = 520\n", "sigma = 80\n", "sample_size = 20\n", "\n", "confidence_level = 0.95\n", "z_crit = stats.norm.ppf((1 + confidence_level) / 2)\n", "half_width = z_crit * sigma / np.sqrt(sample_size)\n", "\n", "repeats = 100_000\n", "samples = rng.normal(loc=mu, scale=sigma, size=(repeats, sample_size))\n", "\n", "sample_means = samples.mean(axis=1)\n", "lower_bounds = sample_means - half_width\n", "upper_bounds = sample_means + half_width\n", "\n", "coverage = np.mean((lower_bounds <= mu) & (mu <= upper_bounds))\n", "print(\"Pokrycie (symulacja):\", float(coverage))\n" ] }, { "cell_type": "markdown", "id": "98e08290", "metadata": {}, "source": [ "**Pytanie**\n", "\n", "Jak wpływa na poniższe symulacje manipulacja wartościami $N$ i $\\sigma$?\n", "\n", "- Im większe $N$, tym węższy przedział ufności.\n", "- Im większa $\\sigma$, tym szerszy przedział ufności. (dłuższy odcinek na osi)\n", "- Im większe $N$, tym częściej przedział ufności obejmuje średnią w populacji.\n", "- Im większa $\\sigma$, tym częściej przedział ufności obejmuje średnią w populacji.\n", "\n", "**Odpowiedź**\n", "\n", "Prawidłowe są dwie pierwsze odpowiedzi:\n", "\n", "- Im większe $N$, tym węższy przedział ufności.\n", "- Im większa $\\sigma$, tym szerszy przedział ufności.\n", "\n", "Zarówno odchylenie standardowe w populacji, jak i wielkość próby, wpływają na błąd standardowy, a zatem precyzję naszej estymacji (szerokość PU).\n", "\n", "Nie mają natomiast związku (i o to chodzi!) z poziomem ufności, który sami ustalamy arbitralnie (np. 95%).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8c79c925", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.408959Z", "iopub.status.busy": "2026-01-17T02:07:38.408755Z", "iopub.status.idle": "2026-01-17T02:07:38.413269Z", "shell.execute_reply": "2026-01-17T02:07:38.412741Z" } }, "outputs": [], "source": [ "def ci_mean_known_sigma(sample_mean: float, *, sigma: float, n: int, confidence_level: float = 0.95) -> tuple[float, float]:\n", " '''Przedział ufności dla średniej przy znanym σ.\n", "\n", " Parameters\n", " ----------\n", " sample_mean:\n", " Średnia z próby.\n", " sigma:\n", " Odchylenie standardowe w populacji (znane).\n", " n:\n", " Liczebność próby.\n", " confidence_level:\n", " Poziom ufności, np. 0.95.\n", "\n", " Returns\n", " -------\n", " (lower, upper)\n", " Dolna i górna granica PU.\n", " '''\n", " alpha = 1 - confidence_level\n", " z_crit = stats.norm.ppf(1 - alpha / 2)\n", " half_width = z_crit * sigma / np.sqrt(n)\n", " return sample_mean - half_width, sample_mean + half_width\n", "\n", "\n", "examples = [\n", " {\"sigma\": 5.0, \"n\": 10},\n", " {\"sigma\": 5.0, \"n\": 100},\n", " {\"sigma\": 10.0, \"n\": 10},\n", " {\"sigma\": 10.0, \"n\": 100},\n", "]\n", "\n", "sample_mean = 0.0\n", "for ex in examples:\n", " lower, upper = ci_mean_known_sigma(sample_mean, sigma=ex[\"sigma\"], n=ex[\"n\"], confidence_level=0.95)\n", " width = upper - lower\n", " print(f\"σ={ex['sigma']:.0f}, n={ex['n']:<3d} -> szerokość PU = {width:.3f}\")\n" ] }, { "cell_type": "markdown", "id": "e50ab1eb", "metadata": {}, "source": [ "Do tej pory milcząco zakładaliśmy, że konstruując przedział ufności wokół średniej (albo testując dotyczącą jej hipotezę zerową) **znamy odchylenie standardowe w populacji**.\n", "\n", "Takie sytuacje rzeczywiście się zdarzają, jednak o wiele częściej jest tak, że tego parametru wcale nie znamy.\n", "\n", "Skoro nie znamy średniej w populacji, dlaczego mielibyśmy znać odchylenie standardowe?\n", "\n", "**Pytanie**\n", "\n", "Co w takiej sytuacji?\n", "\n", "Czy nie znając odchylenia w populacji możemy w ogóle szacować jej średnią?\n", "\n", "A co się stanie, jeśli podmienimy nieznane odchylenie standardowe w populacji na odchylenie standardowe policzone w próbie?\n", "\n", "- Długość przedziału ufności zmienia się z próby na próbę.\n", "- Długość przedziału ufności pozostaje taka sama z próby na próbę.\n", "- Przedział ufności obejmuje średnią w populacji **rzadziej** niż wskazywałoby jego „nominalne” 95% (szczególnie dla małych prób).\n", "- Przedział ufności obejmuje średnią w populacji nieznacznie częściej niż w połowie przypadków.\n", "\n", "**Odpowiedź**\n", "\n", "Prawidłowe są dwie obserwacje:\n", "\n", "- długość przedziału ufności zmienia się z próby na próbę (bo zmienia się $s$),\n", "- i jeśli nadal używamy wartości krytycznej z rozkładu normalnego (Z), to pokrycie jest zbyt małe (przedział bywa zbyt wąski).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "96fd2571", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.414463Z", "iopub.status.busy": "2026-01-17T02:07:38.414338Z", "iopub.status.idle": "2026-01-17T02:07:38.418298Z", "shell.execute_reply": "2026-01-17T02:07:38.417706Z" } }, "outputs": [], "source": [ "rng = np.random.default_rng(SEED + 3)\n", "\n", "mu = 520\n", "sigma = 80\n", "sample_size = 20\n", "\n", "confidence_level = 0.95\n", "alpha = 1 - confidence_level\n", "z_crit = stats.norm.ppf(1 - alpha / 2)\n", "\n", "half_widths = []\n", "for _ in range(5):\n", " sample = rng.normal(loc=mu, scale=sigma, size=sample_size)\n", " s = float(sample.std(ddof=1))\n", " half_widths.append(z_crit * s / np.sqrt(sample_size))\n", "\n", "print(\"Kilka wartości 'ramienia' PU (z sd(sample)):\")\n", "for hw in half_widths:\n", " print(f\" {hw:.3f}\")\n" ] }, { "cell_type": "markdown", "id": "f7b26a72", "metadata": {}, "source": [ "Okazuje się, że odchylenie standardowe w próbie jest całkiem dobrym estymatorem odchylenia standardowego w populacji: przedziały ufności nadal „działają”.\n", "\n", "Ponieważ jednak to odchylenie za każdym razem liczymy z próby i zmienia się ono z próby na próbę, zmienna jest również długość przedziału.\n", "\n", "Mogłoby się wydawać, że raz będziemy przeszacowywać, a raz niedoszacowywać.\n", "\n", "Tymczasem okazuje się, że używając odchylenia standardowego z próby i krytycznych wartości z rozkładu normalnego (Z) **systematycznie zawężamy przedział**: faktyczne pokrycie spada poniżej 95%.\n", "\n", "Poniżej pokazujemy symulację dla małej próby (N=10) i większej (N=100).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6b648011", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.420068Z", "iopub.status.busy": "2026-01-17T02:07:38.419902Z", "iopub.status.idle": "2026-01-17T02:07:38.677174Z", "shell.execute_reply": "2026-01-17T02:07:38.676376Z" } }, "outputs": [], "source": [ "def coverage_with_z_and_sample_sd(*, mu: float, sigma: float, n: int, repeats: int = 100_000, seed: int = 0) -> float:\n", " rng = np.random.default_rng(seed)\n", "\n", " confidence_level = 0.95\n", " alpha = 1 - confidence_level\n", " z_crit = stats.norm.ppf(1 - alpha / 2)\n", "\n", " samples = rng.normal(loc=mu, scale=sigma, size=(repeats, n))\n", "\n", " sample_means = samples.mean(axis=1)\n", " sample_sds = samples.std(axis=1, ddof=1)\n", "\n", " half_widths = z_crit * sample_sds / np.sqrt(n)\n", "\n", " lower = sample_means - half_widths\n", " upper = sample_means + half_widths\n", "\n", " return float(np.mean((lower <= mu) & (mu <= upper)))\n", "\n", "\n", "mu = 520\n", "sigma = 80\n", "\n", "coverage_n10 = coverage_with_z_and_sample_sd(mu=mu, sigma=sigma, n=10, seed=SEED + 4)\n", "coverage_n100 = coverage_with_z_and_sample_sd(mu=mu, sigma=sigma, n=100, seed=SEED + 5)\n", "\n", "print(\"Pokrycie przy użyciu Z oraz sd(sample):\")\n", "print(\"N=10 :\", coverage_n10)\n", "print(\"N=100:\", coverage_n100)\n" ] }, { "cell_type": "markdown", "id": "e5a88dae", "metadata": {}, "source": [ "**Pytanie**\n", "\n", "Wyjaśnienie przynosi spojrzenie na rozkład wariancji (albo odchylenia standardowego) z próby.\n", "\n", "Która z poniższych odpowiedzi jest prawidłowa?\n", "\n", "- Ponieważ rozkład wariancji z próby, zwłaszcza dla małych prób, jest prawostronnie skośny, jest większe prawdopodobieństwo, że pojedyncza wartość $s$ niedoszacowuje $\\sigma$, niż że je przeszacowuje.\n", "- Ponieważ rozkład wariancji z próby, zwłaszcza dla małych prób, jest lewostronnie skośny, jest większe prawdopodobieństwo, że pojedyncza wartość $s$ niedoszacowuje $\\sigma$, niż że je przeszacowuje.\n", "- Ponieważ rozkład wariancji z próby, zwłaszcza dla małych prób, jest prawostronnie skośny, jest większe prawdopodobieństwo, że pojedyncza wartość $s$ przeszacowuje $\\sigma$, niż że je niedoszacowuje.\n", "- Ponieważ rozkład wariancji z próby, zwłaszcza dla małych prób, jest lewostronnie skośny, jest większe prawdopodobieństwo, że pojedyncza wartość $s$ przeszacowuje $\\sigma$, niż że je niedoszacowuje.\n", "\n", "**Odpowiedź**\n", "\n", "Rozkład wariancji z próby jest lekko prawoskośny. Zatem losując pojedynczą próbę, mamy większe prawdopodobieństwo, że jej wariancja będzie niższa od wariancji w całej populacji.\n", "\n", "Im większa liczebność próby, tym bardziej ta skośność zanika.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d92ff2a7", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.678929Z", "iopub.status.busy": "2026-01-17T02:07:38.678715Z", "iopub.status.idle": "2026-01-17T02:07:38.941170Z", "shell.execute_reply": "2026-01-17T02:07:38.940320Z" } }, "outputs": [], "source": [ "rng = np.random.default_rng(SEED + 6)\n", "\n", "mu = 0.0\n", "sigma = 80.0\n", "n = 10\n", "repeats = 200_000\n", "\n", "samples = rng.normal(loc=mu, scale=sigma, size=(repeats, n))\n", "sample_vars = samples.var(axis=1, ddof=1)\n", "\n", "plt.figure(figsize=(10, 5))\n", "plt.hist(sample_vars, bins=80, color=\"lightgrey\", edgecolor=\"white\")\n", "plt.axvline(sigma**2, color=\"red\", linestyle=\":\", linewidth=2, label=r\"$\\sigma^2$\")\n", "\n", "plt.title(\"Rozkład wariancji z próby (N=10): lekka prawoskośność\")\n", "plt.xlabel(\"wariancja z próby (ms²)\")\n", "plt.ylabel(\"liczność\")\n", "plt.legend()\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "print(\"Średnia wariancji z próby:\", float(sample_vars.mean()))\n", "print(\"Mediana wariancji z próby:\", float(np.median(sample_vars)))\n", "print(\"P( s^2 < σ^2 ):\", float(np.mean(sample_vars < sigma**2)))\n" ] }, { "cell_type": "markdown", "id": "1025b30d", "metadata": {}, "source": [ "Niemniej, kiedy nie znamy $\\sigma$ i opieramy się na $s$, od rozkładu normalnego lepszym przybliżeniem jest rozkład $t$.\n", "\n", "Rozkład ten wygląda podobnie do standardowego rozkładu normalnego, ale ma **grubsze ogony** (szczególnie dla małych prób).\n", "\n", "Dalej zaczyna się obszar krytyczny i dłuższe są przedziały ufności oparte na tym rozkładzie.\n", "\n", "Jest jeden parametr, który dokładnie określa kształt rozkładu $t$: liczba stopni swobody.\n", "\n", "Im mniejsza próba (mniej stopni swobody), tym trudniej odrzucić hipotezę zerową i tym mniej precyzyjny (dłuższy) przedział ufności wokół średniej.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6d7e6afa", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:38.942659Z", "iopub.status.busy": "2026-01-17T02:07:38.942539Z", "iopub.status.idle": "2026-01-17T02:07:39.093919Z", "shell.execute_reply": "2026-01-17T02:07:39.093450Z" } }, "outputs": [], "source": [ "x = np.linspace(-4, 4, 400)\n", "\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(x, stats.norm.pdf(x), color=\"black\", linestyle=\"--\", linewidth=2, label=r\"$N(0,1)$\")\n", "\n", "for df, color in [(10, \"red\"), (15, \"blue\"), (25, \"green\"), (35, \"orange\")]:\n", " plt.plot(x, stats.t.pdf(x, df=df), color=color, linewidth=2, label=f\"t({df})\")\n", "\n", "plt.title(\"Rozkład t a standardowy normalny\")\n", "plt.xlabel(\"wartość\")\n", "plt.ylabel(\"gęstość\")\n", "plt.legend()\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "51626936", "metadata": {}, "source": [ "### Widżet: rozkład $t$ a liczba stopni swobody\n", "\n", "Suwak poniżej pokazuje, jak zmienia się rozkład $t$ wraz ze wzrostem liczby stopni swobody (czyli w praktyce: wraz ze wzrostem liczebności próby)." ] }, { "cell_type": "code", "execution_count": null, "id": "25676e9d", "metadata": {}, "outputs": [], "source": [ "if not WIDGETY_DOSTEPNE:\n", " print('Widżety nie są dostępne: zainstaluj pakiet ipywidgets, aby korzystać z części interaktywnych.')\n", "else:\n", " df_slider = widgets.IntSlider(\n", " value=10,\n", " min=1,\n", " max=200,\n", " step=1,\n", " description='df',\n", " continuous_update=False,\n", " )\n", "\n", " def pokaz_t(df: int) -> None:\n", " df = int(df)\n", " x = np.linspace(-4, 4, 400)\n", "\n", " plt.figure(figsize=(10, 4))\n", " plt.plot(x, stats.norm.pdf(x), color='black', linestyle='--', linewidth=2, label=r'$N(0,1)$')\n", " plt.plot(x, stats.t.pdf(x, df=df), color='steelblue', linewidth=2, label=f't({df})')\n", "\n", " plt.title('Rozkład t a standardowy normalny')\n", " plt.xlabel('wartość')\n", " plt.ylabel('gęstość')\n", " plt.legend()\n", " plt.tight_layout()\n", " plt.show()\n", "\n", " out = widgets.interactive_output(pokaz_t, {'df': df_slider})\n", " display(widgets.VBox([df_slider, out]))\n" ] }, { "cell_type": "markdown", "id": "689f5f1a", "metadata": {}, "source": [ "Jak pamiętamy, wzór na statystykę testową $z$ wyglądał tak:\n", "\n", "$$\n", "z = \\frac{\\bar{X} - \\mu}{\\sigma/\\sqrt{N}}\n", "$$\n", "\n", "Zmodyfikowany tak, że uwzględnia fakt, że estymujemy odchylenie standardowe, wygląda tak:\n", "\n", "$$\n", "t = \\frac{\\bar{X} - \\mu}{s/\\sqrt{N}}\n", "$$\n", "\n", "Jeśli użyjemy krytycznych wartości z rozkładu $t$, symulacja pokrycia powinna wrócić w okolice 0.95.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "87bc664a", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:39.095243Z", "iopub.status.busy": "2026-01-17T02:07:39.095126Z", "iopub.status.idle": "2026-01-17T02:07:39.141268Z", "shell.execute_reply": "2026-01-17T02:07:39.140625Z" } }, "outputs": [], "source": [ "def coverage_with_t(*, mu: float, sigma: float, n: int, repeats: int = 100_000, seed: int = 0) -> float:\n", " rng = np.random.default_rng(seed)\n", "\n", " confidence_level = 0.95\n", " alpha = 1 - confidence_level\n", " t_crit = stats.t.ppf(1 - alpha / 2, df=n - 1)\n", "\n", " samples = rng.normal(loc=mu, scale=sigma, size=(repeats, n))\n", " sample_means = samples.mean(axis=1)\n", " sample_sds = samples.std(axis=1, ddof=1)\n", "\n", " half_widths = t_crit * sample_sds / np.sqrt(n)\n", "\n", " lower = sample_means - half_widths\n", " upper = sample_means + half_widths\n", "\n", " return float(np.mean((lower <= mu) & (mu <= upper)))\n", "\n", "\n", "mu = 520\n", "sigma = 80\n", "sample_size = 20\n", "\n", "coverage_t = coverage_with_t(mu=mu, sigma=sigma, n=sample_size, seed=SEED + 7)\n", "print(\"Pokrycie (przedział t):\", coverage_t)\n" ] }, { "cell_type": "markdown", "id": "bf61e01c", "metadata": {}, "source": [ "## Zadanie\n", "\n", "Na koniec zadanie.\n", "\n", "Pewni amerykańscy badacze (Compas et al., 1994) przyglądali się rozwojowi dzieci dorastających w stresujących warunkach.\n", "\n", "Badacze ci z zaskoczeniem odkryli, że badane przez nich dzieci zgłaszały mniej objawów lęku czy depresji, niż się tego spodziewano.\n", "\n", "Równocześnie jednak wyniki tych dzieci na Skali Kłamstwa (narzędzie mierzące tendencję do udzielania społecznie oczekiwanych odpowiedzi) były wyższe niż przeciętna.\n", "\n", "Średni wynik na Skali Kłamstwa w amerykańskiej populacji wynosi $3.87$, a w próbie $36$ dzieci objętych tym badaniem średnia wyniosła $4.39$, a odchylenie standardowe — $2.61$.\n", "\n", "Czy rzeczywiście badane dzieci miały podwyższoną tendencję do udzielania społecznie oczekiwanych odpowiedzi?\n", "\n", "Użyj funkcji `scipy.stats.t.ppf` (wartości krytyczne) oraz `scipy.stats.t.cdf`/`scipy.stats.t.sf` (p-wartość). Wybierz właściwe odpowiedzi:\n", "\n", "- Wartość statystyki $t$ wynosi $1.1954$ i jest niższa od wartości krytycznej dla dwustronnego $\\alpha = 0.05$ ($2.03011$). Nie mamy zatem podstaw, żeby uznać, że badane dzieci miały istotnie podwyższoną tendencję do udzielania społecznie oczekiwanych odpowiedzi.\n", "\n", "- Dolna granica 95-procentowego przedziału ufności wokół średniej w próbie wynosi $3.5069$, a zatem średnia w populacji ($3.87$) mieści się w tym przedziale. Nie mamy zatem podstaw, żeby uznać, że badane dzieci miały istotnie podwyższoną tendencję do udzielania społecznie oczekiwanych odpowiedzi.\n", "\n", "- Zgodnie z hipotezą zerową ta próba $36$ dzieci została wylosowana z populacji o średniej $3.87$.\n", "\n", "- Wartość statystyki $t$ wynosi $1.1954$ i jest niższa od wartości krytycznej dla dwustronnego $\\alpha = 0.05$ ($1.96$). Nie mamy zatem podstaw, żeby uznać, że badane dzieci miały istotnie podwyższoną tendencję do udzielania społecznie oczekiwanych odpowiedzi.\n" ] }, { "cell_type": "markdown", "id": "e5fa7339", "metadata": {}, "source": [ "### Zadanie — test i PU krok po kroku\n", "\n", "**Historia**: mamy próbę $n=36$ dzieci, średnia $\\bar{x}=4.39$, odchylenie standardowe $s=2.61$.\n", "\n", "**Pytanie**: czy średni wynik w populacji tych dzieci jest wyższy niż średnia w populacji amerykańskiej $\\mu_0=3.87$?\n", "\n", "**Hipotezy** (zależą od kierunkowości):\n", "\n", "- dwustronnie: $H_0: \\mu = 3.87$ wobec $H_A: \\mu \\neq 3.87$,\n", "- prawostronnie: $H_0: \\mu = 3.87$ wobec $H_A: \\mu > 3.87$.\n", "\n", "**Poziom istotności**: ustalamy $\\alpha=0.05$.\n", "\n", "**Statystyka i rozkład pod $H_0$**:\n", "\n", "$$\n", " t = \\frac{\\bar{x} - \\mu_0}{s/\\sqrt{n}},\\quad T \\sim t(df=n-1).\n", "$$\n", "\n", "**Region krytyczny**:\n", "\n", "- dwustronnie: $|t| \\ge t_{1-\\alpha/2,\\,df}$,\n", "- prawostronnie: $t \\ge t_{1-\\alpha,\\,df}$.\n", "\n", "**p-wartość „na piechotę”**:\n", "\n", "- dwustronnie: $p = 2\\,P(T \\ge |t_{obs}|\\mid H_0)$,\n", "- prawostronnie: $p = P(T \\ge t_{obs}\\mid H_0)$.\n", "\n", "**Weryfikacja funkcją**: mając tylko dane podsumowane nie uruchomimy `ttest_1samp` (brak surowych obserwacji), ale p-wartość liczymy z dystrybuanty rozkładu $t$ (`scipy.stats.t.cdf/sf`).\n", "\n", "**Interpretacja**: raportujemy estymatę efektu (np. $\\bar{x}-\\mu_0$) i 95% PU; p-wartość opisuje rzadkość danych warunkowo na $H_0$.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "eaf6c41d", "metadata": { "execution": { "iopub.execute_input": "2026-01-17T02:07:39.142658Z", "iopub.status.busy": "2026-01-17T02:07:39.142546Z", "iopub.status.idle": "2026-01-17T02:07:39.147132Z", "shell.execute_reply": "2026-01-17T02:07:39.146629Z" } }, "outputs": [], "source": [ "n = 36\n", "mu0 = 3.87\n", "sample_mean = 4.39\n", "sample_sd = 2.61\n", "\n", "alpha = 0.05\n", "\n", "df = n - 1\n", "standard_error = sample_sd / np.sqrt(n)\n", "\n", "t_stat = (sample_mean - mu0) / standard_error\n", "\n", "# Krytyczna wartość dla testu dwustronnego\n", "critical_two_sided = stats.t.ppf(1 - alpha / 2, df=df)\n", "\n", "# p-wartość \"na piechotę\" (dwustronnie)\n", "p_two_sided = 2 * stats.t.sf(abs(t_stat), df=df)\n", "\n", "# Jeśli ktoś chciałby test jednostronny: H_A: mu > mu0\n", "critical_one_sided = stats.t.ppf(1 - alpha, df=df)\n", "p_one_sided = stats.t.sf(t_stat, df=df)\n", "\n", "# 95% PU dla średniej\n", "ci_low = sample_mean - critical_two_sided * standard_error\n", "ci_high = sample_mean + critical_two_sided * standard_error\n", "\n", "# Estymata efektu w naturalnej skali (różnica średnich)\n", "effect = sample_mean - mu0\n", "\n", "print(f\"n = {n}, df = {df}\")\n", "print(f\"Średnia w próbie = {sample_mean:.2f}, sd w próbie = {sample_sd:.2f}\")\n", "print(f\"Efekt (różnica względem μ0) = {effect:.2f}\")\n", "print()\n", "print(f\"t = {t_stat:.4f}\")\n", "print(f\"t_krytyczne (dwustronnie, α=0.05) = {critical_two_sided:.5f}\")\n", "print(f\"p (dwustronnie) = {float(p_two_sided):.4f}\")\n", "print()\n", "print(f\"t_krytyczne (prawostronnie, α=0.05) = {critical_one_sided:.5f}\")\n", "print(f\"p (prawostronnie) = {float(p_one_sided):.4f}\")\n", "print()\n", "print(f\"95% PU dla średniej: ({ci_low:.4f}, {ci_high:.4f})\")\n" ] }, { "cell_type": "markdown", "id": "7b7d7b55", "metadata": {}, "source": [ "**Odpowiedź**\n", "\n", "Tylko ostatnia odpowiedź jest nieprawidłowa – musimy posłużyć się rozkładem $t$, nie zaś rozkładem normalnym.\n", "\n", "Dodatkowa uwaga o dyscyplinie językowej: zdanie „Zgodnie z hipotezą zerową ta próba została wylosowana...” nie jest wnioskiem z danych, tylko (mniej precyzyjnym) opisem **założenia** $H_0$.\n", "\n", "W interpretacji frequentistycznej mówimy raczej:\n", "\n", "- „Zakładając $H_0$ oraz założenia testu, takie dane (lub bardziej skrajne) nie są na tyle rzadkie, żebyśmy odrzucili $H_0$ przy $\\alpha = 0.05$”,\n", "- oraz pokazujemy estymatę efektu i niepewność: tu różnica średnich to $0.52$, a 95% PU dla średniej obejmuje wartość $\\mu_0=3.87$.\n" ] }, { "cell_type": "markdown", "id": "96efb311", "metadata": {}, "source": [ "## Podsumowanie\n", "\n", "- Błąd standardowy średniej maleje jak $1/\\sqrt{N}$.\n", "- 95% PU to własność procedury w powtarzalnych próbach (pokrycie ≈ 0.95).\n", "- Gdy $\\sigma$ nieznane, używamy rozkładu $t$ (grubsze ogony) i krytycznej wartości $t$.\n", "- W teście dwustronnym z $\\alpha=0.05$ wartość $\\mu_0$ jest zgodna z danymi, jeśli leży w 95% PU (przy tych samych założeniach).\n" ] }, { "cell_type": "markdown", "id": "ee358cec", "metadata": {}, "source": [ "## Ćwiczenia\n", "\n", "1) W badaniu czasów reakcji (ms) w prostym zadaniu 2AFC uzyskano z danych podsumowanych: $\\bar{x}=512$, $s=74$, $n=25$. Oblicz 95% PU dla średniej (użyj rozkładu $t$).\n", "2) Sprawdź, jak zmieni się szerokość PU, gdy $n$ wzrośnie z 25 do 100 (przy tym samym $s$).\n", "3) Własnymi słowami: czym różni się interpretacja 95% PU od interpretacji p-wartości?\n", "4) (Opcjonalnie) Efekt Stroopa jako problem jednopróbkowy: mamy różnice $d_i = RT_{\\text{niespójne}} - RT_{\\text{spójne}}$ dla $n=30$ osób. Z danych podsumowanych: $\\bar{d}=35$ ms, $s_d=60$ ms. Oblicz 95% PU dla średniej różnicy i zinterpretuj wynik.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.2" } }, "nbformat": 4, "nbformat_minor": 5 }