5  第五部分:統計檢定

本部分預計時間:30 分鐘

在第四部分,你已經學會畫出專業的圖表。現在我們要學習統計檢定 — 用數字證明你在圖表中看到的差異是否有意義。

我有兩份資料: - patient_data.csv:欄位 treatment(A/B)、age、gender(M/F)、los(住院天數) - patient_data_for_survival.csv:欄位 treatment(Drug_A/Drug_B)、age、gender、stage(I/II/III)、time(追蹤月數)、status(1=事件,0=設限)

請幫我一次完成以下統計分析:

  1. 用 Shapiro-Wilk 檢定 los 的常態性,根據結果選擇 t-test 或 Wilcoxon test 比較兩組
  2. 計算 Cohen’s d 效應量,用白話文解讀結果
  3. 檢查假設(Q-Q plot、Levene’s test),說明不符合時的替代方法
  4. 用卡方檢定比較兩組的性別比例
  5. 用存活資料畫 Kaplan-Meier 曲線(含信賴區間和風險表),做 log-rank test
  6. 跑 Cox 比例風險模型(treatment + age + gender + stage),整理成 gtsummary 表格

給我從讀取資料到產出所有結果的完整 script,加上中文註解。

5.1 任務 20:讓 AI 選擇統計方法

📋 複製這段話,貼給 AI:

上傳 patient_data.csv 給 AI,然後問: 我想要比較兩組病人(treatment A vs B)的住院天數(los)有沒有顯著差異。我的資料存在 patient_data 裡面。

請幫我:

  1. 依變項尺度、組數、配對與常態性,依教科書原則選擇最合適之統計,並簡要說明選擇理由。
  2. 給我執行這個檢定的 R 程式碼,用內建的套件優先
  3. 告訴我怎麼解讀結果

5.1.1 統計檢定選擇

# 讀取資料
patient_data <- read.csv("patient_data.csv")

# 先檢查資料分佈
library(ggplot2)

# 檢視分佈圖(字型已由 _common.R 全域設定)
ggplot(patient_data, aes(x = los, fill = treatment)) +
  geom_histogram(binwidth = 1, alpha = 0.6, position = "dodge") +
  facet_wrap(~treatment, ncol = 1)

# Shapiro-Wilk 常態性檢定:p > 0.05 表示資料可能符合常態分佈
shapiro_a <- shapiro.test(patient_data$los[patient_data$treatment == "A"])
shapiro_b <- shapiro.test(patient_data$los[patient_data$treatment == "B"])

print("Shapiro-Wilk 常態性檢定結果:")
[1] "Shapiro-Wilk 常態性檢定結果:"
print(paste("A 組 p-value:", round(shapiro_a$p.value, 4)))
[1] "A 組 p-value: 4e-04"
print(paste("B 組 p-value:", round(shapiro_b$p.value, 4)))
[1] "B 組 p-value: 0.0829"

5.1.2 執行統計檢定

# 執行 t-test(假設資料符合常態分佈)
t_test_result <- t.test(los ~ treatment, data = patient_data)
print(t_test_result)

    Welch Two Sample t-test

data:  los by treatment
t = -19.12, df = 68.524, p-value < 2.2e-16
alternative hypothesis: true difference in means between group A and group B is not equal to 0
95 percent confidence interval:
 -7.708356 -6.251644
sample estimates:
mean in group A mean in group B 
           4.58           11.56 
# 執行 Wilcoxon rank-sum test(無母數檢定)
wilcox_result <- wilcox.test(los ~ treatment, data = patient_data)
print(wilcox_result)

    Wilcoxon rank sum test with continuity correction

data:  los by treatment
W = 0, p-value < 2.2e-16
alternative hypothesis: true location shift is not equal to 0

5.2 任務 21:解讀你的結果

📋 把統計檢定的輸出複製起來,加上這段話貼給 AI:

這是我的統計檢定結果: 【貼上輸出】

請用白話文解釋這個結果,包括:

  1. 兩組有沒有顯著差異
  2. p-value 是什麼意思
  3. 這個結果在臨床上可能代表什麼

5.2.1 結果解讀

# 載入必要套件
library(dplyr)

# 安裝並載入 broom 套件(用於整理統計結果)
if (!require(broom, quietly = TRUE)) {
  install.packages("broom", repos = "https://cloud.r-project.org/")
}
library(broom)

# 安裝並載入 effsize 套件(用於計算效應量)
if (!require(effsize, quietly = TRUE)) {
  install.packages("effsize", repos = "https://cloud.r-project.org/")
}
library(effsize)

# 整理結果摘要
result_summary <- patient_data %>%
  group_by(treatment) %>%
  summarise(
    n = n(),
    mean_los = mean(los),
    sd_los = sd(los),
    median_los = median(los),
    min_los = min(los),
    max_los = max(los)
  )

print("各組描述性統計:")
[1] "各組描述性統計:"
print(result_summary)
# A tibble: 2 × 7
  treatment     n mean_los sd_los median_los min_los max_los
  <chr>     <int>    <dbl>  <dbl>      <dbl>   <int>   <int>
1 A            50     4.58   1.07          5       3       7
2 B            50    11.6    2.35         11       8      17
# 使用 broom::tidy() 整理 t-test 結果
t_test_tidy <- tidy(t_test_result)
print("T-test 結果(broom 整理後):")
[1] "T-test 結果(broom 整理後):"
print(t_test_tidy)
# A tibble: 1 × 10
  estimate estimate1 estimate2 statistic  p.value parameter conf.low conf.high
     <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>    <dbl>     <dbl>
1    -6.98      4.58      11.6     -19.1 3.54e-29      68.5    -7.71     -6.25
# ℹ 2 more variables: method <chr>, alternative <chr>
# 使用 broom::tidy() 整理 Wilcoxon 結果
wilcox_tidy <- tidy(wilcox_result)
print("Wilcoxon test 結果(broom 整理後):")
[1] "Wilcoxon test 結果(broom 整理後):"
print(wilcox_tidy)
# A tibble: 1 × 4
  statistic  p.value method                                          alternative
      <dbl>    <dbl> <chr>                                           <chr>      
1         0 4.63e-18 Wilcoxon rank sum test with continuity correct… two.sided  
# 計算效應量(Cohen's d)
cohen_d <- cohen.d(los ~ treatment, data = patient_data)
print(paste("效應量 (Cohen's d):", round(cohen_d$estimate, 3)))
[1] "效應量 (Cohen's d): -3.824"
# 解釋
if (t_test_result$p.value < 0.05) {
  print("結論:兩組在統計上有顯著差異 (p < 0.05)")
  print(paste("B 組的平均住院天數比 A 組多約",
              round(diff(result_summary$mean_los), 1), "天"))
} else {
  print("結論:兩組在統計上無顯著差異 (p >= 0.05)")
}
[1] "結論:兩組在統計上有顯著差異 (p < 0.05)"
[1] "B 組的平均住院天數比 A 組多約 7 天"

5.2.2 產出純文字報告

# 方法一:使用 sink() 將所有輸出導向檔案
sink("report.txt")

cat("=" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("        統計分析報告:兩組治療住院天數比較\n")
cat("=" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("報告產生時間:", format(Sys.time(), "%Y-%m-%d %H:%M:%S"), "\n\n")

cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("一、描述性統計\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
print(result_summary)

cat("\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("二、常態性檢定(Shapiro-Wilk Test)\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("A 組 p-value:", round(shapiro_a$p.value, 4), "\n")
cat("B 組 p-value:", round(shapiro_b$p.value, 4), "\n")
if (shapiro_a$p.value >= 0.05 & shapiro_b$p.value >= 0.05) {
  cat("結論:兩組資料均符合常態分佈假設\n")
} else {
  cat("結論:至少一組資料不符合常態分佈假設\n")
}

cat("\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("三、統計檢定結果\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")

cat("\n【T-test 結果】\n")
cat("檢定方法:", t_test_tidy$method, "\n")
cat("t 統計量:", round(t_test_tidy$statistic, 4), "\n")
cat("自由度:", round(t_test_tidy$parameter, 2), "\n")
cat("p-value:", format(t_test_tidy$p.value, digits = 4, scientific = FALSE), "\n")
cat("95% 信賴區間:[", round(t_test_tidy$conf.low, 2), ", ",
    round(t_test_tidy$conf.high, 2), "]\n")
cat("兩組平均差異:", round(t_test_tidy$estimate1 - t_test_tidy$estimate2, 2), "天\n")

cat("\n【Wilcoxon Rank-Sum Test 結果】\n")
cat("檢定方法:", wilcox_tidy$method, "\n")
cat("W 統計量:", wilcox_tidy$statistic, "\n")
cat("p-value:", format(wilcox_tidy$p.value, digits = 4, scientific = FALSE), "\n")

cat("\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("四、效應量\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("Cohen's d:", round(cohen_d$estimate, 3), "\n")
cat("效應量大小:", as.character(cohen_d$magnitude), "\n")
cat("\n效應量解讀標準:\n")
cat("  |d| < 0.2:微小效應\n")
cat("  0.2 <= |d| < 0.5:小效應\n")
cat("  0.5 <= |d| < 0.8:中效應\n")
cat("  |d| >= 0.8:大效應\n")

cat("\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("五、結論\n")
cat("-" %>% rep(60) %>% paste(collapse = ""), "\n")
if (t_test_result$p.value < 0.05) {
  cat("統計結論:兩組住院天數有顯著差異 (p < 0.05)\n")
  cat("臨床意義:B 組的平均住院天數比 A 組多約",
      round(diff(result_summary$mean_los), 1), "天\n")
} else {
  cat("統計結論:兩組住院天數無顯著差異 (p >= 0.05)\n")
  cat("臨床意義:目前證據不足以證明兩種治療在住院天數上有差異\n")
}

cat("\n")
cat("=" %>% rep(60) %>% paste(collapse = ""), "\n")
cat("報告結束\n")
cat("=" %>% rep(60) %>% paste(collapse = ""), "\n")

sink()  # 關閉 sink,恢復正常輸出

# 確認檔案已產生
cat("報告已儲存至 report.txt\n")
報告已儲存至 report.txt
cat("檔案大小:", file.info("report.txt")$size, "bytes\n")
檔案大小: 2166 bytes
# 顯示報告內容預覽
cat("\n--- 報告內容預覽 ---\n")

--- 報告內容預覽 ---
cat(readLines("report.txt", n = 20), sep = "\n")
============================================================ 
        統計分析報告:兩組治療住院天數比較
============================================================ 
報告產生時間: 2026-04-04 15:35:21 

------------------------------------------------------------ 
一、描述性統計
------------------------------------------------------------ 
# A tibble: 2 × 7
  treatment     n mean_los sd_los median_los min_los max_los
  <chr>     <int>    <dbl>  <dbl>      <dbl>   <int>   <int>
1 A            50     4.58   1.07          5       3       7
2 B            50    11.6    2.35         11       8      17

------------------------------------------------------------ 
二、常態性檢定(Shapiro-Wilk Test)
------------------------------------------------------------ 
A 組 p-value: 4e-04 
B 組 p-value: 0.0829 
結論:至少一組資料不符合常態分佈假設
cat("...\n")
...

5.3 任務 22:檢查假設

📋 複製這段話,貼給 AI:

剛剛的統計檢定有什麼前提假設?我要怎麼用 R 檢查我的資料有沒有符合這些假設?如果不符合,有什麼替代方法?

5.3.1 假設檢驗

# 1. 檢查常態性
if (!require(car, quietly = TRUE)) {
  install.packages("car", repos = "https://cloud.r-project.org/")
  library(car)
}

# Q-Q plot
par(mfrow = c(1, 2))
qqnorm(patient_data$los[patient_data$treatment == "A"], main = "Q-Q Plot: Treatment A")
qqline(patient_data$los[patient_data$treatment == "A"])

qqnorm(patient_data$los[patient_data$treatment == "B"], main = "Q-Q Plot: Treatment B")
qqline(patient_data$los[patient_data$treatment == "B"])

# 2. 檢查變異數同質性
levene_result <- leveneTest(los ~ treatment, data = patient_data)
print("Levene's Test for Homogeneity of Variance:")
[1] "Levene's Test for Homogeneity of Variance:"
print(levene_result)
Levene's Test for Homogeneity of Variance (center = median)
      Df F value   Pr(>F)    
group  1  21.224 1.23e-05 ***
      98                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# 3. 根據假設檢驗結果選擇適當方法
if (shapiro_a$p.value < 0.05 | shapiro_b$p.value < 0.05) {
  print("建議:資料不符合常態分佈,應使用 Wilcoxon rank-sum test")
} else if (levene_result$`Pr(>F)`[1] < 0.05) {
  print("建議:變異數不同質,應使用 Welch's t-test")
  welch_result <- t.test(los ~ treatment, data = patient_data, var.equal = FALSE)
  print(welch_result)
} else {
  print("建議:資料符合所有假設,可使用 Student's t-test")
}
[1] "建議:資料不符合常態分佈,應使用 Wilcoxon rank-sum test"

5.4 任務 23:類別變數的比較

📋 複製這段話,貼給 AI:

我想要看 treatment(A vs B)和 gender(M vs F)有沒有關聯。也就是說,A 組和 B 組的男女比例有沒有不同。

請給我適當的統計檢定程式碼,並教我怎麼解讀。

5.4.1 類別變數檢定

# 建立列聯表
contingency_table <- table(patient_data$treatment, patient_data$gender)
print("列聯表:")
[1] "列聯表:"
print(contingency_table)
   
     F  M
  A 23 27
  B 27 23
# 加上比例
prop_table <- prop.table(contingency_table, margin = 1) * 100
print("各組性別比例(%):")
[1] "各組性別比例(%):"
print(round(prop_table, 1))
   
     F  M
  A 46 54
  B 54 46
# 執行卡方檢定
chisq_result <- chisq.test(contingency_table)
print("卡方檢定結果:")
[1] "卡方檢定結果:"
print(chisq_result)

    Pearson's Chi-squared test with Yates' continuity correction

data:  contingency_table
X-squared = 0.36, df = 1, p-value = 0.5485
# Fisher's exact test(適用於小樣本)
fisher_result <- fisher.test(contingency_table)
print("Fisher's exact test:")
[1] "Fisher's exact test:"
print(fisher_result)

    Fisher's Exact Test for Count Data

data:  contingency_table
p-value = 0.5487
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
 0.3068457 1.7125734
sample estimates:
odds ratio 
 0.7280058 
# 解讀結果
if (chisq_result$p.value < 0.05) {
  print("結論:兩組的性別分佈有顯著差異")
} else {
  print("結論:兩組的性別分佈無顯著差異")
}
[1] "結論:兩組的性別分佈無顯著差異"

5.5 任務 24:存活分析 (Survival Analysis)

在臨床研究中,我們經常需要分析「時間到事件」的資料,例如:

  • 病人從診斷到死亡的時間
  • 從治療開始到疾病復發的時間
  • 從手術到再住院的時間

這類分析稱為 存活分析 (Survival Analysis),是臨床研究中非常重要的統計方法。

5.5.1 存活分析的特殊之處

存活分析資料有一個特點:設限 (Censoring)

  • 有些病人在研究結束時還活著(我們不知道他們最終會活多久)
  • 有些病人中途失聯(lost to follow-up)
  • 這些「不完整」的觀察值不能直接丟掉,需要特殊處理
Important新資料集

從這裡開始,我們使用另一份資料 patient_data_for_survival.csv(已包含在課程資料中)。這份資料的欄位和之前的 patient_data.csv 不同:

  • time:追蹤時間(月)
  • status:事件狀態(1 = 事件發生,0 = 設限/未發生)
  • treatment:治療組別(Drug_A 或 Drug_B)
  • stage:癌症期別(I、II、III)

📋 複製這段話,貼給 AI:

我有一組存活分析的資料(patient_data_for_survival.csv),包含追蹤時間(time)和事件狀態(status,1=事件發生,0=設限)。請幫我:

  1. 畫出 Kaplan-Meier 存活曲線,比較兩組治療
  2. 用 log-rank test 檢定兩組有沒有差異
  3. 用白話文解釋結果
問題 方法選擇
是不是 time-to-event?不是就回傳統 GLM
有沒有互斥終點截斷這個事件? competing risks(CIF + cause-specific / Fine-Gray)
同一人會不會多次發生這個事件,而且「次數」重要? recurrent events
是否在講多個臨床狀態與轉移路徑? multi-state
暴露/關鍵 covariate 隨時間出現或改變,或需要先活一段時間才能算暴露? time-dependent / trial emulation,防 immortal time
入隊時間、離隊(censoring)、中心/醫師,是否明顯和預後相關或構成分群? left truncation、IPCW / joint、cluster-robust 或 frailty

5.5.2 讀取存活分析資料

# 讀取存活分析資料
survival_data <- read.csv("patient_data_for_survival.csv")

# 檢視資料結構
str(survival_data)
'data.frame':   100 obs. of  7 variables:
 $ patient_id: int  1 2 3 4 5 6 7 8 9 10 ...
 $ treatment : chr  "Drug_A" "Drug_B" "Drug_B" "Drug_B" ...
 $ age       : int  64 45 58 73 64 73 68 50 52 59 ...
 $ gender    : chr  "F" "F" "M" "M" ...
 $ stage     : chr  "III" "III" "II" "I" ...
 $ time      : num  7.8 16.9 17.7 32.2 22 2.4 2.2 36 1.9 12.1 ...
 $ status    : int  1 1 1 1 1 1 1 0 0 1 ...
# 查看前幾筆
head(survival_data)
  patient_id treatment age gender stage time status
1          1    Drug_A  64      F   III  7.8      1
2          2    Drug_B  45      F   III 16.9      1
3          3    Drug_B  58      M    II 17.7      1
4          4    Drug_B  73      M     I 32.2      1
5          5    Drug_A  64      M    II 22.0      1
6          6    Drug_A  73      F   III  2.4      1
# 確認事件分佈
table(survival_data$treatment, survival_data$status)
        
          0  1
  Drug_A 16 36
  Drug_B 15 33

5.5.3 Kaplan-Meier 存活分析

# 安裝並載入必要套件
if (!require(survival, quietly = TRUE)) {
  install.packages("survival", repos = "https://cloud.r-project.org/")
}
if (!require(ggsurvfit, quietly = TRUE)) {
  install.packages("ggsurvfit", repos = "https://cloud.r-project.org/")
}

library(survival)
library(ggsurvfit)

# 建立存活物件
# Surv(time, status) 會建立一個存活時間物件
# time = 追蹤時間, status = 事件是否發生 (1=是, 0=否/設限)
surv_obj <- Surv(time = survival_data$time, event = survival_data$status)

# 看看存活物件長什麼樣子
head(surv_obj, 10)
 [1]  7.8  16.9  17.7  32.2  22.0   2.4   2.2  36.0+  1.9+ 12.1 
# 注意:數字後面的 "+" 表示該觀察值是 censored(設限)

5.5.4 繪製 Kaplan-Meier 存活曲線

# 使用 ggsurvfit 繪製存活曲線(字型已由 _common.R 全域設定)
# survfit2() 是 ggsurvfit 的專用函數,可以更好地追蹤變數名稱
survfit2(Surv(time, status) ~ treatment, data = survival_data) |>
  ggsurvfit(linewidth = 1) +
  add_confidence_interval() +
  add_risktable() +
  add_pvalue(caption = "Log-rank {p.value}") +
  add_quantile(y_value = 0.5, linetype = "dashed", color = "gray50") +
  scale_color_manual(values = c("#E7B800", "#2E9FDF")) +
  scale_fill_manual(values = c("#E7B800", "#2E9FDF")) +
  labs(
    x = "追蹤時間(月)",
    y = "存活機率",
    title = "Kaplan-Meier 存活曲線:Drug A vs Drug B"
  ) +
  theme(legend.position = "bottom")

5.5.5 查看存活率摘要

# 依照治療組別做 Kaplan-Meier 分析
km_fit <- survfit(Surv(time, status) ~ treatment, data = survival_data)

# 查看摘要(6個月、12個月、24個月的存活率)
summary(km_fit, times = c(6, 12, 24))
Call: survfit(formula = Surv(time, status) ~ treatment, data = survival_data)

                treatment=Drug_A 
 time n.risk n.event survival std.err lower 95% CI upper 95% CI
    6     40       9    0.823  0.0536        0.725        0.935
   12     30       9    0.634  0.0690        0.512        0.785
   24     19      11    0.402  0.0709        0.284        0.568

                treatment=Drug_B 
 time n.risk n.event survival std.err lower 95% CI upper 95% CI
    6     34      13    0.724  0.0651        0.607        0.864
   12     27       7    0.575  0.0721        0.450        0.735
   24     19       8    0.405  0.0716        0.286        0.573

5.5.6 Log-Rank Test:比較兩組存活率

# Log-rank test(最常用的存活曲線比較方法)
logrank_test <- survdiff(Surv(time, status) ~ treatment, data = survival_data)
print(logrank_test)
Call:
survdiff(formula = Surv(time, status) ~ treatment, data = survival_data)

                  N Observed Expected (O-E)^2/E (O-E)^2/V
treatment=Drug_A 52       36     35.1    0.0219    0.0451
treatment=Drug_B 48       33     33.9    0.0228    0.0451

 Chisq= 0  on 1 degrees of freedom, p= 0.8 
# 提取 p-value
logrank_pvalue <- 1 - pchisq(logrank_test$chisq, length(logrank_test$n) - 1)
print(paste("Log-rank test p-value:", round(logrank_pvalue, 4)))
[1] "Log-rank test p-value: 0.8319"
# 解讀結果
if (logrank_pvalue < 0.05) {
  print("結論:兩組的存活曲線有顯著差異 (p < 0.05)")
} else {
  print("結論:兩組的存活曲線無顯著差異 (p >= 0.05)")
}
[1] "結論:兩組的存活曲線無顯著差異 (p >= 0.05)"

5.5.7 計算中位存活時間

# 計算各組的中位存活時間
print("各組中位存活時間(月):")
[1] "各組中位存活時間(月):"
print(km_fit)
Call: survfit(formula = Surv(time, status) ~ treatment, data = survival_data)

                  n events median 0.95LCL 0.95UCL
treatment=Drug_A 52     36   17.5    12.1    27.9
treatment=Drug_B 48     33   17.7     9.0    32.2
# 使用 ggsurvfit 的 tidy 方式呈現
survfit2(Surv(time, status) ~ treatment, data = survival_data) |>
  tidy_survfit() |>
  head(10)
# A tibble: 10 × 16
    time n.risk n.event n.censor cum.event cum.censor estimate std.error
   <dbl>  <dbl>   <dbl>    <dbl>     <dbl>      <dbl>    <dbl>     <dbl>
 1   0       52       0        0         0          0    1        0     
 2   0.5     52       2        0         2          0    0.962    0.0277
 3   0.7     50       2        0         4          0    0.923    0.0400
 4   1.3     48       0        1         4          1    0.923    0.0400
 5   1.8     47       1        0         5          1    0.903    0.0454
 6   2       46       0        1         5          2    0.903    0.0454
 7   2.4     45       1        0         6          2    0.883    0.0507
 8   2.9     44       1        0         7          2    0.863    0.0557
 9   3.1     43       1        0         8          2    0.843    0.0604
10   5.5     42       1        0         9          2    0.823    0.0651
# ℹ 8 more variables: conf.high <dbl>, conf.low <dbl>, strata <fct>,
#   estimate_type <chr>, estimate_type_label <chr>, monotonicity_type <chr>,
#   strata_label <chr>, conf.level <dbl>

5.5.8 進階:Cox 比例風險模型

📋 複製這段話,貼給 AI:

我想要做更進階的存活分析,考慮多個變數(治療、年齡、性別、期別)對存活的影響。請幫我用 Cox proportional hazards model 分析,並解釋 Hazard Ratio 的意義。

# Cox 比例風險模型
# 可以同時考慮多個變數對存活的影響
cox_model <- coxph(
  Surv(time, status) ~ treatment + age + gender + stage,
  data = survival_data
)

# 查看模型結果
summary(cox_model)
Call:
coxph(formula = Surv(time, status) ~ treatment + age + gender + 
    stage, data = survival_data)

  n= 100, number of events= 69 

                    coef exp(coef) se(coef)      z Pr(>|z|)    
treatmentDrug_B -0.24768   0.78061  0.26698 -0.928 0.353548    
age              0.03947   1.04026  0.01122  3.518 0.000435 ***
genderM         -0.02607   0.97426  0.26378 -0.099 0.921265    
stageII          0.43456   1.54429  0.43274  1.004 0.315280    
stageIII         0.98111   2.66742  0.40840  2.402 0.016291 *  
stageIV          1.77548   5.90313  0.44506  3.989 6.63e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

                exp(coef) exp(-coef) lower .95 upper .95
treatmentDrug_B    0.7806     1.2811    0.4626     1.317
age                1.0403     0.9613    1.0176     1.063
genderM            0.9743     1.0264    0.5810     1.634
stageII            1.5443     0.6475    0.6613     3.606
stageIII           2.6674     0.3749    1.1980     5.939
stageIV            5.9031     0.1694    2.4674    14.123

Concordance= 0.682  (se = 0.029 )
Likelihood ratio test= 29.63  on 6 df,   p=5e-05
Wald test            = 28.58  on 6 df,   p=7e-05
Score (logrank) test = 30.83  on 6 df,   p=3e-05
# 整理成漂亮的表格
if (!require(gtsummary, quietly = TRUE)) {
  install.packages("gtsummary", repos = "https://cloud.r-project.org/")
}
library(gtsummary)

cox_model %>%
  tbl_regression(
    exponentiate = TRUE,  # 將係數轉換為 Hazard Ratio
    label = list(
      treatment ~ "治療組別",
      age ~ "年齡",
      gender ~ "性別",
      stage ~ "癌症期別"
    )
  ) %>%
  bold_p(t = 0.05) %>%
  modify_header(label = "**變數**") %>%
  modify_caption("**Cox 比例風險模型結果**")
Cox 比例風險模型結果
變數 HR 95% CI p-value
治療組別


    Drug_A
    Drug_B 0.78 0.46, 1.32 0.4
年齡 1.04 1.02, 1.06 <0.001
性別


    F
    M 0.97 0.58, 1.63 >0.9
癌症期別


    I
    II 1.54 0.66, 3.61 0.3
    III 2.67 1.20, 5.94 0.016
    IV 5.90 2.47, 14.1 <0.001
Abbreviations: CI = Confidence Interval, HR = Hazard Ratio

5.5.9 存活分析結果解讀

# 提取 Hazard Ratio 和信賴區間
cox_summary <- summary(cox_model)
hr <- exp(cox_summary$coefficients[, "coef"])
ci_lower <- exp(cox_summary$coefficients[, "coef"] - 1.96 * cox_summary$coefficients[, "se(coef)"])
ci_upper <- exp(cox_summary$coefficients[, "coef"] + 1.96 * cox_summary$coefficients[, "se(coef)"])

# 整理成表格
hr_table <- data.frame(
  Variable = names(hr),
  HR = round(hr, 2),
  CI_Lower = round(ci_lower, 2),
  CI_Upper = round(ci_upper, 2),
  p_value = round(cox_summary$coefficients[, "Pr(>|z|)"], 4)
)
print(hr_table)
                       Variable   HR CI_Lower CI_Upper p_value
treatmentDrug_B treatmentDrug_B 0.78     0.46     1.32  0.3535
age                         age 1.04     1.02     1.06  0.0004
genderM                 genderM 0.97     0.58     1.63  0.9213
stageII                 stageII 1.54     0.66     3.61  0.3153
stageIII               stageIII 2.67     1.20     5.94  0.0163
stageIV                 stageIV 5.90     2.47    14.12  0.0001
cat("\n=== Hazard Ratio 解讀指南 ===\n")

=== Hazard Ratio 解讀指南 ===
cat("HR = 1:該因素對存活沒有影響\n")
HR = 1:該因素對存活沒有影響
cat("HR > 1:該因素會增加死亡風險(例如 HR=2 表示風險增加一倍)\n")
HR > 1:該因素會增加死亡風險(例如 HR=2 表示風險增加一倍)
cat("HR < 1:該因素會降低死亡風險(例如 HR=0.5 表示風險減半)\n")
HR < 1:該因素會降低死亡風險(例如 HR=0.5 表示風險減半)

5.5.10 小結:存活分析的關鍵概念

概念 說明
Censoring(設限) 觀察結束時事件尚未發生(例如病人還活著)
Kaplan-Meier 估計存活機率隨時間變化的曲線
Log-rank test 比較兩組或多組存活曲線是否有差異
Hazard Ratio (HR) 相對風險,HR=2 表示風險是對照組的 2 倍
Cox model 可同時考慮多個變數對存活的影響