AgentSkillsCN

validate-statistical-output

使用 Docker 容器化基于 R 的 MCP(模型上下文协议)服务器。涵盖 mcptools 集成、端口暴露、stdio 与 HTTP 传输方式的对比,以及将 Claude Code 与容器化服务器连接。

SKILL.md
--- frontmatter
name: validate-statistical-output
description: >
  Validate statistical analysis output through double programming,
  independent verification, and reference comparison. Covers
  comparison methodology, tolerance definitions, and deviation
  handling for regulated environments.
license: MIT
allowed-tools: Read Write Edit Bash Grep Glob
metadata:
  author: Philipp Thoss
  version: "1.0"
  domain: compliance
  complexity: advanced
  language: R
  tags: validation, statistics, double-programming, verification, pharma

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:

  1. Verify both implementations use the same input data (hash comparison)
  2. Check for differences in NA handling
  3. Compare intermediate calculations step by step
  4. Document the root cause
  5. 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