Wald Confidence Interval for a Difference of Binomial Proportions Based on Paired Data

Paired data에서 wald 신뢰 구간을 구하는 방법을 통해 민감도와 특이도의 신뢰 구간을 구하는 방법을 소개합니다.

Author
Published

August 26, 2024

2x2 table에서 민감도와 특이도는 본래 McNemar’s test를 통해 z=(f21f12)f12+f21로 정의된 z값의 p-value를 확인하는 방법으로 진행되었습니다. 하지만 위 방식으로는 민감도와 특이도의 신뢰 구간에 대한 정보를 알 수 없기에, 신뢰 구간까지 정할 수 있는 방법인 Wald confidence interval을 적용함으로써 더 많은 정보를 얻을 수 있습니다.

개요

이 문서에서는 진단 테스트 1과 테스트 2의 민감도 차이를 기반으로 신뢰 구간(CI)을 계산하는 방법을 다룹니다. 이를 위해 두 테스트의 결과를 기반으로 2x2 테이블을 구성하고, 각 민감도를 계산합니다. 최종적으로 두 민감도의 차이에 대한 신뢰 구간을 계산하는 수식을 도출합니다.

1. 기본 개념

각 테스트의 민감도 p1p2는 아래와 같이 정의됩니다:

  • p1은 테스트 1이 진정한 양성을 탐지한 비율입니다.
  • p2은 테스트 2가 진정한 양성을 탐지한 비율입니다.

2x2 테이블은 아래와 같이 구성될 수 있습니다:

Test 2 PositiveTest 2 NegativeTotalTest 1 Positiveaba+bTest 1 Negativecdc+dTotala+cb+dn

여기서 각 테스트의 민감도는 다음과 같이 계산됩니다.(특이도 역시 모든 데이터들이 양성인 것을 음성으로 바꾸어 생각하면 아래 과정이 동일해집니다)

p1=a+bn,p2=a+cn

2. 신뢰 구간의 도출

두 민감도의 차이에 대한 신뢰 구간을 계산하기 위해 Wald 방법을 사용할 수 있습니다. Wald 신뢰 구간은 아래와 같이 정의됩니다:

2.1. 분산의 기본 식

두 확률 변수 p1p2의 차이의 분산은 다음과 같이 계산됩니다:

Var(p2p1)=Var(p2)+Var(p1)2Cov(p2,p1)

2.2. 각 분산의 계산

우리는 p1p2를 비율로 간주할 수 있습니다. 이 비율들의 분산은 다음과 같이 계산됩니다:

Var(p1)=p1(1p1)n=p12+p11n(1p12+p11n)

Var(p2)=p2(1p2)n=p21+p11n(1p21+p11n)

2.3. 공분산 Cov(p2,p1) 의 계산

p2p1 의 공분산은 다음과 같이 정의됩니다:

Cov(p2,p1)=Cov(p11+p21,p11+p12)

이를 확장하면 다음과 같은 네 가지 항으로 분리할 수 있습니다:

Cov(p2,p1)=Cov(p11,p11)+Cov(p11,p12)+Cov(p21,p11)+Cov(p21,p12)

공분산은 다음과 같이 정리할 수 있습니다.

Var(p11)=p11(1p11)n

Cov(p11,p12)=p11p12n

Cov(p11,p21)=p11p21n

Cov(p21,p12)=p21p12n

이를 바탕으로 Cov(p2,p1)를 계산하면:

Cov(p2,p1)=p11(1p11p12p21)p21p12n

여기서 p11+p12+p21+p22=1임을 이용하여, 공분산을 다음과 같이 단순화할 수 있습니다:

Cov(p2,p1)=p11p22p21p12n

2.4. 최종적으로 Var(p2p1) 의 유도

이제 분산 공식을 대입하여 최종적으로 p2p1의 분산을 계산합니다:

Var(p2p1)=Var(p2)+Var(p1)2Cov(p2,p1)

따라서 p2p1의 분산은 다음과 같습니다:

Var(p2p1)=(p12+p21)(p21p12)2/nn

Var(p2p1)=(b+c)(bc)2/nn

이를 적용한 민감도의 차이에 대한 신뢰 구간은 다음과 같이 계산됩니다.  CI1α/2(θ^)=[θ^±z1α/21nb+c(bc)2n] 

  if ( (ci.method == "wald") & (cont.corr == FALSE) ) {
    # sensitivity
    b <- tab$diseased[1,2]; c <- tab$diseased[2,1]; n <- tab$diseased[3,3]
    sens.diff.se <- sqrt((b+c) - ((b-c)**2) / n) / n
    sens.diff.cl <- sens.diff + c(-1,1) * qnorm(1-alpha/2) * sens.diff.se}

만약 이항분포로 얻어진 값을 정규분포에 맞도록 조정하기 위해 continuity correction을 진행한다면 wald에서는 확률 p 하나당 12n만큼 분산을 늘려, 아래와 같은 값을 갖게 된다.

CI1α/2(θ^)=[θ^±(z1α/21nb+c(bc)2n+1n)].

  if ( (ci.method == "wald") & (cont.corr == TRUE) ) {
    # sensitivity
    b <- tab$diseased[1,2]; c <- tab$diseased[2,1]; n <- tab$diseased[3,3]
    sens.diff.se <- (sqrt((b+c) - ((b-c)**2) / n) / n) + 1/n
    sens.diff.cl <- sens.diff + c(-1,1) * qnorm(1-alpha/2) * sens.diff.se}

3. Wald 이외 신뢰구간 계산 방법

3.1. Agresti 신뢰 구간(Agresti CI)

Wald 신뢰 구간을 수정한 방법 중 하나로, 샘플 크기에 특정 상수를 더한 후 위 wald와 같은 방식으로 전체 샘플 크기 𝑛에 1에서 4까지의 상수를 더한 후 이 방법들을 비교했습니다. 그 결과, n+2를 사용하는 것이 표준 Wald 신뢰 구간과 비교했을 때 표본 증가의 효과로 신뢰 구간의 성능(coverage probability)을 향상시킴을 확인했습니다.  CI1α/2(θ^)=[θ^±z1α/21n+2(b+0.5)+(c+0.5)(bc)2n+2] 

  if (ci.method == "agresti-min") {
    k <- 0.5
    # sensitivity    
    b <- tab$diseased[1,2]+k; c <- tab$diseased[2,1]+k; n <- tab$diseased[3,3]+4*k
    sens.diff.se <- (sqrt((b+c) - ((b-c)**2) / n) / n) 
    sens.diff.cl <- sens.diff + c(-1,1) * qnorm(1-alpha/2) * sens.diff.se}

3.2. Tango

Tango는 귀무가설과 함수는 두 비율 간의 차이와, 주어진 정보 행렬(b, c, n)을 기반으로 likelihood를 계산해 신뢰구간을 계산하는 방법입니다. R에서는 scoreci.mp라는 별도의 함수를 사용해 진행합니다.

  if (ci.method == "tango") {
    # sensitivity    
    b <- tab$diseased[1,2]; c <- tab$diseased[2,1]; n <- tab$diseased[3,3]
    tango <- scoreci.mp(b, c, n, conf.level=1-alpha)    
    sens.diff.se <- NA    
    sens.diff.cl <- sort(c(tango$conf.int[1], tango$conf.int[2]))
    if ( (tango$conf.int[1] > sens.diff) | (tango$conf.int[2] < sens.diff))
      sens.diff.cl <- sort(-1*sens.diff.cl)}

4. 예시 코드

4.1. 일반적인 민감도, 특이도 검정

Loading required package: PropCIs
t1 <- read.tab.paired(18, 14, 0, 18,
                      18, 12, 2, 18)
t1
Two binary diagnostic tests (paired design)

Test1: 'Noname 1'
Test2: 'Noname 2'

Diseased:
           Test1 pos. Test1 neg. Total
Test2 pos.         18         14    32
Test2 neg.          0         18    18
Total              18         32    50

Non-diseased:
           Test1 pos. Test1 neg. Total
Test2 pos.         18         12    30
Test2 neg.          2         18    20
Total              20         30    50
sesp.diff.ci(t1, ci.method="wald", cont.corr=FALSE)
$sensitivity
     test1      test2       diff    diff.se   diff.lcl   diff.ucl 
0.36000000 0.64000000 0.28000000 0.06349803 0.15554615 0.40445385 

$specificity
      test1       test2        diff     diff.se    diff.lcl    diff.ucl 
 0.60000000  0.40000000 -0.20000000  0.06928203 -0.33579029 -0.06420971 

$ci.method
[1] "wald"

$alpha
[1] 0.05

$cont.corr
[1] FALSE
sesp.diff.ci(t1, ci.method="wald", cont.corr=TRUE)
$sensitivity
     test1      test2       diff    diff.se   diff.lcl   diff.ucl 
0.36000000 0.64000000 0.28000000 0.08349803 0.11634687 0.44365313 

$specificity
      test1       test2        diff     diff.se    diff.lcl    diff.ucl 
 0.60000000  0.40000000 -0.20000000  0.08928203 -0.37498957 -0.02501043 

$ci.method
[1] "wald"

$alpha
[1] 0.05

$cont.corr
[1] TRUE
sesp.diff.ci(t1, ci.method="agresti-min")
$sensitivity
     test1      test2       diff    diff.se   diff.lcl   diff.ucl 
0.36000000 0.64000000 0.28000000 0.06444681 0.15368658 0.40631342 

$specificity
      test1       test2        diff     diff.se    diff.lcl    diff.ucl 
 0.60000000  0.40000000 -0.20000000  0.06954236 -0.33630053 -0.06369947 

$ci.method
[1] "agresti-min"

$alpha
[1] 0.05

$cont.corr
[1] FALSE
sesp.diff.ci(t1, ci.method="tango")
$sensitivity
    test1     test2      diff   diff.se  diff.lcl  diff.ucl 
0.3600000 0.6400000 0.2800000        NA 0.1747417 0.4166512 

$specificity
      test1       test2        diff     diff.se    diff.lcl    diff.ucl 
 0.60000000  0.40000000 -0.20000000          NA -0.34470882 -0.06111243 

$ci.method
[1] "tango"

$alpha
[1] 0.05

$cont.corr
[1] FALSE

아래와 같은 방법으로 answer, test1, test2를 열로 가지고, 각 결과 데이터가 1,0(1은 diseased sample, 2는 non-diseased sample)로 표시되어 있는 경우에는 바로 paired table을 제작할 수 있습니다.

library(DTComPair)
tb <- tab.paired(answer, test1, test2, data = na.omit(sample_data))

4.2. 민감도, 특이도의 비열등성 검정

어떤 테스트의 민감도와 특이도가 대조 테스트에 비해 떨어지지 않는다는 것을 확인하기 위해서는 비열등성 검정을 사용해야 합니다. 이 때는 sesp.diff.ci 함수를 통해 구한 standard error 값과 두 민감도/특이도의 차이를 통해 p value를 계산합니다. 아래 예시에서는 민감도에 대한 비열등성 마진(sens_margin)을 5%, 특이도에 대한 비열등성 마진(spec_margin)을 10%로 설정했습니다.
민감도를 예로 들자면 귀무가설은 아래와 같고, 아래 수식에 따라 p value를 계산합니다.

H0:test1 sensitivitytest2 sensitivity sensitivity margin pvalue=1Φ(test1 sensitivitytest2 sensitivity+sensitivity marginsensitivity diff.SE)

library(DTComPair)
t1 <- read.tab.paired(18, 14, 0, 18,
                      18, 12, 2, 18)
t1.wald <- sesp.diff.ci(t1, ci.method="wald", cont.corr=FALSE)

sens_margin <-  0.05
spec_margin <-  0.1

p_value_sensitivity <- pnorm((t1.wald$sensitivity['diff'] + sens_margin) / t1.wald$sensitivity['diff.se'], lower.tail = FALSE)
p_value_specificity <- pnorm((t1.wald$specificity['diff'] + spec_margin) / t1.wald$specificity['diff.se'], lower.tail = FALSE)
p_value_sensitivity
        diff 
1.012589e-07 
p_value_specificity
     diff 
0.9255427 

따라서 민감도에 대해서는 위 귀무가설이 기각 되었음으로 test1의 민감도가 test2보다 열등하지 않다고 말할 수 있지만, 특이도에 대해서는 귀무가설이 기각되지 않았기 때문에 test1의 특이도가 test2에 비해 열등하지 않다는 결론을 내릴 수 없습니다.

Reuse

Citation

BibTeX citation:
@online{myung2024,
  author = {Myung, Hyojong},
  title = {Wald {Confidence} {Interval} for a {Difference} of {Binomial}
    {Proportions} {Based} on {Paired} {Data}},
  date = {2024-08-26},
  url = {https://blog.zarathu.com/posts/2024-08-26-sensspec-waldinterval/},
  langid = {en}
}
For attribution, please cite this work as:
Myung, Hyojong. 2024. “Wald Confidence Interval for a Difference of Binomial Proportions Based on Paired Data.” August 26, 2024. https://blog.zarathu.com/posts/2024-08-26-sensspec-waldinterval/.