Course outline

R Programming / Evaluation, ownership, and reproducible results / Lesson 18

An auditable analysis pipeline

Combine typed inputs, explicit rejection policy, invariant checks, and a reviewable result boundary.

About 5 min readContent reviewed
On this page

Lesson goals

  • Write a complete input-to-summary contract.
  • Choose strict rejection versus explicit row quarantine.
  • Separate structural validation from substantive conclusions.

Begin with a small explicit analytical question

Suppose a synthetic workshop records one nonnegative duration per uniquely identified item. The task is to report count, total minutes, and mean minutes for supplied valid records. This is a descriptive calculation, not a prediction, causal analysis, or population inference. State which records are eligible and whether a missing or invalid value rejects the whole input or creates a separate rejected-row report. These choices determine the denominator and therefore the meaning of the average. Keep the pipeline small enough that a reviewer can follow every transformation and independently verify the fixture by hand.

Validate at the boundary where meaning is introduced

The first fixture imports supplied CSV text under declared column classes and checks exact names, unique nonempty ids, a nonempty table, finite measurements, and nonnegative durations. It then returns a named result. The code is intentionally strict and rejects incomplete measurements rather than guessing values. Parsing errors can occur before the explicit checks and should remain visible as unsupported input, not be replaced with zero. A reusable production-facing parser would need more detailed token diagnostics and compatibility tests; this fixture establishes one narrow contract without claiming to solve every CSV dialect or locale.

r Illustrative base-R script; R runtime not available during review
summarize_csv <- function(text) {
  d <- read.csv(text = text, colClasses = c("character", "numeric"),
                stringsAsFactors = FALSE, na.strings = "")
  if (!identical(names(d), c("id", "minutes"))) stop("unexpected schema")
  if (anyNA(d$id) || any(d$id == "") || anyDuplicated(d$id)) stop("invalid ids")
  if (nrow(d) == 0L || anyNA(d$minutes) || !all(is.finite(d$minutes)) || any(d$minutes < 0)) stop("invalid minutes")
  list(rows = nrow(d), total = sum(d$minutes), mean = mean(d$minutes))
}
result <- summarize_csv("id,minutes\na,3\nb,9")
cat("rows=", result$rows, "; total=", result$total, "; mean=", result$mean, "\n", sep = "")
Expected output
rows=2; total=12; mean=6

A complete in-memory pipeline has a narrow declared schema, unique ids, nonnegative finite measurements, and an explicit summary result.

An alternative rejection policy is a different analysis

The second fixture demonstrates explicit row quarantine on already supplied vectors. It reports accepted and rejected positions and computes a total over accepted rows. This policy differs from strict whole-input rejection and must not be introduced silently after seeing a desired result. Preserve original ids, raw tokens, and rejection reasons when permitted, and check uniqueness separately if one record per item is still required. A rejected row is not a measured zero. For richer diagnostics, return one or more reason codes per row rather than only a Boolean, then test overlaps between reasons.

r Illustrative base-R script; R runtime not available during review
audit_rows <- function(ids, values) {
  stopifnot(length(ids) == length(values))
  valid <- !is.na(ids) & nzchar(ids) & !is.na(values) & is.finite(values) & values >= 0
  list(accepted = which(valid), rejected = which(!valid),
       total = sum(values[valid]))
}
result <- audit_rows(c("a", "b", "c"), c(3, NA_real_, -1))
cat("accepted=", paste(result$accepted, collapse = ","), "\n", sep = "")
cat("rejected=", paste(result$rejected, collapse = ","), "\n", sep = "")
cat("accepted total=", result$total, "\n", sep = "")
Expected output
accepted=1
rejected=2,3
accepted total=3

This alternative policy retains explicit rejected row positions instead of silently removing invalid measurements; uniqueness remains a separate declared check.

Use independent invariants and worked fixtures

For the strict valid fixture, two rows with three and nine minutes imply a total of twelve and a mean of six. Check count, names, types, and values together. For a quarantining pipeline, accepted plus rejected counts should cover every supplied position exactly once, while retained identities must remain aligned with values. Add duplicate ids, a singleton, empty input, missingness, nonfinite values, negative values, and unexpected columns as focused cases. Invariants complement expected examples; a preserved total alone can miss a swapped identity or a wrongly converted unit.

Deliver the result with scope and execution evidence

Keep the script, schema, units, rejection policy, configuration, and environment record alongside the summary. A later model or chart should consume this validated result without changing the observation contract invisibly. State which tests were actually executed and which remain documented expectations. In this course no R runtime or parser was available, so these complete base-R examples remain illustrative and their outputs are derived. Before using a learnerโ€™s local execution as evidence, compare all fixtures in a fresh supported session and investigate warnings. A clean synthetic pipeline supports local correctness reasoning, not a guarantee about an external dataset or decision.

Common errors and fixes

A strict pipeline starts dropping bad rows after a disappointing mean

Why it happens
The eligibility policy changed in response to the result.
How to fix it
Choose and document the policy before interpreting summaries.
Prevent it next time
Preserve rejected counts and the analysis revision history.

A correct total hides swapped ids

Why it happens
Only aggregate arithmetic was verified.
How to fix it
Check known identity-value pairs and aligned row operations.
Prevent it next time
Combine structural and numerical fixtures.

A derived output is reported as an executed test

Why it happens
Expected behavior was confused with runtime evidence.
How to fix it
Label the execution environment and actual checks precisely.
Prevent it next time
Keep separate records for derivation, parsing, and execution.

Check your understanding

1. What does the strict fixture return for durations three and nine, and why is missingness not silently ignored?

Show worked solution

It returns two rows, total twelve, and mean six. The declared contract requires every duration to be observed, finite, and nonnegative; missingness therefore rejects the input instead of changing the denominator. A different permitted policy would need its own explicit output and tests.

2. What extra evidence should accompany the quarantine fixture in a real review?

Show worked solution

Retain stable observation ids, raw-token provenance where permitted, reason codes for rejection, counts covering all input rows, and a uniqueness policy. Verify that accepted positions remain paired with their original values. Report the accepted-only scope rather than describing the result as a summary of all supplied measurements.

Key takeaways

  • Eligibility and rejection policies define the denominator.
  • Validate meaning, structure, and arithmetic at separate boundaries.
  • State execution evidence and external limitations accurately.

Official references