Regression Calibration with an External Validation Study

Introduction

This vignette demonstrates the use of the deattenuation factor and substitution methods implemented in the RegCalib package. The example uses a simulated main-study dataset and a simulated external-validation dataset.

Load the package and example data

library(RegCalib)

data("main_data_sim", package = "RegCalib")
data("valid_data_sim", package = "RegCalib")

head(main_data_sim, 3)
#>       id fqcal fqcalinc fqtfat fqtfatinc  fqalc  fqalcinc      age  agec case
#> 1 100002  2387  2.98375     87       8.7 22.341 1.8617500 43.08333 40~45    0
#> 2 100015  1763  2.20375     69       6.9  3.781 0.3150833 47.08333 45~50    0
#> 3 100018  1196  1.49500     55       5.5  0.000 0.0000000 59.75000  >=55    0
head(valid_data_sim, 3)
#>       id  fqcal fqcalinc fqtfat fqtfatinc fqalc fqalcinc drcal drcalinc drtfat
#> 1 100155 1242.5 1.553125   55.2      5.52 61.53   5.1275  1650  2.06250  63.41
#> 2 100325 1181.3 1.476625   38.5      3.85 15.96   1.3300  1733  2.16625  66.62
#> 3 100342  915.8 1.144750   48.3      4.83  5.64   0.4700  1384  1.73000  64.97
#>   drtfatinc dralc  dralcinc age  agec
#> 1     6.341 66.84 5.5700000  49 45~50
#> 2     6.662 13.52 1.1266667  39   <40
#> 3     6.497  4.61 0.3841667  52 50~55

To keep the vignette fast enough to build during package checking, this example uses a representative subset of the main-study data. Replace main_example with main_data_sim to run the analysis using the complete dataset.

set.seed(2026)

rows_by_outcome <- split(
  seq_len(nrow(main_data_sim)),
  main_data_sim$case
)

example_rows <- unlist(
  lapply(
    rows_by_outcome,
    function(rows) sample(rows, min(length(rows), 2500L))
  ),
  use.names = FALSE
)

main_example <- main_data_sim[example_rows, , drop = FALSE]

table(main_example$case)
#> 
#>    0    1 
#> 2500  598

Deattenuation factor method

The deattenuation factor method corrects the outcome-model coefficients using information estimated from the external-validation study.

rcdf <- RegCalibDF(
  supplyEstimates = FALSE,
  ms = main_example,
  vs = valid_data_sim,
  sur = c("fqtfatinc", "fqcalinc", "fqalcinc"),
  exp = c("drtfatinc", "drcalinc", "dralcinc"),
  covCalib = "agec",
  covOutcomePlus = NULL,
  outcome = "case",
  method = "glm",
  family = binomial,
  link = "logit",
  external = TRUE,
  pointEstimates = NA,
  vcovEstimates = NA
)

rcdf$correctedCoefTable
#>              Estimate Std. Error     Z Value     Pr(>|Z|)  lower 95%CI
#> drtfatinc  0.01096000 0.07442649  0.14725944 8.829272e-01 -0.134913236
#> drcalinc  -0.03649072 0.42422570 -0.08601723 9.314527e-01 -0.867957806
#> dralcinc   0.23735858 0.07784217  3.04922884 2.294296e-03  0.084790735
#> agec40~45 -0.07856939 0.17218806 -0.45629986 6.481744e-01 -0.416051783
#> agec45~50  0.31506121 0.16292850  1.93373913 5.314521e-02 -0.004272776
#> agec50~55  0.70180110 0.15834708  4.43204324 9.334429e-06  0.391446527
#> agec>=55   0.91666807 0.16457768  5.56982025 2.550023e-08  0.594101753
#>           upper 95%CI
#> drtfatinc   0.1568332
#> drcalinc    0.7949764
#> dralcinc    0.3899264
#> agec40~45   0.2589130
#> agec45~50   0.6343952
#> agec50~55   1.0121557
#> agec>=55    1.2392344

Because this example uses a logistic regression model, the corrected coefficients and confidence limits can be exponentiated and interpreted as odds ratios.

corrected_RC_DF <- exp(
  cbind(
    OR = rcdf$correctedCoefTable[, 1],
    "2.5 %" = rcdf$correctedCoefTable[, 5],
    "97.5 %" = rcdf$correctedCoefTable[, 6]
  )
)

corrected_RC_DF
#>                  OR     2.5 %   97.5 %
#> drtfatinc 1.0110203 0.8737917 1.169801
#> drcalinc  0.9641670 0.4198080 2.214389
#> dralcinc  1.2678957 1.0884893 1.476872
#> agec40~45 0.9244379 0.6596461 1.295521
#> agec45~50 1.3703432 0.9957363 1.885881
#> agec50~55 2.0173829 1.4791188 2.751526
#> agec>=55  2.5009435 1.8114031 3.452969

Substitution method

The substitution method first predicts the error-prone exposures using the calibration models and then fits the outcome model using the calibrated values.

rcsub <- RegCalibSub(
  ms = main_example,
  vs = valid_data_sim,
  sur = c("fqtfatinc", "fqcalinc", "fqalcinc"),
  exp = c("drtfatinc", "drcalinc", "dralcinc"),
  covCalib = "agec",
  covOutcome = "agec",
  outcome = "case",
  method = "glm",
  family = binomial,
  link = "logit",
  external = TRUE
)

rcsub$correctedCoefTable
#>              Estimate Std. Error     Z Value     Pr(>|Z|)  lower 95%CI
#> drtfatinc  0.01096000 0.07424986  0.14760975 8.826508e-01 -0.134567043
#> drcalinc  -0.03649072 0.43370947 -0.08413632 9.329480e-01 -0.886545666
#> dralcinc   0.23735858 0.07932095  2.99238173 2.768099e-03  0.081892361
#> agec40~45 -0.07856939 0.17304416 -0.45404242 6.497983e-01 -0.417729702
#> agec45~50  0.31506121 0.16414059  1.91945953 5.492620e-02 -0.006648426
#> agec50~55  0.70180110 0.15798621  4.44216674 8.905750e-06  0.392153811
#> agec>=55   0.91666807 0.16451839  5.57182731 2.520812e-08  0.594217946
#>           upper 95%CI
#> drtfatinc   0.1564870
#> drcalinc    0.8135642
#> dralcinc    0.3928248
#> agec40~45   0.2605909
#> agec45~50   0.6367708
#> agec50~55   1.0114484
#> agec>=55    1.2391182

The corrected coefficients and confidence limits are exponentiated to obtain odds ratios.

corrected_RC_SUB <- exp(
  cbind(
    OR = rcsub$correctedCoefTable[, 1],
    "2.5 %" = rcsub$correctedCoefTable[, 5],
    "97.5 %" = rcsub$correctedCoefTable[, 6]
  )
)

corrected_RC_SUB
#>                  OR     2.5 %   97.5 %
#> drtfatinc 1.0110203 0.8740943 1.169396
#> drcalinc  0.9641670 0.4120767 2.255934
#> dralcinc  1.2678957 1.0853390 1.481159
#> agec40~45 0.9244379 0.6585402 1.297697
#> agec45~50 1.3703432 0.9933736 1.890367
#> agec50~55 2.0173829 1.4801654 2.749581
#> agec>=55  2.5009435 1.8116136 3.452568

Access the corrected variance-covariance matrices

rcdf$correctedVCOV
#>              drtfatinc     drcalinc      dralcinc    agec40~45    agec45~50
#> drtfatinc  0.005539302 -0.022798512  0.0023611997  0.001171225  0.001505908
#> drcalinc  -0.022798512  0.179967442 -0.0211201493 -0.002413373  0.003655370
#> dralcinc   0.002361200 -0.021120149  0.0060594027 -0.000938982 -0.001580540
#> agec40~45  0.001171225 -0.002413373 -0.0009389820  0.029648728  0.015307394
#> agec45~50  0.001505908  0.003655370 -0.0015805398  0.015307394  0.026545695
#> agec50~55  0.002410917 -0.007106101 -0.0003886458  0.015330079  0.015682484
#> agec>=55   0.002947009 -0.005725809 -0.0008994323  0.015632081  0.016311025
#>               agec50~55      agec>=55
#> drtfatinc  0.0024109167  0.0029470087
#> drcalinc  -0.0071061007 -0.0057258095
#> dralcinc  -0.0003886458 -0.0008994323
#> agec40~45  0.0153300795  0.0156320806
#> agec45~50  0.0156824845  0.0163110246
#> agec50~55  0.0250737971  0.0163271538
#> agec>=55   0.0163271538  0.0270858112
rcsub$correctedVCOV
#>              drtfatinc     drcalinc      dralcinc     agec40~45    agec45~50
#> drtfatinc  0.005513041 -0.023380338  0.0025481721  0.0015708363  0.001371845
#> drcalinc  -0.023380338  0.188103907 -0.0227070146 -0.0022155439  0.006421093
#> dralcinc   0.002548172 -0.022707015  0.0062918138 -0.0008624052 -0.001621142
#> agec40~45  0.001570836 -0.002215544 -0.0008624052  0.0299442802  0.015700015
#> agec45~50  0.001371845  0.006421093 -0.0016211421  0.0157000150  0.026942132
#> agec50~55  0.002319148 -0.006826565 -0.0000761093  0.0155776483  0.015664263
#> agec>=55   0.002921830 -0.005994030 -0.0006373472  0.0160326730  0.016371705
#>               agec50~55      agec>=55
#> drtfatinc  0.0023191485  0.0029218296
#> drcalinc  -0.0068265652 -0.0059940299
#> dralcinc  -0.0000761093 -0.0006373472
#> agec40~45  0.0155776483  0.0160326730
#> agec45~50  0.0156642625  0.0163717046
#> agec50~55  0.0249596432  0.0162546507
#> agec>=55   0.0162546507  0.0270663013