Author: Marcello Grassi

# Deming Regression for Method Comparison

#### Marcello Grassi

## Introduction

Deming regression is a method for fitting a linear relationship when both variables are measured with error. Unlike ordinary least squares (OLS), which assumes the independent variable is measured without error, Deming regression accounts for measurement uncertainty in both the reference and test methods. This makes it particularly appropriate for method comparison studies in clinical laboratories.

This vignette introduces the theory behind Deming regression, demonstrates its use with the `valytics` package, and provides guidance on when to choose Deming regression over alternatives like Passing-Bablok regression.

    library(valytics)
    library(ggplot2)

## The Problem with Ordinary Least Squares

When comparing two analytical methods, a common approach is to regress the test method (Y) on the reference method (X) using OLS. However, OLS assumes that X is measured without error — an assumption that rarely holds in practice.

When both X and Y contain measurement error, OLS produces biased estimates:

- The slope is attenuated (biased toward zero)
- The intercept is biased away from the true value

This phenomenon, known as regression dilution or attenuation bias, can lead to incorrect conclusions about method agreement.

    set.seed(42)

    # True relationship: Y = 1.0 * X (perfect agreement)
    true_values <- seq(50, 150, length.out = 100)

    # Both methods have measurement error
    x_observed <- true_values + rnorm(100, sd = 10)
    y_observed <- true_values + rnorm(100, sd = 10)

    # Compare OLS vs Deming
    ols_fit <- lm(y_observed ~ x_observed)
    dm_fit <- deming_regression(x_observed, y_observed)

    # Visualize
    df <- data.frame(x = x_observed, y = y_observed)

    ggplot(df, aes(x = x, y = y)) +
      geom_point(alpha = 0.5) +
      geom_abline(intercept = 0, slope = 1, linetype = "solid",
                  color = "gray50", linewidth = 1) +
      geom_abline(intercept = coef(ols_fit)[1], slope = coef(ols_fit)[2],
                  color = "red", linewidth = 0.8) +
      geom_abline(intercept = dm_fit$results$intercept, slope = dm_fit$results$slope,
                  color = "blue", linewidth = 0.8) +
      annotate("text", x = 60, y = 145, label = "True (slope = 1)", color = "gray30") +
      annotate("text", x = 60, y = 138,
               label = sprintf("OLS (slope = %.3f)", coef(ols_fit)[2]), color = "red") +
      annotate("text", x = 60, y = 131,
               label = sprintf("Deming (slope = %.3f)", dm_fit$results$slope), color = "blue") +
      labs(title = "Attenuation Bias in OLS Regression",
           x = "Method X", y = "Method Y") +
      theme_minimal()

Demonstration of attenuation bias: OLS slope is attenuated when X has measurement error.

Notice how the OLS slope is attenuated (less than 1), while Deming regression recovers a slope closer to the true value of 1.

## Deming Regression Theory

Deming regression minimizes the sum of squared perpendicular distances from points to the regression line, weighted by the error variance ratio. The model assumes:

\[Y_i = \alpha + \beta X_i^* + \epsilon_i\] \[X_i = X_i^* + \delta_i\]

where \(X_i^*\) is the true (unobserved) value, and \(\epsilon_i\) and \(\delta_i\) are measurement errors in Y and X respectively.

### The Error Ratio (λ)

The key parameter in Deming regression is the error ratio (lambda, λ):

\[\lambda = \frac{\text{Var}(\epsilon)}{\text{Var}(\delta)} = \frac{\text{Var(error in Y)}}{\text{Var(error in X)}}\]

When λ = 1, both methods have equal error variance, and Deming regression becomes orthogonal regression (also called total least squares). This minimizes perpendicular distances to the line.

### Choosing the Error Ratio

The error ratio can be determined by:

1. Assuming equal precision (λ = 1): Appropriate when both methods have similar analytical performance
2. Using CV ratios: \(\lambda = (CV_Y / CV_X)^2\) when CVs are known from validation studies
3. Using replicate data: Estimate variance components from replicate measurements

## Basic Usage

The `deming_regression()` function follows the same interface as other `valytics` functions:

    # Load example data
    data("glucose_methods")

    # Deming regression with default settings (lambda = 1)
    dm <- deming_regression(reference ~ poc_meter, data = glucose_methods)
    dm
    #>
    #> Deming Regression
    #> ----------------------------------------
    #> n = 60 paired observations
    #>
    #> Error ratio (lambda): 1.000
    #> CI method: Jackknife
    #> Confidence level: 95%
    #>
    #> Regression equation:
    #>   reference = -4.481 + 0.991 * poc_meter
    #>
    #> Results:
    #>   Intercept: -4.481 (SE = 3.074)
    #>     95% CI: [-10.635, 1.673]
    #>     (includes 0: no significant constant bias)
    #>
    #>   Slope: 0.991 (SE = 0.027)
    #>     95% CI: [0.936, 1.046]
    #>     (includes 1: no significant proportional bias)

The output shows:

- Error ratio: The λ value used (1 = orthogonal regression)
- Regression equation: The fitted line
- Slope and intercept: Point estimates with confidence intervals
- Interpretation: Whether CIs include the null values (0 for intercept, 1 for slope)

## Detailed Results

The `summary()` method provides comprehensive output:

    summary(dm)
    #>
    #> Deming Regression - Detailed Summary
    #> ==================================================
    #>
    #> Data:
    #>   X variable: poc_meter
    #>   Y variable: reference
    #>   Sample size: 60
    #>
    #> Settings:
    #>   Error ratio (lambda): 1.0000
    #>     (orthogonal regression - equal error variances assumed)
    #>   Confidence level: 95%
    #>   CI method: Jackknife
    #>
    #> Regression Coefficients:
    #> --------------------------------------------------
    #>           Estimate Std. Error 95% Lower 95% Upper
    #> Intercept  -4.4807     3.0744  -10.6349    1.6734
    #> Slope       0.9911     0.0273    0.9364    1.0457
    #>
    #> Regression equation:
    #>   reference = -4.4807 + 0.9911 * poc_meter
    #>
    #> Interpretation:
    #> --------------------------------------------------
    #>   Intercept: CI includes 0
    #>     -> No significant constant (additive) bias
    #>   Slope: CI includes 1
    #>     -> No significant proportional (multiplicative) bias
    #>
    #> Conclusion:
    #> --------------------------------------------------
    #>   The two methods are EQUIVALENT within the measured range.
    #>   No systematic differences detected.
    #>
    #> Residuals (perpendicular):
    #> --------------------------------------------------
    #>     Min.  1st Qu.   Median     Mean  3rd Qu.     Max.
    #> -17.3800  -2.7747  -0.3476   0.0000   2.7091  17.8990
    #>
    #> Note on error ratio:
    #> --------------------------------------------------
    #>   Using lambda = 1 (orthogonal regression).
    #>   This assumes both methods have equal measurement error variance.
    #>   If this assumption is violated, consider specifying 'error_ratio'
    #>   based on replicate measurements or known precision data.

## Visualization

The `plot()` method creates publication-ready figures:

    plot(dm)

Deming regression scatter plot with confidence band.

### Residual Diagnostics

Examine residuals to check model assumptions:

    plot(dm, type = "residuals")

Residual plot for assessing model fit.

Look for:

- Random scatter around zero: Good model fit
- Trends: Suggest non-linearity
- Funnel shape: Suggest heteroscedasticity (non-constant variance)

## Specifying the Error Ratio

When methods have different precision, specify the error ratio:

    # If POC meter has twice the error variance of the reference
    dm_lambda2 <- deming_regression(
      reference ~ poc_meter,
      data = glucose_methods,
      error_ratio = 2
    )

    dm_lambda2
    #>
    #> Deming Regression
    #> ----------------------------------------
    #> n = 60 paired observations
    #>
    #> Error ratio (lambda): 2.000
    #> CI method: Jackknife
    #> Confidence level: 95%
    #>
    #> Regression equation:
    #>   reference = -4.192 + 0.989 * poc_meter
    #>
    #> Results:
    #>   Intercept: -4.192 (SE = 3.063)
    #>     95% CI: [-10.323, 1.939]
    #>     (includes 0: no significant constant bias)
    #>
    #>   Slope: 0.989 (SE = 0.027)
    #>     95% CI: [0.935, 1.043]
    #>     (includes 1: no significant proportional bias)

### Estimating λ from CVs

If you know the coefficients of variation from method validation:

    # Example: Reference CV = 2.5%, POC CV = 4.5%
    cv_reference <- 0.025
    cv_poc <- 0.045

    lambda_estimated <- (cv_poc / cv_reference)^2
    # lambda_estimated = 3.24

    dm_cv <- deming_regression(
      reference ~ poc_meter,
      data = glucose_methods,
      error_ratio = lambda_estimated
    )

## Confidence Interval Methods

Two methods are available for computing confidence intervals:

### Jackknife (Default)

The jackknife method, following Linnet (1990), provides robust standard error estimates:

    dm_jack <- deming_regression(
      reference ~ poc_meter,
      data = glucose_methods,
      ci_method = "jackknife"
    )

    # Standard errors are available
    cat("Slope SE:", round(dm_jack$results$slope_se, 4), "\n")
    #> Slope SE: 0.0273
    cat("Intercept SE:", round(dm_jack$results$intercept_se, 4), "\n")
    #> Intercept SE: 3.0744

### Bootstrap BCa

For smaller samples or when parametric assumptions are questionable:

    dm_boot <- deming_regression(
      reference ~ poc_meter,
      data = glucose_methods,
      ci_method = "bootstrap",
      boot_n = 1999
    )

## Comparison with Passing-Bablok

Both Deming and Passing-Bablok regression are appropriate for method comparison, but they have different characteristics:

Aspect | Deming | Passing-Bablok
--- | --- | ---
Approach | Parametric | Non-parametric
Error assumption | Normally distributed | Distribution-free
Outlier sensitivity | Sensitive | Robust
Error ratio | User-specified (λ) | Implicitly assumes equal
Sample size | Works with smaller n | Needs ~30+ for stable CIs
Ties | Handles ties | Can be affected by ties

### When to Use Deming

- You have knowledge about the error ratio (from validation data)
- Data are approximately normally distributed
- No severe outliers
- Smaller sample sizes (n < 30)

### When to Use Passing-Bablok

- Error structure is unknown
- Potential outliers in the data
- Non-normal error distributions
- Larger sample sizes available

### Side-by-Side Comparison

    # Fit both models
    dm <- deming_regression(reference ~ poc_meter, data = glucose_methods)
    pb <- pb_regression(reference ~ poc_meter, data = glucose_methods)

    # Compare coefficients
    comparison <- data.frame(
      Method = c("Deming", "Passing-Bablok"),
      Slope = c(dm$results$slope, pb$results$slope),
      Slope_Lower = c(dm$results$slope_ci["lower"], pb$results$slope_ci["lower"]),
      Slope_Upper = c(dm$results$slope_ci["upper"], pb$results$slope_ci["upper"]),
      Intercept = c(dm$results$intercept, pb$results$intercept),
      Int_Lower = c(dm$results$intercept_ci["lower"], pb$results$intercept_ci["lower"]),
      Int_Upper = c(dm$results$intercept_ci["upper"], pb$results$intercept_ci["upper"])
    )

    print(comparison, digits = 3)
    #>           Method Slope Slope_Lower Slope_Upper Intercept Int_Lower Int_Upper
    #> 1         Deming 0.991       0.936       1.046     -4.48    -10.63      1.67
    #> 2 Passing-Bablok 0.973       0.963       0.985     -2.79     -4.32     -1.91

    # Visual comparison
    ggplot(glucose_methods, aes(x = reference, y = poc_meter)) +
      geom_point(alpha = 0.6) +
      geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray50") +
      geom_abline(intercept = dm$results$intercept, slope = dm$results$slope,
                  color = "#2166AC", linewidth = 1) +
      geom_abline(intercept = pb$results$intercept, slope = pb$results$slope,
                  color = "#B2182B", linewidth = 1) +
      annotate("text", x = 80, y = 340, label = "Identity", color = "gray50") +
      annotate("text", x = 80, y = 320, label = "Deming", color = "#2166AC") +
      annotate("text", x = 80, y = 300, label = "Passing-Bablok", color = "#B2182B") +
      labs(title = "Regression Method Comparison",
           x = "Reference (mg/dL)",
           y = "POC Meter (mg/dL)") +
      theme_minimal()

Visual comparison of Deming and Passing-Bablok regression lines.

## Complete Workflow Example

Here is a complete method comparison workflow using the creatinine dataset:

    # Load data
    data("creatinine_serum")

    # 1. Deming regression
    dm <- deming_regression(enzymatic ~ jaffe, data = creatinine_serum)

    # 2. View summary
    summary(dm)
    #>
    #> Deming Regression - Detailed Summary
    #> ==================================================
    #>
    #> Data:
    #>   X variable: jaffe
    #>   Y variable: enzymatic
    #>   Sample size: 80
    #>
    #> Settings:
    #>   Error ratio (lambda): 1.0000
    #>     (orthogonal regression - equal error variances assumed)
    #>   Confidence level: 95%
    #>   CI method: Jackknife
    #>
    #> Regression Coefficients:
    #> --------------------------------------------------
    #>           Estimate Std. Error 95% Lower 95% Upper
    #> Intercept  -0.2908     0.0323   -0.3550   -0.2265
    #> Slope       1.0488     0.0157    1.0175    1.0801
    #>
    #> Regression equation:
    #>   enzymatic = -0.2908 + 1.0488 * jaffe
    #>
    #> Interpretation:
    #> --------------------------------------------------
    #>   Intercept: CI excludes 0 (-0.355 to -0.227)
    #>     -> Significant negative constant bias of -0.291
    #>   Slope: CI excludes 1 (1.018 to 1.080)
    #>     -> Significant proportional bias of 4.9%
    #>
    #> Conclusion:
    #> --------------------------------------------------
    #>   The two methods show SYSTEMATIC DIFFERENCES:
    #>     - Constant bias: 0.291 enzymatic
    #>     - Proportional bias: 4.9%
    #>
    #> Residuals (perpendicular):
    #> --------------------------------------------------
    #>      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.
    #> -0.390154 -0.039726  0.008273  0.000000  0.059742  0.475833
    #>
    #> Note on error ratio:
    #> --------------------------------------------------
    #>   Using lambda = 1 (orthogonal regression).
    #>   This assumes both methods have equal measurement error variance.
    #>   If this assumption is violated, consider specifying 'error_ratio'
    #>   based on replicate measurements or known precision data.

    # 3. Create visualization
    plot(dm, title = "Creatinine: Jaffe vs Enzymatic Method")

Complete Deming regression analysis for creatinine methods.

    # 4. Check residuals
    plot(dm, type = "residuals")

Residual diagnostics for creatinine comparison.

## Extracting Results

For further analysis or reporting, extract components from the result object:

    # Coefficients
    slope <- dm$results$slope
    intercept <- dm$results$intercept

    # Confidence intervals
    slope_ci <- dm$results$slope_ci
    intercept_ci <- dm$results$intercept_ci

    # Standard errors
    slope_se <- dm$results$slope_se
    intercept_se <- dm$results$intercept_se

    # Formatted output for reporting
    cat(sprintf("Slope: %.4f (95%% CI: %.4f to %.4f)\n",
                slope, slope_ci["lower"], slope_ci["upper"]))
    #> Slope: 1.0488 (95% CI: 1.0175 to 1.0801)
    cat(sprintf("Intercept: %.4f (95%% CI: %.4f to %.4f)\n",
                intercept, intercept_ci["lower"], intercept_ci["upper"]))
    #> Intercept: -0.2908 (95% CI: -0.3550 to -0.2265)

## References

Cornbleet PJ, Gochman N. Incorrect least-squares regression coefficients in method-comparison analysis. Clinical Chemistry. 1979;25(3):432-438.

Linnet K. Estimation of the linear relationship between the measurements of two methods with proportional errors. Statistics in Medicine. 1990;9(12):1463-1473.

Linnet K. Evaluation of regression procedures for methods comparison studies. Clinical Chemistry. 1993;39(3):424-432.

Deming WE. Statistical Adjustment of Data. Wiley; 1943.