13  定性分析

13.1 分析目的

定性(qualitative)分析评价候选方法与参考方法在阳性/阴性判定上的一致程度,对应 CLSI EP12。 ivdtools 提供 raw_to_table() 从原始配对数据构建四格表,counts_to_table() 从已有的 TP/FP/TN/FN 计数直接构建,并配套 diagnostics()(灵敏度、特异度、PPV、NPV、似然比等)、kappa()(Kappa 与 PABAK)与 mcnemar()(配对差异检验)三个分析方法。定量结果需转定性时可用 continuous_to_binary()

本文用三个示例演示三种数据来源的完整分析。示例数据为确定性的教学数据,阳性方向与阈值应依据 方案确定。

代码
library(ivdtools)
library(readr)

13.2 函数概述

函数 主要用途 关键输入或输出
raw_to_table() 原始配对数据构建四格表 candidatereferencepositivena.rm
counts_to_table() 由 TP/FP/TN/FN 构建四格表 candidate/reference 命名
continuous_to_binary() 定量转定性 cutoff(≥ cutoff 为 1)
describe() 原始数据描述 raw_to_table() 来源可用
diagnostics() 诊断准确性指标 ci.methodprevalence
kappa() Kappa / PABAK prevalence 切换
mcnemar() 配对差异检验 不一致对 <25 用精确检验

13.3 示例一:原始配对数据的完整分析

13.3.1 读取和核验数据

代码
qual <- read_csv("./data/qualitative-raw.csv", show_col_types = FALSE)
qual <- as.data.frame(qual)
str(qual)
'data.frame':   60 obs. of  5 variables:
 $ id    : chr  "S001" "S002" "S003" "S004" ...
 $ new   : chr  "positive" "negative" "negative" "negative" ...
 $ gold  : chr  "positive" "negative" "negative" "negative" ...
 $ age   : num  46.1 44.7 65.7 60 64.6 32.7 47 40.9 38.4 48.1 ...
 $ gender: chr  "M" "F" "M" "F" ...
代码
table(qual$gold)

negative positive 
      36       24 
代码
table(qual$new)

negative positive 
      36       24 

13.3.2 构建四格表

代码
qa <- raw_to_table(
  qual,
  candidate = "new",
  reference = "gold",
  id = "id",
  positive = "positive"
)
qa

Fourfold Table
  Source            : raw data
  Total n           : 60
  Duplicate IDs     : none
  Candidate         : new
  Reference         : gold

  2x2 Contingency Table
  ---------------------------------------- 
              gold                       
                  positive  negative  Sum       
  ---------------------------------------- 
  new   positive  23        1         24        
        negative  1         35        36        
        sum       24        36        60        
  ---------------------------------------- 

参数说明: raw_to_table() 要求候选与参考列都恰好有 2 个水平;positive 指定阳性水平 (用于因子/字符列的水平映射);na.rm 控制是否删除缺失行。id 可选,用于重复样本检测。

13.3.3 原始数据描述

代码
qa <- describe(qa)

Data Summary
  Rows:    60
  Columns: 5
  ID:      60 unique, no duplicates

  new
    negative        36  ( 60.0%)
    positive        24  ( 40.0%)
  gold
    negative        36  ( 60.0%)
    positive        24  ( 40.0%)
  age
    Mean (SD):       51.05 (13.45)
    Median (Q1-Q3):  48.90 (42.15 - 57.28)
    Range:           24.30 - 90.10
  gender
    F               32  ( 53.3%)
    M               28  ( 46.7%)

describe() 仅对 raw_to_table() 来源可用,输出行数、ID 重复、各列摘要与缺失情况。

13.3.4 诊断准确性指标

代码
qa <- diagnostics(qa, ci.method = "wilson")

Diagnostic Performance Summary
  Sensitivity        0.958  (0.798, 0.993) 
  Specificity        0.972  (0.858, 0.995) 
  PPV                0.958  (0.798, 0.993) 
  NPV                0.972  (0.858, 0.995) 
  Accuracy           0.967  (0.886, 0.991) 
  Prevalence         0.400  (0.286, 0.526) 
  LR+                34.500  (4.986, 238.724) 
  LR-                0.043  (0.006, 0.292) 
  Odds Ratio         805.000  (47.921, 13522.837) 

  # PPV & NPV are based on data prevalence of 0.400.
  # Confidence intervals for proportions use 'wilson' method.
代码
qa$diagnostics[c("sensitivity", "specificity", "ppv", "npv", "accuracy")]
$sensitivity
      est     lower     upper 
0.9583333 0.7975819 0.9926065 

$specificity
      est     lower     upper 
0.9722222 0.8583028 0.9950796 

$ppv
      est     lower     upper 
0.9583333 0.7975819 0.9926065 

$npv
      est     lower     upper 
0.9722222 0.8583028 0.9950796 

$accuracy
      est     lower     upper 
0.9666667 0.8863623 0.9908107 

本例灵敏度 0.958、特异度 0.972。若已知目标人群患病率,可用 prevalence 重算 PPV/NPV:

代码
qa <- diagnostics(qa, prevalence = 0.4)

Diagnostic Performance Summary
  Sensitivity        0.958  (0.798, 0.993) 
  Specificity        0.972  (0.858, 0.995) 
  PPV                0.958  (0.790, 0.993) 
  NPV                0.972  (0.864, 0.995) 
  Accuracy           0.967  (0.886, 0.991) 
  Prevalence         0.400 
  LR+                34.500  (4.986, 238.724) 
  LR-                0.043  (0.006, 0.292) 
  Odds Ratio         805.000  (47.921, 13522.837) 

  # PPV & NPV are based on user-specified prevalence of 0.400.
  # Confidence intervals for proportions use 'wilson' method.
代码
qa$diagnostics[c("ppv", "npv")]
$ppv
      est     lower     upper 
0.9583333 0.7895852 0.9926193 

$npv
      est     lower     upper 
0.9722222 0.8641373 0.9950711 

参数说明: diagnostics()ci.method 可选 7 种比例置信区间方法(默认 "wilson"); 给出 prevalence 时 PPV/NPV 按贝叶斯公式重算,否则基于数据患病率。

13.3.5 Kappa 一致性

代码
qa <- kappa(qa)

Cohen's Kappa
  new  vs  gold
  n = 60

  Kappa                 0.9306
  SE                    0.0483
  95% CI                (0.8359, 1.0000)
  Observed agreement    0.9667
  Expected agreement    0.5200
代码
qa <- kappa(qa, prevalence = 0.5)

PABAK
  new  vs  gold
  n = 60

  Kappa                 0.9333
  SE                    0.0463
  95% CI                (0.8425, 1.0000)
  Observed agreement    0.9667
  Expected agreement    0.5000
  # PABAK (prevalence = 0.500): 2 * p_obs - 1, assumes expected agreement = 0.5

参数说明: kappa() 给出 Cohen’s Kappa;当给出 prevalence 时改为计算 PABAK (2·p_obs − 1)。两者对患病率偏倚的处理不同,报告时应注明方法。

13.3.6 McNemar 检验

代码
qa <- mcnemar(qa)

McNemar Test
  new  vs  gold
  Discordant pairs:  b (pos, neg) = 1,  c (neg, pos) = 1
  n (b + c)        = 2

  Method: Exact binomial test
  P-value:          1.0000
  Conclusion: Not significant at 95% confidence level

不一致对 b+c = 2 < 25,函数自动使用精确二项检验。

13.3.7 汇总

代码
summary(qa)

Qualitative Analysis - summary
-------------------------------------------------

Fourfold Table
  Source            : raw data
  Total n           : 60
  Duplicate IDs     : none
  Candidate         : new
  Reference         : gold

  2x2 Contingency Table
  ---------------------------------------- 
              gold                       
                  positive  negative  Sum       
  ---------------------------------------- 
  new   positive  23        1         24        
        negative  1         35        36        
        sum       24        36        60        
  ---------------------------------------- 

Analyses performed:

  [x] Describe
  [x] Diagnostics
  [x] Kappa
  [x] McNemar

Data Summary
  Rows:    60
  Columns: 5
  ID:      60 unique, no duplicates

  new
    negative        36  ( 60.0%)
    positive        24  ( 40.0%)
  gold
    negative        36  ( 60.0%)
    positive        24  ( 40.0%)
  age
    Mean (SD):       51.05 (13.45)
    Median (Q1-Q3):  48.90 (42.15 - 57.28)
    Range:           24.30 - 90.10
  gender
    F               32  ( 53.3%)
    M               28  ( 46.7%)

Diagnostic Performance Summary
  Sensitivity        0.958  (0.798, 0.993) 
  Specificity        0.972  (0.858, 0.995) 
  PPV                0.958  (0.790, 0.993) 
  NPV                0.972  (0.864, 0.995) 
  Accuracy           0.967  (0.886, 0.991) 
  Prevalence         0.400 
  LR+                34.500  (4.986, 238.724) 
  LR-                0.043  (0.006, 0.292) 
  Odds Ratio         805.000  (47.921, 13522.837) 

  # PPV & NPV are based on user-specified prevalence of 0.400.
  # Confidence intervals for proportions use 'wilson' method.

PABAK
  new  vs  gold
  n = 60

  Kappa                 0.9333
  SE                    0.0463
  95% CI                (0.8425, 1.0000)
  Observed agreement    0.9667
  Expected agreement    0.5000
  # PABAK (prevalence = 0.500): 2 * p_obs - 1, assumes expected agreement = 0.5

McNemar Test
  new  vs  gold
  Discordant pairs:  b (pos, neg) = 1,  c (neg, pos) = 1
  n (b + c)        = 2

  Method: Exact binomial test
  P-value:          1.0000
  Conclusion: Not significant at 95% confidence level

13.4 示例二:由 TP/FP/TN/FN 直接构建

已有计数时无需原始数据:

代码
tab <- counts_to_table(
  tp = 80, fp = 5, tn = 120, fn = 3,
  candidate = "new method",
  reference = "gold standard"
)
tab

Fourfold Table
  Source            : counts
  Total n           : 208
  Candidate         : new method
  Reference         : gold standard

  2x2 Contingency Table
  ----------------------------------------------- 
                     gold standard              
                         positive  negative  Sum       
  ----------------------------------------------- 
  new method   positive  80        5         85        
               negative  3         120       123       
               sum       83        125       208       
  ----------------------------------------------- 
代码
tab <- diagnostics(tab)

Diagnostic Performance Summary
  Sensitivity        0.964  (0.899, 0.988) 
  Specificity        0.960  (0.910, 0.983) 
  PPV                0.941  (0.870, 0.975) 
  NPV                0.976  (0.931, 0.992) 
  Accuracy           0.962  (0.926, 0.980) 
  Prevalence         0.399  (0.335, 0.467) 
  LR+                24.096  (10.198, 56.934) 
  LR-                0.038  (0.012, 0.114) 
  Odds Ratio         640.000  (148.773, 2753.180) 

  # PPV & NPV are based on data prevalence of 0.399.
  # Confidence intervals for proportions use 'wilson' method.
代码
tab <- kappa(tab)

Cohen's Kappa
  new method  vs  gold standard
  n = 208

  Kappa                 0.9201
  SE                    0.0277
  95% CI                (0.8659, 0.9744)
  Observed agreement    0.9615
  Expected agreement    0.5184
代码
tab <- mcnemar(tab)

McNemar Test
  new method  vs  gold standard
  Discordant pairs:  b (pos, neg) = 5,  c (neg, pos) = 3
  n (b + c)        = 8

  Method: Exact binomial test
  P-value:          0.7266
  Conclusion: Not significant at 95% confidence level
代码
summary(tab)

Qualitative Analysis - summary
-------------------------------------------------

Fourfold Table
  Source            : counts
  Total n           : 208
  Candidate         : new method
  Reference         : gold standard

  2x2 Contingency Table
  ----------------------------------------------- 
                     gold standard              
                         positive  negative  Sum       
  ----------------------------------------------- 
  new method   positive  80        5         85        
               negative  3         120       123       
               sum       83        125       208       
  ----------------------------------------------- 

Analyses performed:

  [ ] Describe
  [x] Diagnostics
  [x] Kappa
  [x] McNemar

Diagnostic Performance Summary
  Sensitivity        0.964  (0.899, 0.988) 
  Specificity        0.960  (0.910, 0.983) 
  PPV                0.941  (0.870, 0.975) 
  NPV                0.976  (0.931, 0.992) 
  Accuracy           0.962  (0.926, 0.980) 
  Prevalence         0.399  (0.335, 0.467) 
  LR+                24.096  (10.198, 56.934) 
  LR-                0.038  (0.012, 0.114) 
  Odds Ratio         640.000  (148.773, 2753.180) 

  # PPV & NPV are based on data prevalence of 0.399.
  # Confidence intervals for proportions use 'wilson' method.

Cohen's Kappa
  new method  vs  gold standard
  n = 208

  Kappa                 0.9201
  SE                    0.0277
  95% CI                (0.8659, 0.9744)
  Observed agreement    0.9615
  Expected agreement    0.5184

McNemar Test
  new method  vs  gold standard
  Discordant pairs:  b (pos, neg) = 5,  c (neg, pos) = 3
  n (b + c)        = 8

  Method: Exact binomial test
  P-value:          0.7266
  Conclusion: Not significant at 95% confidence level
代码
describe(tab)   # counts 来源无原始数据,describe() 不可用

counts_to_table() 构建的对象没有原始数据,因此 describe() 不可用;diagnostics()kappa()mcnemar() 均正常。

13.5 示例三:定量数据转定性

13.5.1 读取数据

候选方法为定量结果 result,参考为定性 gold

代码
cont <- read_csv("./data/qualitative-continuous.csv", show_col_types = FALSE)
cont <- as.data.frame(cont)
str(cont)
'data.frame':   50 obs. of  3 variables:
 $ id    : chr  "S001" "S002" "S003" "S004" ...
 $ result: num  10.11 13.06 1.53 7.78 4.98 ...
 $ gold  : chr  "positive" "positive" "negative" "negative" ...

13.5.2 转换为二分类

按 cutoff = 8 转二分类:

代码
cont_bin <- continuous_to_binary(cont, cols = "result", cutoff = 8)
head(cont_bin)

continuous_to_binary() 生成 result_binary 列(≥ cutoff 为 1,否则 0)。为满足 raw_to_table()positive 映射要求,先重标为与参考一致的标签:

代码
cont_bin$result_binary_lab <- ifelse(cont_bin$result_binary == 1, "positive", "negative")

13.5.3 构建四格表并分析

代码
qa3 <- raw_to_table(
  cont_bin,
  candidate = "result_binary_lab",
  reference = "gold",
  id = "id",
  positive = "positive"
)
qa3 <- diagnostics(qa3)

Diagnostic Performance Summary
  Sensitivity        0.952  (0.773, 0.992) 
  Specificity        0.897  (0.736, 0.964) 
  PPV                0.870  (0.679, 0.955) 
  NPV                0.963  (0.817, 0.993) 
  Accuracy           0.920  (0.812, 0.968) 
  Prevalence         0.420  (0.294, 0.558) 
  LR+                9.206  (3.140, 26.994) 
  LR-                0.053  (0.008, 0.361) 
  Odds Ratio         173.333  (16.746, 1794.100) 

  # PPV & NPV are based on data prevalence of 0.420.
  # Confidence intervals for proportions use 'wilson' method.
代码
qa3$diagnostics[c("sensitivity", "specificity", "accuracy")]
$sensitivity
      est     lower     upper 
0.9523810 0.7733064 0.9915440 

$specificity
      est     lower     upper 
0.8965517 0.7361492 0.9641851 

$accuracy
      est     lower     upper 
0.9200000 0.8116175 0.9684505 

注意: positive 指定的水平必须同时存在于两列中,因此先把二值列重标为 positive/negative。 若不指定 positive,函数可能按排序水平把阳性/阴性方向颠倒,导致 TP/FP 翻转,务必核对。

13.6 结果判读要点

  1. 方向一致:确认候选与参考的阳性方向定义一致,避免四格表整体翻转。
  2. 水平唯一:候选/参考列都必须恰好 2 个水平;多水平或连续列需先转二分类。
  3. 患病率:PPV/NPV 依赖患病率,报告数据患病率或指定目标人群患病率。
  4. 方法标注:Kappa 与 PABAK 含义不同;McNemar 在小样本不一致对时用精确检验,报告方法。
  5. 阈值记录:定量转定性必须记录 cutoff 与阳性方向,结果只在该阈值下成立。