powerの分布を返す関数、作った

Posted by toro on Friday, August 5, 2022

powerの分布を考えてみよう

モチベ: 様々な真の値に対するpowerの分布を計算したい。
“真のリスク比” なんて神のみぞ知る値。powerの計算ってどれくらいrobustnessがあるのかな?という関心です。

  • 曝露変数: 0/1の2値、結果変数: 0/1の2値、のシチュエーション
  • 真のリスク比を与えられるとpowerを返す関数を作り、リスク比に対するpowerの関係を可視化したい

真の値と言っているのに、変動するのはおかしいんじゃ?という指摘はごもっともです。今回は単なる思考実験と思ってください。


NOTE: これはあくまで真の値を知っているときの話であって、標本値の話ではない

pacman::p_load(pwr, tidyverse)

PlotPower_byRR <- function(n_control, n_exposed, p_event_control, rr_min = 0.25, rr_max=3.0){
  
  # あるリスク比を定めると、powerを返す関数
  GetPower_byRR<- function(risk_ratio){
    res <- pwr.2p2n.test(h = ES.h(p1 = p_event_control, p2 = p_event_control*risk_ratio),
                         n1 = n_control, 
                         n2 = n_exposed,
                         sig.level = 0.05,
                         alternative = "two.sided")
    power <- res[["power"]]
    return(power)
    }
  
  # イベント発生人数がN of participantsを超えるとエラーになるので、先に宣言しておく
  rr_max <- ifelse(p_event_control*rr_max >= 1, 1/p_event_control, rr_max) 
  
  scale_riskratio <- seq(from=rr_min, to=rr_max, by=0.01)
  estimate_power <- sapply(scale_riskratio, GetPower_byRR)
  
  df <- data.frame(risk_ratio = scale_riskratio, power = estimate_power)
  # RR < 1 でpower >= 0.8となる、1に近い (最大の) RRを得る
  df_rr1 <- df %>% 
    filter(risk_ratio < 1 & power >= 0.8) %>% 
    arrange(power) # powerで昇順ソート
  power_080_1 <- max(df_rr1$risk_ratio)

  # RR > 1 でpower >= 0.8となる、1に近い (最小の) RRを得る
  df_rr2 <- df %>% 
    filter(risk_ratio > 1 & power >= 0.80) %>% 
    arrange(power) # powerで昇順ソート
  power_080_2 <- min(df_rr2$risk_ratio)
  
  # description
  cha1 <- sprintf("number of participants: control group=%d", n_control)
  cha2 <- sprintf(", exposure group=%d\n", n_exposed)
  cha3 <- sprintf("true value of event probability in control group=%.2f", p_event_control)
  cha4 <- sprintf("blue hashed line: the maximum value of risk ratio (within RR <1) that achieved power ≥ 0.8=%.2f", power_080_1)
  cha5 <- sprintf("\norange hashed line: the minimum value of risk ratio (within RR>1) that achieved power ≥ 0.8=%.2f", power_080_2)
  
  subtitle = paste0(cha1, cha2, cha3)
  caption = paste0(cha4, cha5)
  
  # plot
  plot <- ggplot(data = df, aes(x = risk_ratio, y = power)) + 
    geom_line() +
    geom_vline(xintercept = 1, colour = "darkgray") + 
    geom_vline(xintercept = power_080_1, linetype = 2, colour = "blue", alpha=0.8) + 
    geom_vline(xintercept = power_080_2, linetype = 2, colour = "orange", alpha=0.8) + 
    geom_hline(yintercept = 0.8, colour = "navy", alpha = 0.5) + 
    scale_y_continuous(breaks = seq(0, 1, 0.2)) + 
    labs(title = "Relationship between risk ratio and power in binary-outcome studies",
        subtitle = subtitle,
        caption = caption) +
    xlab("risk ratio of exposed group compared to control group") +
    theme_bw() 
  return(plot)
}

関数の説明

引数は次の通り

  • n_control: コントロール群の人数
  • n_exposed: 曝露群の人数
  • p_event_control: コントロール群でのイベント発生割合

これらを入力すると、曝露のリスク比(横軸)に対するpower(縦軸)を返します。

example1

以降のセクションでは、リスク比 (RR) が1以上と考えます。(RR<1でも同じ議論です)


コントロール群100人、曝露群100人で、曝露群でのイベント発生割合が25%とします。

真のRRが1.75ならpowerは0.80となり、「十分」と考えられます。 ただし、リスク比が1.5ならpower=0.50程度となります。かなりばらつくんですね。

PlotPower_byRR(n_control = 100, 
               n_exposed = 100, 
               p_event_control = 0.25)

example2

人数を増やしてみましょう。今度はコントロール群200人、曝露グループ200人にします。

この場合、RR=1.75のときpowerはほぼ1 (100%) となります。人数が増えたので、当然です。 power=0.8を達成するリスク比は1.52となりました。

PlotPower_byRR(n_control = 200, 
               n_exposed = 200, 
               p_event_control = 0.25)

感想

先行研究で、RR=1.75 [95%CI: 1.50-2.00] みたいな結果があったとしましょう。 同じような研究を実施する場合、この値を使ってpower計算することになると思います。

素直にRR=1.75という点推定値を使えば、example1の通り、各群100人合計200人でokです。 ただし、「RR=1.75が真の値である」という仮定を置いているだけで、実際には下振れしてしまうかもしれません。期待値的に点推定値を使うのはご尤もなんですが、真の値なんて知り得ない以上、ある程度ばらつきや分布を考えてもいいのかも?と思ってしまいました。例えば ±1σ の幅を持たせてあげるとか。

こういう話は研究のリソースや研究倫理も絡むのでもっと難しい議論になりそうですね。ひとまず勉強の感想でした。