Validate Statistical Output
Verify statistical analysis results through independent calculation and systematic comparison.
When to Use
- •Validating primary and secondary endpoint analyses for regulatory submissions
- •Performing double programming (R vs SAS, or independent R implementations)
- •Verifying that analysis code produces correct results
- •Re-validating after code or environment changes
Inputs
- •Required: Primary analysis code and results
- •Required: Reference results (independent calculation, published values, or known test data)
- •Required: Tolerance criteria for numeric comparisons
- •Optional: Regulatory submission context
Procedure
Step 1: Define Comparison Framework
r
# Define tolerance levels for different statistics tolerances <- list( counts = 0, # Exact match for integers proportions = 1e-4, # 0.01% for proportions means = 1e-6, # Numeric precision for means p_values = 1e-4, # 4 decimal places for p-values confidence_limits = 1e-3 # 3 decimal places for CIs )
Step 2: Create Comparison Function
r
#' Compare two result sets with tolerance-based matching
#'
#' @param primary Results from the primary analysis
#' @param reference Results from the independent calculation
#' @param tolerances Named list of tolerance values
#' @return Data frame with comparison results
compare_results <- function(primary, reference, tolerances) {
stopifnot(names(primary) == names(reference))
comparison <- data.frame(
statistic = names(primary),
primary_value = unlist(primary),
reference_value = unlist(reference),
stringsAsFactors = FALSE
)
comparison$absolute_diff <- abs(comparison$primary_value - comparison$reference_value)
comparison$tolerance <- sapply(comparison$statistic, function(s) {
# Match to tolerance category or use default
tol <- tolerances[[s]]
if (is.null(tol)) tolerances$means # default tolerance
else tol
})
comparison$pass <- comparison$absolute_diff <= comparison$tolerance
comparison
}
Step 3: Implement Double Programming
Write an independent implementation that reaches the same results through different code:
r
# PRIMARY ANALYSIS (in R/primary_analysis.R)
primary_analysis <- function(data) {
model <- lm(endpoint ~ treatment + baseline + sex, data = data)
coefs <- summary(model)$coefficients
list(
treatment_estimate = coefs["treatmentActive", "Estimate"],
treatment_se = coefs["treatmentActive", "Std. Error"],
treatment_p = coefs["treatmentActive", "Pr(>|t|)"],
n_subjects = nobs(model),
r_squared = summary(model)$r.squared
)
}
# INDEPENDENT VERIFICATION (in validation/independent_analysis.R)
# Written by a different analyst or using different methodology
independent_analysis <- function(data) {
# Using matrix algebra instead of lm()
X <- model.matrix(~ treatment + baseline + sex, data = data)
y <- data$endpoint
beta <- solve(t(X) %*% X) %*% t(X) %*% y
residuals <- y - X %*% beta
sigma2 <- sum(residuals^2) / (nrow(X) - ncol(X))
var_beta <- sigma2 * solve(t(X) %*% X)
se <- sqrt(diag(var_beta))
t_stat <- beta["treatmentActive"] / se["treatmentActive"]
p_value <- 2 * pt(-abs(t_stat), df = nrow(X) - ncol(X))
list(
treatment_estimate = as.numeric(beta["treatmentActive"]),
treatment_se = se["treatmentActive"],
treatment_p = as.numeric(p_value),
n_subjects = nrow(data),
r_squared = 1 - sum(residuals^2) / sum((y - mean(y))^2)
)
}
Step 4: Run Comparison
r
# Execute both analyses
primary_results <- primary_analysis(study_data)
independent_results <- independent_analysis(study_data)
# Compare
comparison <- compare_results(primary_results, independent_results, tolerances)
# Report
cat("Validation Comparison Report\n")
cat("============================\n")
cat(sprintf("Date: %s\n", Sys.time()))
cat(sprintf("Overall: %s\n\n",
ifelse(all(comparison$pass), "ALL PASS", "DISCREPANCIES FOUND")))
print(comparison)
Step 5: Compare Against External Reference (SAS)
When comparing R output against SAS:
r
# Load SAS results (exported as CSV or from .sas7bdat) sas_results <- list( treatment_estimate = 1.2345, # From SAS PROC GLM output treatment_se = 0.3456, treatment_p = 0.0004, n_subjects = 200, r_squared = 0.4567 ) comparison <- compare_results(primary_results, sas_results, tolerances) # Known sources of difference between R and SAS: # - Default contrasts (R: treatment, SAS: GLM parameterization) # - Rounding of intermediate calculations # - Handling of missing values (na.rm vs listwise deletion)
Step 6: Document Results
Create a validation report:
r
# validation/output_comparison_report.R
sink("validation/output_comparison_report.txt")
cat("OUTPUT VALIDATION REPORT\n")
cat("========================\n")
cat(sprintf("Project: %s\n", project_name))
cat(sprintf("Date: %s\n", format(Sys.time())))
cat(sprintf("Primary Analyst: %s\n", primary_analyst))
cat(sprintf("Independent Analyst: %s\n", independent_analyst))
cat(sprintf("R Version: %s\n\n", R.version.string))
cat("COMPARISON RESULTS\n")
cat("------------------\n")
print(comparison, row.names = FALSE)
cat(sprintf("\nOVERALL VERDICT: %s\n",
ifelse(all(comparison$pass), "VALIDATED", "DISCREPANCIES - INVESTIGATION REQUIRED")))
cat("\nSESSION INFO\n")
print(sessionInfo())
sink()
Step 7: Handle Discrepancies
When results don't match:
- •Verify both implementations use the same input data (hash comparison)
- •Check for differences in NA handling
- •Compare intermediate calculations step by step
- •Document the root cause
- •Determine if the difference is acceptable (within tolerance) or requires code correction
Validation
- • Independent analysis produces results within tolerance
- • All comparison statistics are documented
- • Discrepancies (if any) are investigated and resolved
- • Input data integrity verified (hash match)
- • Tolerance criteria are pre-specified and justified
- • Validation report is complete and signed
Common Pitfalls
- •Same analyst writing both implementations: Double programming requires independent analysts for true validation
- •Sharing code between implementations: The independent version must not copy from the primary
- •Inappropriate tolerance: Too loose hides real errors; too strict flags floating-point noise
- •Ignoring systematic differences: Small consistent biases may indicate a real error even within tolerance
- •Not validating the validation: Verify the comparison code itself works correctly with known inputs
Related Skills
- •
setup-gxp-r-project- project structure for validated work - •
write-validation-documentation- protocol and report templates - •
implement-audit-trail- tracking the validation process itself - •
write-testthat-tests- automated test suites for ongoing validation