tests/testthat/test-verify_precision.R

# Tests for verify_precision() - Phase 4
# Precision verification against manufacturer claims

# Test Data Setup ----

#' Create simple test data for verification
#' @noRd
create_verification_data <- function(seed = 42, n = 25, mean_val = 100, sd = 3.5) {
  set.seed(seed)
  rnorm(n, mean = mean_val, sd = sd)
}


#' Create precision study data for verification testing
#' @noRd
create_prec_study_data <- function(seed = 42, mean_val = 100,
                                   sd_day = 1.5, sd_error = 2.5) {
  set.seed(seed)
  
  n_days <- 5
  n_reps <- 5
  
  data <- expand.grid(
    day = 1:n_days,
    replicate = 1:n_reps
  )
  
  day_effects <- rnorm(n_days, 0, sd_day)
  errors <- rnorm(nrow(data), 0, sd_error)
  
  day_effect <- day_effects[as.numeric(as.factor(data$day))]
  data$value <- mean_val + day_effect + errors
  
  data
}


# Input Validation Tests ----

test_that("verify_precision validates claimed values", {
  x <- create_verification_data()
  
  # Must provide at least one of claimed_cv or claimed_sd
  expect_error(
    verify_precision(x, mean_value = 100),
    "Either `claimed_cv` or `claimed_sd` must be provided"
  )
  
  # Invalid claimed_cv
  expect_error(
    verify_precision(x, claimed_cv = -5, mean_value = 100),
    "`claimed_cv` must be a single positive number"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 0, mean_value = 100),
    "`claimed_cv` must be a single positive number"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = c(3, 4), mean_value = 100),
    "`claimed_cv` must be a single positive number"
  )
  
  # Invalid claimed_sd
  expect_error(
    verify_precision(x, claimed_sd = -3, mean_value = 100),
    "`claimed_sd` must be a single positive number"
  )
  
  expect_error(
    verify_precision(x, claimed_sd = 0, mean_value = 100),
    "`claimed_sd` must be a single positive number"
  )
})


test_that("verify_precision validates alpha", {
  x <- create_verification_data()
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 0),
    "`alpha` must be a single number between 0 and 1"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 1),
    "`alpha` must be a single number between 0 and 1"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = -0.1),
    "`alpha` must be a single number between 0 and 1"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = c(0.05, 0.1)),
    "`alpha` must be a single number between 0 and 1"
  )
})


test_that("verify_precision validates conf_level", {
  x <- create_verification_data()
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, conf_level = 0),
    "`conf_level` must be a single number between 0 and 1"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, conf_level = 1),
    "`conf_level` must be a single number between 0 and 1"
  )
  
  expect_error(
    verify_precision(x, claimed_cv = 5, mean_value = 100, conf_level = 1.5),
    "`conf_level` must be a single number between 0 and 1"
  )
})


test_that("verify_precision validates numeric vector input", {
  # Non-numeric input
  expect_error(
    verify_precision("not numeric", claimed_cv = 5, mean_value = 100),
    "`x` must be a numeric vector"
  )
  
  # Too few observations
  expect_error(
    verify_precision(c(1, 2), claimed_cv = 5, mean_value = 100),
    "At least 3 observations are required"
  )
  
  # Empty vector after NA removal
  expect_error(
    verify_precision(rep(NA_real_, 5), claimed_cv = 5, mean_value = 100),
    "At least 3 observations are required"
  )
})


# Basic Functionality Tests ----

test_that("verify_precision returns correct class", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_s3_class(result, "verify_precision")
  expect_s3_class(result, "valytics_precision")
  expect_s3_class(result, "valytics_result")
})


test_that("verify_precision returns expected structure", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # Check main components
  expect_named(result, c("input", "observed", "claimed", "test",
                         "verification", "ci", "settings", "call"))
  
  # Check input
  expect_named(result$input, c("n", "df", "mean_value", "source"))
  expect_equal(result$input$n, 25)
  expect_equal(result$input$df, 24)
  
  # Check observed
  expect_named(result$observed, c("sd", "cv_pct", "variance"))
  expect_true(is.numeric(result$observed$sd))
  expect_true(is.numeric(result$observed$cv_pct))
  
  # Check claimed
  expect_named(result$claimed, c("sd", "cv_pct", "variance"))
  expect_equal(result$claimed$cv_pct, 5)
  
  # Check test
  expect_named(result$test, c("statistic", "df", "p_value",
                              "alternative", "method"))
  expect_true(is.numeric(result$test$statistic))
  expect_true(is.numeric(result$test$p_value))
  
  # Check verification
  expect_named(result$verification, c("verified", "ratio", "cv_ratio",
                                      "upper_verification_limit"))
  expect_true(is.logical(result$verification$verified))
  
  # Check CI
  expect_named(result$ci, c("variance_ci", "sd_ci", "cv_ci"))
  expect_named(result$ci$sd_ci, c("lower", "upper"))
})


test_that("verify_precision works with claimed_cv", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 3.5)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # Claimed CV should be as specified
  expect_equal(result$claimed$cv_pct, 5)
  
  # Claimed SD should be derived from CV and mean
  expect_equal(result$claimed$sd, 5)  # 5% of 100
  
  # Observed CV should be reasonable
  expect_true(result$observed$cv_pct > 0)
  expect_true(result$observed$cv_pct < 20)  # Reasonable bound
})


test_that("verify_precision works with claimed_sd", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 3.5)
  
  result <- verify_precision(x, claimed_sd = 5, mean_value = 100)
  
  # Claimed SD should be as specified
  expect_equal(result$claimed$sd, 5)
  
  # Claimed CV should be derived from SD and mean
  expect_equal(result$claimed$cv_pct, 5)  # 5/100 * 100%
})


test_that("verify_precision works with both claimed_cv and claimed_sd", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, claimed_sd = 4, mean_value = 100)
  
  # Both should be used as given
  expect_equal(result$claimed$cv_pct, 5)
  expect_equal(result$claimed$sd, 4)
})


# Verification Logic Tests ----

test_that("verify_precision correctly verifies when observed < claimed", {
  # Generate data with SD = 3, claim CV = 5%
  set.seed(123)
  x <- rnorm(30, mean = 100, sd = 3)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # Should be verified because observed CV is lower than claimed
  expect_true(result$verification$verified)
  expect_lt(result$observed$cv_pct, result$verification$upper_verification_limit)
})


test_that("verify_precision correctly fails when observed >> claimed", {
  # Generate data with SD = 8, claim CV = 3%
  set.seed(123)
  x <- rnorm(30, mean = 100, sd = 8)
  
  result <- verify_precision(x, claimed_cv = 3, mean_value = 100)
  
  # Should NOT be verified because observed CV is much higher than claimed
  expect_false(result$verification$verified)
  expect_gt(result$observed$cv_pct, result$verification$upper_verification_limit)
})


test_that("verify_precision upper verification limit is correct", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 4)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 0.05)
  
  # Calculate expected UVL manually
  df <- 24
  chi_sq_crit <- qchisq(0.95, df = df)
  claimed_var <- 25  # (5/100 * 100)^2
  uvl_var <- claimed_var * chi_sq_crit / df
  uvl_cv <- 100 * sqrt(uvl_var) / 100
  
  expect_equal(result$verification$upper_verification_limit, uvl_cv,
               tolerance = 0.001)
})


test_that("verify_precision chi-square statistic is correct", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 4)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # Calculate expected chi-square manually
  observed_var <- var(x)
  claimed_var <- 25  # (5/100 * 100)^2
  expected_chisq <- 24 * observed_var / claimed_var
  
  expect_equal(result$test$statistic, expected_chisq, tolerance = 0.001)
})


# Alternative Hypothesis Tests ----

test_that("verify_precision respects alternative argument", {
  set.seed(42)
  x <- rnorm(30, mean = 100, sd = 4)
  
  result_less <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                  alternative = "less")
  result_greater <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                     alternative = "greater")
  result_two <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                 alternative = "two.sided")
  
  # All should have different p-values (generally)
  expect_equal(result_less$test$alternative, "less")
  expect_equal(result_greater$test$alternative, "greater")
  expect_equal(result_two$test$alternative, "two.sided")
  
  # p-values should differ
  # For two-sided: p = 2 * min(p_less, p_greater)
  expect_true(result_two$test$p_value <= 1)
})


# Confidence Interval Tests ----

test_that("verify_precision confidence intervals are valid", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100, conf_level = 0.95)
  
  # Lower < observed < upper
  expect_lt(result$ci$sd_ci["lower"], result$observed$sd)
  expect_gt(result$ci$sd_ci["upper"], result$observed$sd)
  
  expect_lt(result$ci$cv_ci["lower"], result$observed$cv_pct)
  expect_gt(result$ci$cv_ci["upper"], result$observed$cv_pct)
  
  expect_lt(result$ci$variance_ci["lower"], result$observed$variance)
  expect_gt(result$ci$variance_ci["upper"], result$observed$variance)
})


test_that("verify_precision CI width changes with conf_level", {
  x <- create_verification_data()
  
  result_90 <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                conf_level = 0.90)
  result_95 <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                conf_level = 0.95)
  result_99 <- verify_precision(x, claimed_cv = 5, mean_value = 100,
                                conf_level = 0.99)
  
  # CI width should increase with conf_level
  width_90 <- diff(result_90$ci$sd_ci)
  width_95 <- diff(result_95$ci$sd_ci)
  width_99 <- diff(result_99$ci$sd_ci)
  
  expect_lt(width_90, width_95)
  expect_lt(width_95, width_99)
})


# Input Type Tests ----

test_that("verify_precision works with precision_study object", {
  skip_if_not_installed("valytics")
  
  data <- create_prec_study_data()
  prec <- precision_study(data, value = "value", day = "day")
  
  result <- verify_precision(prec, claimed_cv = 5)
  
  expect_s3_class(result, "verify_precision")
  expect_equal(result$input$source, "precision_study object")
  expect_true(result$input$n > 0)
})


test_that("verify_precision works with data frame input", {
  skip_if_not_installed("valytics")
  
  data <- create_prec_study_data()
  
  result <- verify_precision(
    data,
    claimed_cv = 5,
    value = "value",
    day = "day"
  )
  
  expect_s3_class(result, "verify_precision")
  expect_equal(result$input$source, "data frame (via precision_study)")
})


test_that("verify_precision handles NA values in numeric vector", {
  x <- c(rnorm(20, 100, 3.5), NA, NA, NA)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_equal(result$input$n, 20)
  expect_equal(result$input$df, 19)
})


test_that("verify_precision uses provided mean_value for CV calculation", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 4)
  
  # Override mean with a different value
  result <- verify_precision(x, claimed_cv = 5, mean_value = 200)
  
  # CV should be calculated using 200 as the mean
  expected_cv <- 100 * sd(x) / 200
  expect_equal(result$observed$cv_pct, expected_cv, tolerance = 0.001)
})


test_that("verify_precision calculates mean from data when not provided", {
  set.seed(42)
  x <- rnorm(25, mean = 100, sd = 4)
  
  # When using claimed_sd, mean can be inferred from data
  # But when using claimed_cv, we need mean to convert
  # Test with claimed_sd:
  result <- verify_precision(x, claimed_sd = 4)
  
  expect_equal(result$input$mean_value, mean(x), tolerance = 0.001)
})


# Print and Summary Tests ----

test_that("print.verify_precision runs without error", {
  x <- create_verification_data()
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_output(print(result), "Precision Verification")
  expect_output(print(result), "VERIFIED|NOT VERIFIED")
  expect_output(print(result), "Chi-square")
})


test_that("summary.verify_precision returns correct class", {
  x <- create_verification_data()
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  summ <- summary(result)
  
  expect_s3_class(summ, "summary.verify_precision")
  expect_named(summ, c("call", "input", "observed", "claimed", "test",
                       "verification", "ci", "settings"))
})


test_that("print.summary.verify_precision runs without error", {
  x <- create_verification_data()
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  summ <- summary(result)
  
  expect_output(print(summ), "Precision Verification - Detailed Summary")
  expect_output(print(summ), "Confidence Intervals")
  expect_output(print(summ), "Verification Outcome")
})


# Edge Cases ----

test_that("verify_precision handles minimum sample size (n=3)", {
  x <- c(100, 102, 98)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_s3_class(result, "verify_precision")
  expect_equal(result$input$n, 3)
  expect_equal(result$input$df, 2)
})


test_that("verify_precision handles perfect precision (zero variance)", {
  x <- rep(100, 10)  # No variation
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_equal(result$observed$sd, 0)
  expect_equal(result$observed$cv_pct, 0)
  expect_true(result$verification$verified)  # Zero variance is always verified
})


test_that("verify_precision handles large sample sizes", {
  set.seed(42)
  x <- rnorm(500, mean = 100, sd = 4)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_s3_class(result, "verify_precision")
  expect_equal(result$input$n, 500)
  expect_equal(result$input$df, 499)
})


test_that("verify_precision handles small mean values", {
  set.seed(42)
  x <- rnorm(25, mean = 1, sd = 0.04)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 1)
  
  expect_s3_class(result, "verify_precision")
  expect_true(result$observed$cv_pct > 0)
})


test_that("verify_precision handles large mean values", {
  set.seed(42)
  x <- rnorm(25, mean = 10000, sd = 400)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 10000)
  
  expect_s3_class(result, "verify_precision")
  expect_true(result$observed$cv_pct > 0)
})


# Numerical Consistency Tests ----

test_that("verify_precision ratios are consistent", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # CV ratio should equal SD ratio (when mean is same)
  expect_equal(result$verification$cv_ratio,
               result$observed$cv_pct / result$claimed$cv_pct,
               tolerance = 0.001)
  
  # Variance ratio should be SD ratio squared
  expect_equal(result$verification$ratio,
               (result$observed$sd / result$claimed$sd)^2,
               tolerance = 0.001)
})


test_that("verify_precision variance calculations are consistent", {
  x <- create_verification_data()
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  # Observed variance should equal sd^2
  expect_equal(result$observed$variance, result$observed$sd^2, tolerance = 0.001)
  
  # Claimed variance should equal sd^2
  expect_equal(result$claimed$variance, result$claimed$sd^2, tolerance = 0.001)
})


# Degrees of Freedom Tests ----

test_that("verify_precision uses correct df for numeric vector", {
  x <- rnorm(30, 100, 4)
  
  result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
  
  expect_equal(result$input$df, 29)
  expect_equal(result$test$df, 29)
})


test_that("verify_precision df equals input n-1 for vector input", {
  for (n in c(5, 10, 25, 50)) {
    x <- rnorm(n, 100, 4)
    result <- verify_precision(x, claimed_cv = 5, mean_value = 100)
    
    expect_equal(result$input$df, n - 1,
                 info = sprintf("Failed for n = %d", n))
  }
})


# Significance Level Tests ----

test_that("verify_precision alpha affects verification limit", {
  x <- create_verification_data()
  
  result_01 <- verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 0.01)
  result_05 <- verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 0.05)
  result_10 <- verify_precision(x, claimed_cv = 5, mean_value = 100, alpha = 0.10)
  
  # More stringent alpha -> higher UVL (easier to verify)
  expect_gt(result_01$verification$upper_verification_limit,
            result_05$verification$upper_verification_limit)
  expect_gt(result_05$verification$upper_verification_limit,
            result_10$verification$upper_verification_limit)
})


# Integration Tests ----

test_that("verify_precision results are reproducible with same seed", {
  set.seed(123)
  x1 <- rnorm(25, 100, 4)
  result1 <- verify_precision(x1, claimed_cv = 5, mean_value = 100)
  
  set.seed(123)
  x2 <- rnorm(25, 100, 4)
  result2 <- verify_precision(x2, claimed_cv = 5, mean_value = 100)
  
  expect_equal(result1$observed$cv_pct, result2$observed$cv_pct)
  expect_equal(result1$test$statistic, result2$test$statistic)
  expect_equal(result1$verification$verified, result2$verification$verified)
})


test_that("verify_precision handles realistic clinical lab scenario", {
  # Simulate verification study: 5 days x 5 replicates
  # Manufacturer claims CV = 4%
  set.seed(42)
  
  n_days <- 5
  n_reps <- 5
  true_sd <- 3.2  # Actual CV ~ 3.2%
  mean_val <- 100
  
  data <- expand.grid(day = 1:n_days, replicate = 1:n_reps)
  day_effects <- rnorm(n_days, 0, 1)
  errors <- rnorm(nrow(data), 0, 2)
  
  data$value <- mean_val + day_effects[data$day] + errors
  
  # Test with data frame input
  result <- verify_precision(
    data,
    claimed_cv = 4,
    value = "value",
    day = "day"
  )
  
  expect_s3_class(result, "verify_precision")
  # With these parameters, should likely be verified
  expect_true(is.logical(result$verification$verified))
})

Try the valytics package in your browser

Any scripts or data that you put into this service are public.

valytics documentation built on Feb. 19, 2026, 5:06 p.m.