Skip to content

Commit 0d49c44

Browse files
JamesHWadeclaude
andcommitted
fix: improve calibration CIs, Gage R&R warnings, and COW dead code cleanup
- Calibration inverse prediction intervals now use proper ISO 11843 formula accounting for leverage and calibration center distance, not just sigma/slope - Gage R&R now warns when negative variance components are clipped to zero, instead of silently forcing them - Remove dead code line in COW alignment DP (prev_total was always 0) - Update Gage R&R tests to suppressWarnings for random data scenarios Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7e4e3c7 commit 0d49c44

File tree

4 files changed

+57
-39
lines changed

4 files changed

+57
-39
lines changed

R/align.R

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,9 +1171,6 @@ tunable.step_measure_align_cow <- function(x, ...) {
11711171
for (w in warp_options) {
11721172
# Try all previous states
11731173
for (prev_w in warp_options) {
1174-
prev_total <- (seg - 2) * 0 # Not tracking cumulative directly
1175-
# This approach: track segment-by-segment, each warp is local
1176-
11771174
prev_state <- state_idx(prev_w)
11781175
if (cost[seg - 1, prev_state] == Inf) {
11791176
next

R/calibration-fit.R

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -537,29 +537,35 @@ measure_calibration_predict <- function(
537537
interval_type,
538538
level
539539
) {
540-
# Simplified interval calculation using delta method approximation
541-
# For rigorous intervals, would need to implement Fieller's theorem
542-
543540
fit <- object$model
544541
sigma <- summary(fit)$sigma
545542
n <- nrow(object$data)
546-
547-
# Get slope for delta method
548-
coefs <- stats::coef(fit)
549-
slope <- coefs[object$conc_col]
550-
551-
# Standard error of inverse prediction (approximate)
552-
se_pred <- sigma / abs(slope)
553-
554-
# Additional uncertainty for prediction intervals
555-
if (interval_type == "prediction") {
556-
se_pred <- se_pred * sqrt(1 + 1 / n)
557-
}
558-
559-
# Critical value
560543
df <- summary(fit)$df[2]
561544
t_crit <- stats::qt((1 + level) / 2, df)
562545

546+
if (object$model_type == "linear") {
547+
# Classical inverse prediction intervals (ISO 11843 / Draper & Smith)
548+
# Confidence: SE = (sigma/|b|) * sqrt(1/n + (x0 - xbar)^2 / Sxx)
549+
# Prediction: SE = (sigma/|b|) * sqrt(1 + 1/n + (x0 - xbar)^2 / Sxx)
550+
coefs <- stats::coef(fit)
551+
slope <- coefs[object$conc_col]
552+
cal_conc <- object$data[[object$conc_col]]
553+
x_bar <- mean(cal_conc)
554+
sxx <- sum((cal_conc - x_bar)^2)
555+
556+
extra <- if (interval_type == "prediction") 1 else 0
557+
se_pred <- (sigma / abs(slope)) *
558+
sqrt(extra + 1 / n + (pred_conc - x_bar)^2 / sxx)
559+
} else {
560+
# Quadratic models: use delta method approximation
561+
coefs <- stats::coef(fit)
562+
slope <- coefs[object$conc_col]
563+
se_pred <- sigma / abs(slope)
564+
if (interval_type == "prediction") {
565+
se_pred <- se_pred * sqrt(1 + 1 / n)
566+
}
567+
}
568+
563569
list(
564570
lower = pred_conc - t_crit * se_pred,
565571
upper = pred_conc + t_crit * se_pred

R/precision.R

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,28 @@ measure_gage_rr <- function(
409409
ms_error <- anova_table["Residuals", "Mean Sq"]
410410

411411
# Calculate variance components (EMS method)
412+
# Negative estimates are set to zero (standard practice for ANOVA-based
413+
414+
# variance components), but we warn since this can indicate poor model fit
412415
var_repeatability <- ms_error
413-
var_interaction <- max(0, (ms_interaction - ms_error) / n_replicates)
414-
var_operator <- max(
415-
0,
416-
(ms_operator - ms_interaction) / (n_parts * n_replicates)
416+
raw_interaction <- (ms_interaction - ms_error) / n_replicates
417+
raw_operator <- (ms_operator - ms_interaction) / (n_parts * n_replicates)
418+
raw_part <- (ms_part - ms_interaction) / (n_operators * n_replicates)
419+
420+
clipped <- c(
421+
if (raw_interaction < 0) "interaction",
422+
if (raw_operator < 0) "operator",
423+
if (raw_part < 0) "part"
417424
)
418-
var_part <- max(0, (ms_part - ms_interaction) / (n_operators * n_replicates))
425+
if (length(clipped) > 0) {
426+
cli::cli_warn(
427+
"Negative variance component{?s} set to zero for {.val {clipped}}. This may indicate insufficient variation or poor model fit."
428+
)
429+
}
430+
431+
var_interaction <- max(0, raw_interaction)
432+
var_operator <- max(0, raw_operator)
433+
var_part <- max(0, raw_part)
419434

420435
# Combine components
421436
var_reproducibility <- var_operator + var_interaction

tests/testthat/test-precision.R

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,12 @@ test_that("measure_gage_rr calculates variance components", {
216216
ifelse(data$operator == "A", 0.5, ifelse(data$operator == "B", -0.3, 0)) +
217217
rnorm(nrow(data), 0, 0.5)
218218

219-
result <- measure_gage_rr(
219+
result <- suppressWarnings(measure_gage_rr(
220220
data,
221221
response_col = "measurement",
222222
part_col = "part",
223223
operator_col = "operator"
224-
)
224+
))
225225

226226
expect_s3_class(result, "measure_gage_rr")
227227
expect_equal(nrow(result), 4)
@@ -240,12 +240,12 @@ test_that("measure_gage_rr calculates percentages correctly", {
240240
)
241241
data$measurement <- rnorm(nrow(data), 100, 2)
242242

243-
result <- measure_gage_rr(
243+
result <- suppressWarnings(measure_gage_rr(
244244
data,
245245
response_col = "measurement",
246246
part_col = "part",
247247
operator_col = "operator"
248-
)
248+
))
249249

250250
# Percentages should sum appropriately
251251
expect_true(all(result$pct_contribution >= 0))
@@ -261,13 +261,13 @@ test_that("measure_gage_rr calculates tolerance percentage when provided", {
261261
)
262262
data$measurement <- 50 + (data$part - 3) * 5 + rnorm(nrow(data), 0, 1)
263263

264-
result <- measure_gage_rr(
264+
result <- suppressWarnings(measure_gage_rr(
265265
data,
266266
response_col = "measurement",
267267
part_col = "part",
268268
operator_col = "operator",
269269
tolerance = 20
270-
)
270+
))
271271

272272
expect_false(anyNA(result$pct_tolerance))
273273
expect_true(all(result$pct_tolerance >= 0))
@@ -282,12 +282,12 @@ test_that("measure_gage_rr stores metadata", {
282282
)
283283
data$measurement <- rnorm(nrow(data))
284284

285-
result <- measure_gage_rr(
285+
result <- suppressWarnings(measure_gage_rr(
286286
data,
287287
response_col = "measurement",
288288
part_col = "part",
289289
operator_col = "operator"
290-
)
290+
))
291291

292292
expect_equal(attr(result, "n_parts"), 10)
293293
expect_equal(attr(result, "n_operators"), 3)
@@ -318,13 +318,13 @@ test_that("measure_gage_rr print method works", {
318318
)
319319
data$measurement <- rnorm(nrow(data), 50, 2)
320320

321-
result <- measure_gage_rr(
321+
result <- suppressWarnings(measure_gage_rr(
322322
data,
323323
response_col = "measurement",
324324
part_col = "part",
325325
operator_col = "operator",
326326
tolerance = 20
327-
)
327+
))
328328

329329
expect_output(print(result), "measure_gage_rr")
330330
expect_output(print(result), "Variance Components")
@@ -345,12 +345,12 @@ test_that("measure_gage_rr ndc calculation is reasonable", {
345345
(data$part - 5) * 10 + # Large part variation
346346
rnorm(nrow(data), 0, 0.5) # Small measurement error
347347

348-
result <- measure_gage_rr(
348+
result <- suppressWarnings(measure_gage_rr(
349349
data,
350350
response_col = "measurement",
351351
part_col = "part",
352352
operator_col = "operator"
353-
)
353+
))
354354

355355
# With large part variation and small error, ndc should be high
356356
expect_true(attr(result, "ndc") >= 5)
@@ -378,12 +378,12 @@ test_that("tidy.measure_gage_rr returns tibble", {
378378
)
379379
data$measurement <- rnorm(nrow(data))
380380

381-
result <- measure_gage_rr(
381+
result <- suppressWarnings(measure_gage_rr(
382382
data,
383383
response_col = "measurement",
384384
part_col = "part",
385385
operator_col = "operator"
386-
)
386+
))
387387

388388
tidy_result <- tidy(result)
389389
expect_s3_class(tidy_result, "tbl_df")

0 commit comments

Comments
 (0)