Measurement error correction in a continuous trial endpoint

Linda Nab

2021-12-01

This vignette shows how one can correct for the bias in the trial’s effect estimate using the R package mecor. We make use of external validation data. Suppose an endpoint in a trial is measured with error, i.e., the substitute endpoint \(Y^*\) instead of the reference endpoint \(Y\) is observed.

First, we simulate some example data for a trial composed out of two groups. For example, a placebo group (\(X = 0\)) and an active comparator (\(X = 1\)). The number of individuals included in the trial is set to 1000, 500 individuals in each group. Suppose the substitute endpoint \(Y^*\) is observed instead of \(Y\). Further, suppose that an external validation set of sample size 500 is available in which both \(Y^*\) and \(Y\) are measured.

# simulate the trial's data
X <- rep(c(0,1), 500)
Y <- 2 * X + rnorm(1000, 0, 1) # estimand: 2
# introduce measurement error
Y_star <- 1.1 * Y + rnorm(1000, 0, 1)
trial <- cbind.data.frame(X = X, Y_star = Y_star)
# simulate an external validation data set
Y <- rnorm(100, 2, 1)
Y_star <- 1.1 * Y + rnorm(500, 0, 1)
trial_ext <- cbind.data.frame(Y = Y, Y_star = Y_star)

When the error is ignored, one would estimate the trial’s effect by regressing \(X\) on \(Y^*\).

# uncorrected estimate of the trial's effect:
uncor_fit <- lm(Y_star ~ X, data = trial)
uncor_fit$coefficients
#> (Intercept)           X 
#>   -0.101728    2.307088

As you might expect, the trial’s effect estimate does not equal 2, to which value the estimand was set when generating the data. To obtain an unbiased trial effect, measurement error correction is needed. First, we estimate the parameters of the measurement error model using our external validation data:

memod_fit <- lm(Y_star ~ Y, data = trial_ext)
memod_fit$coefficients
#> (Intercept)           Y 
#> -0.05710055  1.12903992

Then, mecor can be used to correct for the measurement error in the trial’s effect estimate as follows:

cor_fit <- mecor(MeasErrorExt(substitute = Y_star, model = memod_fit) ~ X,
                 data = trial,
                 method = "standard",
                 B = 0 # for bootstrap intervals, set to e.g. 999
                 )

Confidence intervals for the corrected estimate can be obtained by using the summary object:

summary(cor_fit, fieller = TRUE, zerovar = TRUE)
#> 
#> Call:
#> mecor(formula = MeasErrorExt(substitute = Y_star, model = memod_fit) ~ 
#>     X, data = trial, method = "standard", B = 0)
#> 
#> Coefficients Corrected Model:
#>              Estimate       SE SE (zerovar)
#> (Intercept) -0.039527 0.106485     0.059365
#> X            2.043407 0.115500     0.083954
#> 
#> 95% Confidence Intervals:
#>              Estimate       LCI      UCI LCI (zerovar) UCI (zerovar)
#> (Intercept) -0.039527 -0.248234 0.169181     -0.155879      0.076826
#> X            2.043407  1.817031 2.269783      1.878860      2.207954
#>             LCI (fieller) UCI (fieller)
#> (Intercept)            NA            NA
#> X                1.827951      2.282661
#> 
#> The measurement error is corrected for by application of method of moments 
#> 
#> Coefficients Uncorrected Model:
#>              Estimate Std. Error t value Pr(>|t|)
#> (Intercept) -0.101728   0.067025 -1.5178   0.1294
#> X            2.307088   0.094788 24.3395   <2e-16
#> 
#> 95% Confidence Intervals:
#>              Estimate       LCI      UCI
#> (Intercept) -0.101728 -0.233254 0.029798
#> X            2.307088  2.121082 2.493094
#> 
#> Residual standard error: 1.498726 on 998 degrees of freedom

When there is no external validation data available. One could conduct a sensitivity analysis by making informed guesses about the parameters values of the measurement error model. Suppose e.g. we guess the following measurement error model: \(Y^* = 1.1 Y\). The following code can be used to quantify the impact of the measurement error would be on the trial’s effect estimate:

sens_fit <- mecor(MeasErrorExt(substitute = Y_star, 
                               model = list(coef = c(0, 1.1))) ~ X, 
                  data = trial,
                  method = "standard"
                  )
sens_fit
#> 
#> Call:
#> mecor(formula = MeasErrorExt(substitute = Y_star, model = list(coef = c(0, 
#>     1.1))) ~ X, data = trial, method = "standard")
#> 
#> Coefficients Corrected Model:
#> (Intercept)           X 
#> -0.09247997  2.09735276 
#> 
#> Coefficients Uncorrected Model:
#> (Intercept)           X 
#>   -0.101728    2.307088