統計應用|單元 5:信賴區間——用一個區間說出「我們有多確定」

SuhsiungAI學習教室 授課教師:蘇志雄
適用對象:大學部・統計初學者 實作工具:Python(numpy、scipy.stats、pandas、matplotlib)
預估學習時間:講義閱讀 80 分鐘+實作 90 分鐘
← 上一單元:單元 4:抽樣分配與中央極限定理——從一個樣本看見整個母體


本單元學習目標

完成本單元後,你將能夠:

  1. 說明點估計與區間估計的差別,並寫出信賴區間的通用形式:估計值 ± 臨界值 × 標準誤。
  2. 在 σ 已知時建立平均數的 z 區間,在 σ 未知時建立 t 區間,並用 scipy.stats 一行算出來。
  3. 建立比例的信賴區間(Wald 與 Wilson),檢查 n·p̂ ≥ 10、n(1 − p̂) ≥ 10 的條件。
  4. 正確解讀「95% 信心水準」,並辨認常見的錯誤說法。
  5. 理解信心水準、樣本數、變異程度如何影響區間寬度,並能反推所需樣本數。
  6. 用 numpy 模擬 10,000 次抽樣,親眼驗證「約 95% 的區間會涵蓋真實參數」。
  7. 使用 Bootstrap(自助法)為平均數、中位數建立信賴區間。

一、為什麼需要信賴區間?

單元 4 告訴我們:x̄ 會在 μ 附近跳動,跳動的幅度大約是一個標準誤 SE。現在把問題反過來——手上只有一個 x̄,μ 可能在哪裡?

  • 😴 睡眠時數:30 位同學平均睡 6.69 小時。全校的平均是 6.69 嗎?大概不會剛好是,那「大概在多少到多少之間」?
  • 🗳️ 民調:「支持率 42%,誤差 ±3 個百分點」——這個 ±3% 就是信賴區間的誤差界限。
  • 🧋 手搖飲:店家想知道顧客平均每週買幾杯,只給一個數字太武斷,給一個範圍才誠實。

只報一個數字叫做點估計(point estimate);報一個範圍、並說明這個方法有多可靠,叫做區間估計(interval estimate),也就是信賴區間(Confidence Interval, CI)。


二、信賴區間的通用公式

💡 重點觀念:幾乎所有信賴區間都長這樣:
估計值 ± 臨界值 × 標準誤
其中「臨界值 × 標準誤」稱為誤差界限(margin of error, ME)。

參數 估計值 標準誤 SE 臨界值 使用條件
平均數 μ(σ 已知) x̄ σ/√n z*(95% 為 1.96) 母體常態或 n 夠大
平均數 μ(σ 未知) x̄ s/√n t*(df = n − 1) 母體近似常態或 n 夠大
比例 p p̂ √(p̂(1 − p̂)/n) z* n·p̂ ≥ 10 且 n(1 − p̂) ≥ 10

常用的 z 臨界值:

信心水準 90% 95% 99%
z* 1.645 1.960 2.576

為什麼是 1.96?因為標準常態分配中間 95% 的面積落在 −1.96 到 1.96 之間。單元 4 說「x̄ 有 95% 的機會落在 μ ± 1.96 SE 內」,等價地,「μ 有 95% 的機會被 x̄ ± 1.96 SE 這個(隨機的)區間蓋住」。


三、平均數的信賴區間:z 與 t

1. σ 已知:z 區間

x̄ ± z* × σ/√n。現實中 σ 幾乎不會已知,但這是理解所有區間的起點。

2. σ 未知:t 區間

用樣本標準差 s 代替 σ 時,多了一層不確定性,所以臨界值要改用 t 分配(自由度 df = n − 1):

x̄ ± t* × s/√n

  • t 分配比常態「尾巴厚」,所以 t* > z*:df = 4 時 t* ≈ 2.776,df = 29 時 ≈ 2.045,df 越大越接近 1.96。
  • 實務上只要 σ 未知,一律用 t 區間就對了;n 很大時 t 與 z 幾乎相同,用 t 也不會錯。

⚠️ 條件檢查:t 區間要求資料是隨機樣本、彼此獨立,且母體近似常態或 n 夠大(CLT)。n 很小又嚴重偏態、有極端值時,考慮 Bootstrap 或改看中位數。


四、比例的信賴區間

Wald 區間:p̂ ± z* × √(p̂(1 − p̂)/n)。這就是新聞民調「誤差 ±3%」的公式。

  • 條件:n·p̂ ≥ 10 且 n(1 − p̂) ≥ 10。
  • 樣本小或 p̂ 接近 0、1 時,Wald 區間的涵蓋率會明顯低於 95%,甚至出現負的下限。這時改用 Wilson 區間(stats.binomtest(k, n).proportion_ci(method="wilson")),表現穩定得多。

所需樣本數:想讓誤差界限不超過 E,

  • 平均數:n ≥ (z* × σ/E)²
  • 比例:n ≥ (z*/E)² × p(1 − p),不知道 p 時用最保守的 p = 0.5。

誤差界限減半,樣本數要變 4 倍——這和單元 4「標準誤與 √n 成反比」是同一件事。


五、如何正確解讀信賴區間

💡 正確說法:「如果用同樣的方法重複抽樣很多次,每次都算一個 95% 信賴區間,長期下來約有 95% 的區間會涵蓋真實的 μ。」我們對「方法」有 95% 的信心。

對手上這一個已經算好的區間 [6.36, 7.02],μ 要嘛在裡面、要嘛不在;μ 是固定的,不是隨機的。

常見誤解

錯誤說法 為什麼錯
「μ 有 95% 的機率落在 [6.36, 7.02]。」 μ 是固定值,算好的區間也是固定的,沒有機率可言;95% 描述的是方法的長期成功率。
「95% 的同學睡眠時數在 [6.36, 7.02] 之間。」 信賴區間估計的是平均數,不是個人數值的範圍;個人資料的分散程度遠大於此。
「重抽一次樣本,x̄ 有 95% 的機率落在這個區間。」 區間是針對 μ,不是預測下一個 x̄。
「99% 區間比 95% 區間好。」 信心較高的代價是區間更寬、資訊更模糊;沒有絕對的好壞。
「樣本很大,區間很窄,所以結果一定正確。」 區間只處理隨機誤差。抽樣有偏差(例如只問自願填答者),區間再窄也只是「精確地錯」。
「兩組區間有重疊,所以兩組一定沒有差異。」 重疊不等於沒有差異;比較兩組要用兩組差異的區間或檢定(單元 6)。

什麼會讓區間變窄?

因素 變化 區間寬度
信心水準 95% → 90% 變窄(但較常沒涵蓋 μ)
樣本數 n 變大 變窄(與 √n 成反比)
資料變異 s 變小 變窄

六、Bootstrap:不靠公式的信賴區間

有些統計量(例如中位數)沒有簡單的標準誤公式;資料偏態、樣本又不大時,t 區間也不一定可靠。Bootstrap(自助法)的想法很直接:

  1. 把手上的樣本當成「母體的縮影」。
  2. 從樣本中取後放回抽出 n 個,算一次統計量;重複 B = 10,000 次。
  3. 取這 10,000 個統計量的第 2.5 與第 97.5 百分位數,就是 95% 百分位數 bootstrap 區間。

scipy.stats.bootstrap 已內建,預設使用修正偏差與偏態的 BCa 法。

⚠️ Bootstrap 不是萬靈丹:它仍然假設樣本是隨機且具代表性的;樣本太小(例如 n < 10)時,樣本本身就不太能代表母體。


七、Python 實作:計算、模擬與驗證信賴區間

範例資料說明

本單元繼續使用單元 1~4 的 students.csv:30 位學生的「虛構」問卷資料(由程式隨機產生,非真實個資,人物與數值皆為虛構)。單元 4 把這 30 人當成母體;本單元回到真實情境,把這 30 人當成從全校抽出的樣本,用來推論全校的平均睡眠時數 sleep_hours、運動比例等。民調、模擬母體等例子則使用教學用假設的數字。

📥 下載範例資料:students.csv | 📥 完整程式碼:unit05.py

環境準備

本單元使用的套件和單元 4 相同。Google Colab 已內建;若在自己電腦執行,請先安裝:

pip install numpy scipy pandas matplotlib

把 students.csv 和程式放在同一個資料夾(使用 Colab 的同學,請先點左側📁圖示上傳 students.csv)。下面每一個程式區塊可以當成 Notebook 的一個儲存格(cell),請依序執行——因為所有模擬共用同一個亂數產生器,跳著執行會得到不同的數字。

儲存格 1:匯入套件,把睡眠時數當成「樣本」

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

# 讓圖表可以顯示繁體中文(依序嘗試 Windows / macOS / Linux 的字型)
plt.rcParams["font.sans-serif"] = [
    "Microsoft JhengHei", "PingFang TC", "Noto Sans CJK TC",
    "Noto Serif CJK TC", "Arial Unicode MS", "DejaVu Sans",
]
plt.rcParams["axes.unicode_minus"] = False  # 讓負號正常顯示

pd.set_option("display.unicode.east_asian_width", True)
pd.set_option("display.width", 160)

# 固定種子,讓每次執行的模擬結果都一樣
rng = np.random.default_rng(2026)

df = pd.read_csv("students.csv", encoding="utf-8-sig")
sleep = df["sleep_hours"].to_numpy()
n = len(sleep)
xbar = sleep.mean()
s = sleep.std(ddof=1)              # 這次把 30 人當成「樣本」→ 用樣本標準差(除以 n − 1)
se = s / np.sqrt(n)
print(f"樣本數 n = {n}")
print(f"樣本平均 x̄ = {xbar:.4f} 小時,樣本標準差 s = {s:.4f} 小時")
print(f"標準誤 SE = s / √n = {se:.4f} 小時")

執行結果:

樣本數 n = 30
樣本平均 x̄ = 6.6867 小時,樣本標準差 s = 0.8858 小時
標準誤 SE = s / √n = 0.1617 小時

說明:

  • 現在 30 人是樣本,標準差用 std(ddof=1)(除以 n − 1),得到 s = 0.8858 小時。
  • 標準誤 SE = 0.8858/√30 ≈ 0.162 小時:x̄ 大約會在 μ 附近 ±0.16 小時的範圍內跳動。

儲存格 2:σ 已知的 z 區間

# z 區間:假設母體標準差 σ 已知(教學假設:σ = 0.9 小時)
sigma = 0.9
for conf in [0.90, 0.95, 0.99]:
    z = stats.norm.ppf(1 - (1 - conf) / 2)
    me = z * sigma / np.sqrt(n)
    print(f"{conf:.0%} z 區間:z* = {z:.3f},誤差界限 = {me:.4f},"
          f"區間 = [{xbar - me:.3f}, {xbar + me:.3f}]")
# scipy 一行版本
lo, hi = stats.norm.interval(0.95, loc=xbar, scale=sigma / np.sqrt(n))
print(f"stats.norm.interval(0.95, ...) = [{lo:.3f}, {hi:.3f}]")

執行結果:

90% z 區間:z* = 1.645,誤差界限 = 0.2703,區間 = [6.416, 6.957]
95% z 區間:z* = 1.960,誤差界限 = 0.3221,區間 = [6.365, 7.009]
99% z 區間:z* = 2.576,誤差界限 = 0.4233,區間 = [6.263, 7.110]
stats.norm.interval(0.95, ...) = [6.365, 7.009]

說明:

  • 信心水準越高,z* 越大,區間越寬:90% 寬約 0.54 小時,99% 寬約 0.85 小時。
  • stats.norm.interval(信心水準, loc=x̄, scale=SE) 一行就能算出區間,和手算結果相同。
  • σ = 0.9 是教學假設;現實中 σ 未知,請看下一格。

儲存格 3:σ 未知的 t 區間

# t 區間:σ 未知,用 s 估計 → 臨界值改用 t 分配(自由度 df = n − 1)
df_t = n - 1
t_star = stats.t.ppf(0.975, df_t)
me_t = t_star * se
print(f"df = {df_t},t* = {t_star:.4f}(比 z* = 1.96 稍大)")
print(f"95% t 區間 = {xbar:.4f} ± {me_t:.4f} = [{xbar - me_t:.3f}, {xbar + me_t:.3f}]")
lo, hi = stats.t.interval(0.95, df_t, loc=xbar, scale=stats.sem(sleep))
print(f"stats.t.interval(0.95, df, loc=x̄, scale=stats.sem(...)) = [{lo:.3f}, {hi:.3f}]")
# 不同樣本數下 t* 與 z* 的差距
for d in [4, 9, 29, 99]:
    print(f"df = {d:>3}:t* = {stats.t.ppf(0.975, d):.3f}")

執行結果:

df = 29,t* = 2.0452(比 z* = 1.96 稍大)
95% t 區間 = 6.6867 ± 0.3308 = [6.356, 7.017]
stats.t.interval(0.95, df, loc=x̄, scale=stats.sem(...)) = [6.356, 7.017]
df =   4:t* = 2.776
df =   9:t* = 2.262
df =  29:t* = 2.045
df =  99:t* = 1.984

說明:

  • df = 29 時 t* = 2.045,比 1.96 大一些,所以 t 區間 [6.356, 7.017] 比 z 區間稍寬——這是「不知道 σ」要付出的代價。
  • stats.sem() 會算出 s/√n;搭配 stats.t.interval() 就是標準的寫法。
  • 解讀:我們有 95% 信心,全校學生的平均睡眠時數介於 6.36 到 7.02 小時之間。

儲存格 4:分組計算信賴區間

# 依分組計算平均睡眠時數的 95% t 區間
def t_ci(x, conf=0.95):
    x = np.asarray(x)
    m, se_ = x.mean(), stats.sem(x)
    lo, hi = stats.t.interval(conf, len(x) - 1, loc=m, scale=se_)
    return pd.Series({"n": len(x), "平均": m, "標準差": x.std(ddof=1), "下限": lo, "上限": hi, "寬度": hi - lo})

print(df.groupby("gender")["sleep_hours"].apply(t_ci).unstack().round(3).to_string())

執行結果:

           n   平均  標準差   下限   上限   寬度
gender                                          
女      17.0  6.453   0.969  5.955  6.951  0.996
男      13.0  6.992   0.682  6.580  7.405  0.825

說明:

  • 女生 n = 17,區間寬約 1.0 小時;男生 n = 13,但標準差較小,區間寬約 0.83 小時。區間寬度同時取決於 n 與 s。
  • 兩組區間有重疊,但這不足以斷定兩組沒有差異;正式比較要等到單元 6 的假設檢定。

儲存格 5:比例的信賴區間(Wald 與 Wilson)

# 比例的信賴區間:每週至少運動 3 次的同學比例
k = int((df["exercise_per_week"] >= 3).sum())
p_hat = k / n
se_p = np.sqrt(p_hat * (1 - p_hat) / n)
print(f"每週運動 ≥ 3 次:{k} / {n} 人,p̂ = {p_hat:.4f}")
print(f"檢查條件:n·p̂ = {n * p_hat:.0f},n·(1 − p̂) = {n * (1 - p_hat):.0f}(都要 ≥ 10)")
print(f"Wald 95% 區間 = {p_hat:.4f} ± {1.96 * se_p:.4f} = [{p_hat - 1.96 * se_p:.3f}, {p_hat + 1.96 * se_p:.3f}]")
# Wilson 區間:小樣本或 p̂ 接近 0、1 時表現較好
res = stats.binomtest(k, n).proportion_ci(confidence_level=0.95, method="wilson")
print(f"Wilson 95% 區間 = [{res.low:.3f}, {res.high:.3f}]")

執行結果:

每週運動 ≥ 3 次:10 / 30 人,p̂ = 0.3333
檢查條件:n·p̂ = 10,n·(1 − p̂) = 20(都要 ≥ 10)
Wald 95% 區間 = 0.3333 ± 0.1687 = [0.165, 0.502]
Wilson 95% 區間 = [0.192, 0.512]

說明:

  • 30 人中有 10 人每週運動至少 3 次,n·p̂ = 10 剛好達到門檻,Wald 區間勉強可用。
  • 區間 [0.165, 0.502] 非常寬——30 人估計比例,誤差大約 ±17 個百分點。比例需要的樣本數通常比平均數大得多。
  • Wilson 區間 [0.192, 0.512] 不以 p̂ 為中心,而是稍微往 0.5 拉,小樣本時更可靠。

儲存格 6:民調的誤差界限與所需樣本數(教學假設)

# 民調(教學假設):訪問 1,068 人,449 人支持
k_poll, n_poll = 449, 1068
p_poll = k_poll / n_poll
me_poll = 1.96 * np.sqrt(p_poll * (1 - p_poll) / n_poll)
print(f"p̂ = {p_poll:.4f},95% 區間 = [{p_poll - me_poll:.4f}, {p_poll + me_poll:.4f}],誤差 ±{me_poll:.2%}")
# 想要誤差 ±2%,最保守(p = 0.5)需要多少人?
for e in [0.03, 0.02]:
    print(f"誤差 ±{e:.0%}:n ≥ {int(np.ceil((1.96 / e) ** 2 * 0.25)):,} 人")

執行結果:

p̂ = 0.4204,95% 區間 = [0.3908, 0.4500],誤差 ±2.96%
誤差 ±3%:n ≥ 1,068 人
誤差 ±2%:n ≥ 2,401 人

說明:

  • 1,068 人、p̂ ≈ 0.42,誤差約 ±2.96%,就是新聞常見的「誤差 ±3%」。
  • 誤差從 ±3% 縮小到 ±2%,樣本要從 1,068 人增加到 2,401 人,成本大約多一倍以上。

儲存格 7:模擬——95% 信心到底是什麼意思?

# 模擬:「95% 信心」的真正意思——重複抽樣 10,000 次,看幾個區間涵蓋真實 μ
mu_true, sd_true, n_sim, reps = 7.0, 0.9, 30, 10_000   # 教學假設的母體
samples = rng.normal(mu_true, sd_true, size=(reps, n_sim))
m = samples.mean(axis=1)
se_s = samples.std(axis=1, ddof=1) / np.sqrt(n_sim)
t_s = stats.t.ppf(0.975, n_sim - 1)
cover_t = ((m - t_s * se_s <= mu_true) & (mu_true <= m + t_s * se_s)).mean()
cover_z = ((m - 1.96 * se_s <= mu_true) & (mu_true <= m + 1.96 * se_s)).mean()
print(f"t 區間涵蓋率 = {cover_t:.4f}(目標 0.95)")
print(f"誤用 z* = 1.96 搭配 s 的涵蓋率 = {cover_z:.4f}")
# 小樣本 n = 5 時差距更明顯
sm = rng.normal(mu_true, sd_true, size=(reps, 5))
mm, ss = sm.mean(axis=1), sm.std(axis=1, ddof=1) / np.sqrt(5)
for name, c in [("t*", stats.t.ppf(0.975, 4)), ("z*", 1.96)]:
    print(f"n = 5,用 {name} = {c:.3f}:涵蓋率 = {((mm - c * ss <= mu_true) & (mu_true <= mm + c * ss)).mean():.4f}")

執行結果:

t 區間涵蓋率 = 0.9487(目標 0.95)
誤用 z* = 1.96 搭配 s 的涵蓋率 = 0.9393
n = 5,用 t* = 2.776:涵蓋率 = 0.9504
n = 5,用 z* = 1.960:涵蓋率 = 0.8766

說明:

  • 從已知 μ = 7.0 的母體(教學假設)抽 10,000 個樣本,每個都算一個 95% t 區間,約 94.9% 涵蓋 μ,和 95% 非常接近。
  • 如果 σ 未知卻用 1.96,n = 30 時涵蓋率降到約 93.9%;n = 5 時只剩約 87.7%,大幅「說謊」。這就是一定要用 t* 的原因。

儲存格 8:比例區間的涵蓋率

# 比例區間的涵蓋率:n = 30、p = 0.1 時 Wald 區間會「說謊」
p0, n0 = 0.1, 30
ks = rng.binomial(n0, p0, size=reps)
ph = ks / n0
w = 1.96 * np.sqrt(ph * (1 - ph) / n0)
cover_wald = ((ph - w <= p0) & (p0 <= ph + w)).mean()
z2 = 1.96 ** 2
center = (ph + z2 / (2 * n0)) / (1 + z2 / n0)
half = 1.96 * np.sqrt(ph * (1 - ph) / n0 + z2 / (4 * n0 ** 2)) / (1 + z2 / n0)
cover_wilson = ((center - half <= p0) & (p0 <= center + half)).mean()
print(f"p = {p0}、n = {n0}:Wald 涵蓋率 = {cover_wald:.4f},Wilson 涵蓋率 = {cover_wilson:.4f}")

執行結果:

p = 0.1、n = 30:Wald 涵蓋率 = 0.8087,Wilson 涵蓋率 = 0.9744

說明:

  • p = 0.1、n = 30 時 n·p = 3,遠低於 10。名為 95% 的 Wald 區間,實際涵蓋率只有約 81%;Wilson 區間約 97%,比較接近(且偏保守)。
  • 結論:條件不符時,不要用 Wald 公式。

儲存格 9:Bootstrap 信賴區間

# Bootstrap(自助法):從樣本「取後放回」重抽,用重抽統計量的分位數當區間
B = 10_000
boot_idx = rng.integers(0, n, size=(B, n))
boot_mean = sleep[boot_idx].mean(axis=1)
boot_median = np.median(sleep[boot_idx], axis=1)
print(f"平均數 bootstrap 95% 百分位數區間 = [{np.percentile(boot_mean, 2.5):.3f}, {np.percentile(boot_mean, 97.5):.3f}]")
print(f"中位數 = {np.median(sleep):.2f},bootstrap 95% 區間 = "
      f"[{np.percentile(boot_median, 2.5):.3f}, {np.percentile(boot_median, 97.5):.3f}]")
# scipy 內建 stats.bootstrap(預設 BCa 法)
res = stats.bootstrap((sleep,), np.mean, confidence_level=0.95, n_resamples=10_000, random_state=rng)
print(f"stats.bootstrap(BCa)平均數區間 = [{res.confidence_interval.low:.3f}, {res.confidence_interval.high:.3f}]")

執行結果:

平均數 bootstrap 95% 百分位數區間 = [6.363, 6.987]
中位數 = 6.75,bootstrap 95% 區間 = [6.450, 7.050]
stats.bootstrap(BCa)平均數區間 = [6.337, 6.967]

說明:

  • 平均數的百分位數 bootstrap 區間 [6.36, 6.99]、BCa 區間 [6.34, 6.97],和 t 區間 [6.36, 7.02] 很接近,互相印證。
  • 中位數沒有簡單公式,bootstrap 直接給出 [6.45, 7.05]。
  • 因為睡眠時數是 0.1 小時一格的離散資料,中位數的 bootstrap 分配會有點「跳格」,這是正常現象。

儲存格 10:畫出信賴區間

XB = r"$\bar{x}$"
fig, axes = plt.subplots(2, 2, figsize=(13, 9))

# 左上:100 個 95% t 區間,紅色為沒涵蓋 μ 的區間
ax = axes[0, 0]
for i in range(100):
    lo_i, hi_i = m[i] - t_s * se_s[i], m[i] + t_s * se_s[i]
    miss = not (lo_i <= mu_true <= hi_i)
    ax.plot([lo_i, hi_i], [i, i], color="#C44E52" if miss else "#4C72B0", lw=2 if miss else 1)
    ax.plot(m[i], i, "o", ms=2, color="black")
ax.axvline(mu_true, color="black", linestyle="--", label=f"真實 μ = {mu_true}")
n_miss = sum(not (m[i] - t_s * se_s[i] <= mu_true <= m[i] + t_s * se_s[i]) for i in range(100))
ax.set_title(f"100 次抽樣的 95% 信賴區間:{n_miss} 個沒涵蓋 μ(紅色)")
ax.set_xlabel("睡眠時數(小時)")
ax.set_ylabel("第幾次抽樣")
ax.legend(fontsize=9, loc="upper right")

# 右上:信心水準 vs. 區間寬度(students.csv 睡眠時數)
ax = axes[0, 1]
levels = [0.80, 0.90, 0.95, 0.99]
for j, cf in enumerate(levels):
    lo_j, hi_j = stats.t.interval(cf, n - 1, loc=xbar, scale=se)
    ax.plot([lo_j, hi_j], [j, j], lw=6, color=["#8DA0CB", "#4C72B0", "#1A4E80", "#0B2545"][j])
    ax.text(hi_j + 0.02, j, f"{cf:.0%}:[{lo_j:.2f}, {hi_j:.2f}]", va="center", fontsize=9)
ax.axvline(xbar, color="gray", linestyle="--", label=f"{XB} = {xbar:.2f}")
ax.set_yticks(range(len(levels)), [f"{c:.0%}" for c in levels])
ax.set_xlim(6.0, 7.6)
ax.set_title("信心水準越高,區間越寬")
ax.set_xlabel("平均睡眠時數(小時)")
ax.legend(fontsize=9, loc="lower right")

# 左下:涵蓋率 vs. 樣本數(t* 與誤用 z*)
ax = axes[1, 0]
ns = [3, 5, 10, 20, 30, 50]
cov_t, cov_z = [], []
for nn in ns:
    smp = rng.normal(mu_true, sd_true, size=(5_000, nn))
    mm_, ss_ = smp.mean(axis=1), smp.std(axis=1, ddof=1) / np.sqrt(nn)
    tc = stats.t.ppf(0.975, nn - 1)
    cov_t.append(((mm_ - tc * ss_ <= mu_true) & (mu_true <= mm_ + tc * ss_)).mean())
    cov_z.append(((mm_ - 1.96 * ss_ <= mu_true) & (mu_true <= mm_ + 1.96 * ss_)).mean())
ax.plot(ns, cov_t, "o-", color="#4C72B0", label="t 區間(正確)")
ax.plot(ns, cov_z, "s--", color="#C44E52", label="z* = 1.96 搭配 s(誤用)")
ax.axhline(0.95, color="black", linestyle=":", label="目標 95%")
ax.set_ylim(0.80, 1.0)
ax.set_title("模擬涵蓋率:小樣本一定要用 t")
ax.set_xlabel("樣本數 n")
ax.set_ylabel("涵蓋率")
ax.legend(fontsize=9)

# 右下:bootstrap 平均數分配
ax = axes[1, 1]
ax.hist(boot_mean, bins=50, density=True, color="#55A868", edgecolor="white", label=f"10,000 個 bootstrap {XB}")
for v in np.percentile(boot_mean, [2.5, 97.5]):
    ax.axvline(v, color="#C44E52", linestyle="--")
ax.axvline(xbar, color="black", label=f"原始 {XB} = {xbar:.2f}")
ax.set_title("Bootstrap:重抽樣本平均數的分配(紅線為 2.5%、97.5% 分位數)")
ax.set_xlabel("平均睡眠時數(小時)")
ax.set_ylabel("密度")
ax.legend(fontsize=9)

plt.tight_layout()
plt.savefig("unit05_charts.png", dpi=120)
plt.show()

100 次抽樣的 95% 信賴區間;信心水準與區間寬度;t 與 z 的涵蓋率;bootstrap 平均數分配

圖表解讀:

  • 左上:100 個 95% 區間中,大部分(藍色)蓋住了 μ = 7.0,少數(紅色)沒有。你永遠不知道自己手上的那一個是藍的還是紅的——這就是「95% 信心」的意思。
  • 右上:同一份資料,80% → 99% 信心水準,區間越來越寬。
  • 左下:t 區間在各種 n 下都維持約 95%;誤用 z* 在小樣本時涵蓋率明顯不足。
  • 右下:bootstrap 平均數近似常態,紅色虛線之間就是 95% bootstrap 區間。

八、本單元重點整理

主題 重點
通用公式 估計值 ± 臨界值 × 標準誤
平均數 σ 未知一律用 t 區間:x̄ ± t* × s/√n,df = n − 1
比例 p̂ ± z* × √(p̂(1 − p̂)/n);條件不符時用 Wilson
解讀 95% 是方法的長期涵蓋率,不是 μ 落在某個區間的機率
寬度 信心越高越寬;n 越大、s 越小越窄;誤差減半需 4 倍樣本
Bootstrap 取後放回重抽,取分位數;適合中位數等沒有公式的統計量
限制 只處理隨機誤差,無法修正抽樣偏差

九、練習題

第 1 題|身高的信賴區間
用 students.csv 的 height_cm:
(a) 計算樣本平均、樣本標準差與標準誤。
(b) 計算全校平均身高的 95% 與 99% t 區間,並比較寬度。

第 2 題|所需樣本數
學校想估計學生的平均通勤時間,根據過去資料 σ ≈ 12 分鐘(教學假設)。若希望 95% 信賴區間的誤差界限分別不超過 ±3、±2、±1 分鐘,各至少需要調查多少人?

第 3 題|小樣本比例
40 位同學中有 3 人每天喝超過 2 杯手搖飲。
(a) 用 Wald 公式計算 95% 區間,有什麼問題?
(b) 改用 Wilson 區間。

第 4 題|Bootstrap 中位數
用 bootstrap(種子 5、重抽 10,000 次)為 monthly_allowance(每月生活費)的中位數建立 95% 百分位數區間。為什麼這裡用中位數而不是平均數?

第 5 題|觀念題:哪些說法正確?
某研究得到「大學生平均每週運動時數的 95% 信賴區間為 [2.1, 3.4] 小時」。判斷下列說法是否正確:
(a) 有 95% 的大學生每週運動 2.1 到 3.4 小時。
(b) 如果重複這個研究很多次,約 95% 的區間會涵蓋真實平均。
(c) 真實平均落在 [2.1, 3.4] 的機率是 95%。
(d) 若樣本數變成 4 倍,區間寬度大約減半。
(e) 這個樣本是在健身房門口訪問的,所以區間可以代表全校大學生。


十、練習題參考答案

📝 建議先自己作答,再對照答案喔!

第 1 題

import numpy as np
import pandas as pd
from scipy import stats

df1 = pd.read_csv("students.csv", encoding="utf-8-sig")
h = df1["height_cm"].to_numpy()
m1, se1 = h.mean(), stats.sem(h)
print(f"(a) x̄ = {m1:.2f} cm,s = {h.std(ddof=1):.2f} cm,SE = {se1:.3f} cm")
for cf in [0.95, 0.99]:
    lo, hi = stats.t.interval(cf, len(h) - 1, loc=m1, scale=se1)
    print(f"(b) {cf:.0%} t 區間 = [{lo:.2f}, {hi:.2f}],寬度 = {hi - lo:.2f} cm")

執行結果:

(a) x̄ = 164.21 cm,s = 8.99 cm,SE = 1.641 cm
(b) 95% t 區間 = [160.85, 167.56],寬度 = 6.71 cm
(b) 99% t 區間 = [159.68, 168.73],寬度 = 9.05 cm

(a) x̄ ≈ 164.21 cm、s ≈ 8.99 cm、SE ≈ 1.64 cm。
(b) 95% 區間約 [160.85, 167.56],99% 區間約 [159.68, 168.73]。信心從 95% 提高到 99%,寬度從 6.71 cm 增加到 9.05 cm。

第 2 題

import numpy as np

sigma2 = 12
for e in [3, 2, 1]:
    n_need = (1.96 * sigma2 / e) ** 2
    print(f"誤差 ±{e} 分鐘:n ≥ {n_need:.2f} → 至少 {int(np.ceil(n_need))} 人")

執行結果:

誤差 ±3 分鐘:n ≥ 61.47 → 至少 62 人
誤差 ±2 分鐘:n ≥ 138.30 → 至少 139 人
誤差 ±1 分鐘:n ≥ 553.19 → 至少 554 人

n ≥ (1.96 × 12/E)²,樣本數一律無條件進位:±3 分鐘需 62 人、±2 分鐘需 139 人、±1 分鐘需 554 人。誤差從 2 減到 1,人數約變 4 倍。

第 3 題

from scipy import stats

k3, n3 = 3, 40
p3 = k3 / n3
w3 = 1.96 * (p3 * (1 - p3) / n3) ** 0.5
print(f"p̂ = {p3:.3f},n·p̂ = {n3 * p3:.0f}(< 10,Wald 不適用)")
print(f"Wald 區間 = [{p3 - w3:.3f}, {p3 + w3:.3f}](下限是負的!)")
ci = stats.binomtest(k3, n3).proportion_ci(confidence_level=0.95, method="wilson")
print(f"Wilson 區間 = [{ci.low:.3f}, {ci.high:.3f}]")

執行結果:

p̂ = 0.075,n·p̂ = 3(< 10,Wald 不適用)
Wald 區間 = [-0.007, 0.157](下限是負的!)
Wilson 區間 = [0.026, 0.199]

(a) n·p̂ = 3 < 10,條件不符;Wald 區間的下限是負數,比例不可能小於 0,顯然不合理。
(b) Wilson 區間約 [0.026, 0.199],落在 0 到 1 之間,也較不對稱,更符合小比例的實際情況。

第 4 題

import numpy as np
import pandas as pd

df4 = pd.read_csv("students.csv", encoding="utf-8-sig")
money = df4["monthly_allowance"].to_numpy()
rng4 = np.random.default_rng(5)
boot = np.median(money[rng4.integers(0, len(money), size=(10_000, len(money)))], axis=1)
print(f"樣本中位數 = {np.median(money):,.0f} 元")
print(f"bootstrap 95% 區間 = [{np.percentile(boot, 2.5):,.0f}, {np.percentile(boot, 97.5):,.0f}] 元")

執行結果:

樣本中位數 = 10,750 元
bootstrap 95% 區間 = [9,500, 12,750] 元

中位數約 10,750 元,95% bootstrap 區間約 [9,500, 12,750] 元。生活費通常右偏、可能有少數特別高的值,中位數比平均數更能代表「典型」學生,而中位數沒有簡單的 SE 公式,正好適合用 bootstrap。

第 5 題

(a) 錯。信賴區間估計的是平均數,不是個人數值的範圍。
(b) 對。這正是信心水準的定義。
(c) 錯。真實平均是固定值,已算好的區間要嘛涵蓋、要嘛沒有;95% 是方法的長期成功率。
(d) 對。寬度與 1/√n 成正比,n 變 4 倍,寬度約減半。
(e) 錯。健身房門口的樣本有選擇偏差,會高估運動時數;信賴區間無法修正偏差。

💡 小技巧:不確定某個區間方法可不可靠時,先用 numpy 模擬它的涵蓋率(像儲存格 7、8 那樣),比死記公式條件更直觀。


下一單元預告

信賴區間回答「μ 大概在哪裡?」;但很多時候我們想問的是「μ 是不是等於某個值?」「男生和女生的平均睡眠時數真的不同嗎?」單元 6 將介紹假設檢定:虛無假設與對立假設、p 值的意義、型一與型二錯誤,並說明它和信賴區間其實是一體兩面。

← 回到單元 4:抽樣分配與中央極限定理——從一個樣本看見整個母體

👉 單元 6:假設檢定


© SuhsiungAI學習教室(suhsiung.com)・統計應用課程・授課教師:蘇志雄。本單元範例資料 students.csv 為教學用虛構資料,與任何真實人物無關;民調支持率、通勤時間標準差、模擬母體等例子中的數字,除另有說明外,皆為教學用假設或簡化,不代表任何真實機構或民調結果。