이것저것/R 통계

[R 통계] 일치도 (Agreement) 평가 - Bland-altman plot

엘:) 2024. 11. 6. 19:48
  • 진단방법이나 장비를 비교할 때, 두 측정 방법이 일치하는지를 평가하기 위해 '일치도 (Agreement) 평가'를 수행합니다.
  • 일치도 외에도 신뢰도 (reliability), 재현성 (reproducibility), 일치도 (concordance) 등으로 표현하기도 합니다.
  • 보통 이미 Golden standard로 쓰이고 있는 A 측정 방법과 새로 개발된 B 측정 방법이 사실상 교환해 가며 사용할만큼 Accuracy, Precision이 높은지 평가하는 것을 의미합니다.

  • 결국 새로운 측정방법이 High accuracy, High precision 임을 증명하는 과정을 위해 일치도 평가가 수행됩니다.
  • 주의할 점이 있다면 t-검정이나 상관분석은 일치도 평가에 적절하지 않다는 점을 염두해둬야 합니다. 예를 들어 t 검정은 '평균 차이가 0이다' 라는 귀무가설을 검정하므로 '방법 간 차이가 없다' 는 것이지 '일치한다' 는 이야기는 아닙니다. 또한 상관관계 역시 선형의 관계를 볼 뿐 일치도를 보는 방법은 아닙니다.

 

1. Bland-altman plot의 기본 개념


  • 측정값 변수 : 연속형 변수
  • 비교하는 측정방법의 수 : 2
  • 간단하게 불일치 양상을 살펴보기 유용해서 검사법 비교에서 많이 사용됨

 

  • Bland-altman plot의 형태는 위와 같습니다.
  • X축: A방법과 B방법의 평균값
  • Y축: A방법과 B방법의 차이
  • 평균 차이(Mean): 'A방법과 B방법의 차이'의 전체 평균
  • 일치도 한계 (Limits of agreement, LoA): 표준편차를 이용하여 bias의 정도를 평가한 값 (표준편차의 1.96배 값 만큼을 더하고 뺀 값)

 

2. 분석 예제


  • 예제를 위해 우선 아래와 같이 임의의 두가지 측정방법 (Method1, Method2)를 생성합니다.
  • 실제 데이터를 사용할 때는 두 데이터의 개수가 일치하는지, 결측치는 없는지 미리 확인하시길 추천드립니다.
# 임의의 데이터 생성
set.seed(123)
n <- 300  # 샘플 크기

# 두 측정 방법의 데이터 생성
method1 <- rnorm(n, mean = 100, sd = 15)
method2 <- method1 + rnorm(n, mean = 1, sd = 6)  # method1에 약간의 편향과 오차 추가

# 데이터프레임 생성
data <- data.frame(
  method1 = method1,
  method2 = method2
)
  • 분석을 위해 필요한 값은 두 가지 방법의 평균, 두 가지 방법의 차이, 차이의 평균, 차이의 표준편차 이렇게 4가지 입니다.
# 차이와 평균 계산
differences <- data$method2 - data$method1
means <- (data$method1 + data$method2) / 2

# 평균 차이와 표준편차 계산
mean_diff <- mean(differences)
sd_diff <- sd(differences)
limits <- mean_diff + c(-1.96, 1.96) * sd_diff

 

  • 이렇게 산출을 해두면 해당 값을 끌어와서, ggplot으로 쉽게 Plot을 만들 수 있습니다.
# ggplot으로 그래프 생성
ggplot(data.frame(means = means, differences = differences), 
       aes(x = means, y = differences)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = mean_diff, color = "blue", linetype = "dashed") +
  geom_hline(yintercept = limits, color = "red", linetype = "dashed") +
  geom_hline(yintercept = 0, color = "gray", linetype = "solid") +
  labs(
    title = "Bland-Altman Plot",
    x = "Mean of Methods",
    y = "Difference between Methods (Method2 - Method1)"
  ) +
  theme_minimal() +
  annotate("text", x = max(means), y = mean_diff, 
           label = sprintf("Mean diff: %.2f", mean_diff), 
           hjust = 1, vjust = -0.5) +
  annotate("text", x = max(means), y = limits[2], 
           label = sprintf("+1.96 SD: %.2f", limits[2]), 
           hjust = 1, vjust = -0.5) +
  annotate("text", x = max(means), y = limits[1], 
           label = sprintf("-1.96 SD: %.2f", limits[1]), 
           hjust = 1, vjust = 1.5)

 

 

 

  • 색상을 제외하고 흑백만으로 논문 양식에 적합하게 아래와 같이 수정하는 방법도 있습니다. 데이터의 수에 따라서 이렇게 하는 방법말고 geom_point에 alpha 값을 주어서 겹치는 점들도 더 잘 보이게 하는 쪽이 더 유용할 수도 있습니다.
ggplot(data.frame(means = means, differences = differences), 
       aes(x = means, y = differences)) + 
  theme_bw(base_size = 14) + 
  geom_point(size = 3, shape = 21, fill = "white", stroke = 1) +
  geom_hline(yintercept = mean_diff, color = "black", linetype = "solid", size=1) +
  geom_hline(yintercept = limits, color = "black", linetype = "dashed") +
  geom_hline(yintercept = 0, color = "gray", linetype = "solid") +
  labs(
    x = "Mean of method A and method B",
    y = "Method A - Method B"
  ) +
  theme(panel.grid.major = element_blank(),
                     panel.grid.minor = element_blank(), axis.line = element_line(colour = "black")) +
  annotate("text", x = max(means), y = mean_diff, 
           label = sprintf("Mean: %.2f", mean_diff), 
           hjust = 1, vjust = -0.5, size = 14/.pt) +
  annotate("text", x = max(means), y = limits[2], 
           label = sprintf("+1.96 SD: %.2f", limits[2]), 
           hjust = 1, vjust = -0.5, size = 14/.pt) +
  annotate("text", x = max(means), y = limits[1], 
           label = sprintf("-1.96 SD: %.2f", limits[1]), 
           hjust = 1, vjust = 1.5, size = 14/.pt)

 

 

 

  • 측정 장비가 하나의 값을 output으로 내놓는 게 아니라, 여러 값을 내놓는 장비라면 비교해야할 변수가 많을 수 있습니다. 이런 경우 아래처럼 함수를 만들어두고 한번에 분석하는 것이 편할 수도 있습니다.
bland = function (x) {
  x$avg = (x[,1]+x[,2])/2
  x$diff = x[,2] - x[,1]
  
  x_diff_mean = mean(x$diff)
  x_diff_sd = sd(x$diff)
  
  x_lower = x_diff_mean - 1.96 * x_diff_sd
  x_upper = x_diff_mean + 1.96 * x_diff_sd
  
  ba_df = data.frame()
  ba_df = rbind(ba_df, c(x_diff_mean, x_lower, x_upper))
  names(ba_df) = c("Mean difference", "Lower", "Upper")
  
  return(ba_df)
}

# 미리 지정된 V1, V2, V3에 대하여 한번에 bland-altman analysis를 보고자 할때
ba_results = data.frame()
ba_results = rbind(ba_results, bland(V1), bland(V2), bland(V3))

 

 

  • 그렇게 어려운 수식은 아니라서 위와 같이 산출했지만, library를 이용하는 방법도 있습니다. blandr이라는 library를 이용하는 방법은 아래와 같습니다.
library(blandr)

# 임의의 데이터 생성
set.seed(123)
n <- 300  # 샘플 크기

# 두 측정 방법의 데이터 생성
method1 <- rnorm(n, mean = 100, sd = 15)
method2 <- method1 + rnorm(n, mean = 1, sd = 6)  # method1에 약간의 편향과 오차 추가

# 데이터프레임 생성
data <- data.frame(
  method1 = method1,
  method2 = method2
)

# Bland-Altman 분석 수행
ba_stats <- blandr.statistics(data$method1, data$method2)

# 기본적인 Bland-Altman plot 생성
blandr.draw(data$method1, data$method2,
            plotTitle = "Bland-Altman Plot")

  • 해당 함수를 사용하면 위와 같이 차이 평균, LoA에 대한 신뢰구간도 색상으로 표기해줍니다. 다만 이것의 신뢰구간까지 표현하면 그림이 한 눈에 들어오지 않아서 잘 사용하진 않습니다.
  • 통계 결과를 프린트하면 아래와 같이 확인할 수가 있어서, 편한 측면도 분명 있습니다. 취향에 맞게 원하시는 방법으로 분석하시면 됩니다.
print(ba_stats)

 

Bland-Altman Statistics
=======================
t = -3.0788, df = 299, p-value = 0.002271
alternative hypothesis: true bias is not equal to 0

=======================
Number of comparisons:  300 
Maximum value for average measures:  147.9435 
Minimum value for average measures:  60.90932 
Maximum value for difference in measures:  15.85865 
Minimum value for difference in measures:  -16.42875 

Bias:  -1.054656 
Standard deviation of bias:  5.933154 

Standard error of bias:  0.3425508 
Standard error for limits of agreement:  0.5860676 

Bias:  -1.054656 
Bias- upper 95% CI:  -0.3805402 
Bias- lower 95% CI:  -1.728772 

Upper limit of agreement:  10.57433 
Upper LOA- upper 95% CI:  11.72767 
Upper LOA- lower 95% CI:  9.420986 

Lower limit of agreement:  -12.68364 
Lower LOA- upper 95% CI:  -11.5303 
Lower LOA- lower 95% CI:  -13.83698 

=======================
Derived measures:  
Mean of differences/means:  -0.9235081 
Point estimate of bias as proportion of lowest average:  -1.731518 
Point estimate of bias as proportion of highest average -0.7128774 
Spread of data between lower and upper LoAs:  23.25796 
Bias as proportion of LoA spread:  -4.534602 

=======================
Bias: 
 -1.054656  ( -1.728772  to  -0.3805402 ) 
ULoA: 
 10.57433  ( 9.420986  to  11.72767 ) 
LLoA: 
 -12.68364  ( -13.83698  to  -11.5303 )

 

 

3. Bland-altman plot의 해석


  • Bland-altman plot을 그렸다면 값과 그림을 토대로 그것을 해석해야 합니다. 일반적인 평가 단계는 아래와 같습니다.

 

1. 크기에 따른 불일치 분포 양상을 살펴 본다. 예를 들어 X축이 커질수록 그 차이도 커지거나 작아지는 경우가 있는지 확인합니다.

(위 그림과 같이 X축이 커질수록 뭔가 Y축의 값에도 경향성이 있다면 다른 분석 방법을 고려해보는 것이 좋습니다.)

 

2. 평균 차이가 큰지 확인한다. 주의할 점은 평균 차이가 0에 가깝거나 t-검정 같은 차이 검정이 유의미하지 않다고 해서 두 방법이 일치한다는 의미는 아니라는 것입니다.

 

3. 일치도 한계, 즉 LoA 값을 평가하여 해당 값과 범위가 받아들여질만한 값인지 확인한다. 예를 들어 임상적으로 접근했을 때, 병변의 크기를 재는 측정 기기 간 일치도에서 LoA가 2mm라고 가정해봅시다. 만약 이것이 측정 기기 간의 오차라기 보다는 의료 영상을 분석하는 과정에서 1 pixel 차이 정도로 발생할 수 있는 오차고 임상적으로 유의미하지 않다면, 두 측정 방법은 교환 가능하다고 볼 수 있습니다.

 

 

 

[References]

  1. Pak, Son-Il, and Tae-Ho Oh. "Statistical test of agreement between measurements in method-comparison study." Journal of Veterinary Clinics 28.1 (2011): 108-112.
  2. 공경애. "검사법 평가: 검사법 비교와 신뢰도 평가." Ewha Med J 40.1 (2017): 9-16.
  3. Giavarina, Davide. "Understanding bland altman analysis." Biochemia medica 25.2 (2015): 141-151.