← Back to blog
Statistical AnalysisAI Evaluation

When AI-Written Code Runs, What Has It Proved?

5 min read

When AI-written analysis code runs without an error, it has proved that the code is valid and that your data fit its shape. It has not proved that the code estimates the quantity you meant to estimate. That takes separate evidence, and a research team using coding agents should keep that evidence next to the final table.

The checks below follow a validation strategy the Seascapemodels blog recently laid out for analyses written by coding agents. The author calls it a work in progress, and it reports no error rates, so treat it as practitioner guidance rather than a tested standard. The underlying ideas are older than AI: they are how careful analysts have always checked code they didn’t write themselves.

1. Write the estimand before the code

Before any code exists, write down in plain language what the analysis should estimate: the outcome, the comparison, the population, the unit of analysis, and every transformation and exclusion along the way. For example: “the difference in mean spring reading scores between intervention and comparison schools, among students enrolled in both fall and spring, adjusting for fall scores, with schools as clusters.”

That sentence is what everything else gets checked against. Without it, a reviewer (human or AI) can only ask whether the code looks reasonable, not whether it does what was intended. A specification also exposes logical problems early, when they cost a sentence to fix rather than a rerun.

2. Make data steps fail loudly

Most silent errors live in data preparation, not in the model call. Joins are the classic case: one duplicated ID in a lookup table turns 200 participants into 201 rows, and nothing errors. Assert what you expect after every join, filter, and reshape, and stop the script when it doesn’t hold.

R

set.seed(2026)
survey <- data.frame(id = 1:200, score = rnorm(200))
sites  <- data.frame(id = c(1:200, 17),                  # id 17 appears twice by mistake
                     site = sample(letters[1:5], 201, replace = TRUE))
joined <- merge(survey, sites, by = "id")
nrow(joined)                                             # 201, not 200: no error raised
stopifnot(nrow(joined) == nrow(survey),                  # fail loudly instead
          !anyDuplicated(joined$id))

Python (pandas)

import numpy as np
import pandas as pd

rng = np.random.default_rng(2026)
survey = pd.DataFrame({"id": range(1, 201), "score": rng.normal(size=200)})
sites = pd.DataFrame({"id": list(range(1, 201)) + [17],   # id 17 duplicated
                      "site": rng.choice(list("abcde"), 201)})
print(len(survey.merge(sites, on="id")))                  # 201, no error raised
survey.merge(sites, on="id", validate="one_to_one")       # raises MergeError

For larger pipelines, the R packages pointblank and validate let you declare these expectations as rules. Count checks have a limit, though: a join on the wrong key can produce exactly the right number of rows.

3. Test the workflow on data with a known answer

Turn your model specification around: simulate data from it with parameters you choose, run the simulated data through the same workflow, and see whether the parameters come back. If the code is supposed to estimate a slope of 0.5, it should recover about 0.5 on average across many simulated datasets. A stronger version checks coverage: 95% intervals should contain the true value in roughly 95% of simulations.

R

set.seed(2026)
fit_slope <- function(d) coef(lm(y ~ x, data = d))[["x"]]  # the analysis under test
true_b <- 0.5
est <- replicate(500, {
  d <- data.frame(x = rnorm(300))
  d$y <- 1 + true_b * d$x + rnorm(300)                   # known data-generating model
  fit_slope(d)
})
mean(est)                                                # about 0.5

Python

import numpy as np
import pandas as pd

rng = np.random.default_rng(2026)

def fit_slope(d):                                         # same estimate as R's lm() slope
    return np.polyfit(d["x"], d["y"], 1)[0]

true_b = 0.5
est = []
for _ in range(500):
    x = rng.normal(size=300)
    d = pd.DataFrame({"x": x, "y": 1 + true_b * x + rng.normal(size=300)})
    est.append(fit_slope(d))
print(np.mean(est))                                       # about 0.5

Run as written, these give an average of 0.504 in R and 0.501 in Python; the two languages draw different random numbers, so the values differ slightly. Two cautions apply. First, the test only covers the conditions you simulate, so simulate the ones that worry you: clustering, missing data, skewed outcomes. Second, write the simulation separately from the analysis code. The Seascapemodels post suggests building it in a separate agent session with memory turned off, so the same misunderstanding can’t shape both the generator and the analysis.

4. Perturb what shouldn’t matter, and what should

  • Shuffle the rows. If order is irrelevant to the analysis, randomly reordering rows should leave the result unchanged. This only applies where order really doesn’t matter: lags, cumulative sums, and “keep the first record” deduplication legitimately depend on it.
  • Shuffle a covariate. Permuting one predictor independently of the outcome should push its estimated effect toward zero on average. If it doesn’t, the code is picking up something it shouldn’t.
  • Fit the same model two ways. For a mixed model, compare implementations such as lme4, nlme, and glmmTMB. Agreement is reassuring but not proof: implementations can share the same assumptions, and code written by the same kind of agent can share the same mistakes.

What a pass cannot establish

Each check answers a narrow question. Read passes and failures against your design, not as a certificate.

Intended resultIndependent checkWhat a pass cannot establish
The code estimates the stated quantityReviewer compares the written estimand with the codeThat the estimand is the scientifically right one
Records merge correctlyRow and unique-ID counts after each joinThat the right records matched; a wrong key can give the right count
The model recovers its parametersSimulated data with known valuesThat your real data follow the simulated model
Results don’t depend on incidental choicesRow shuffles, covariate shuffles, second implementationThat shared assumptions are correct

None of these checks can establish causal identification or the scientific truth of the model. They catch implementation errors: code that runs, produces plausible numbers, and answers a different question than the one you asked.

What to ask for

Pick one result your team plans to report and write down what independent check would have caught a plausible but wrong implementation. If the answer is “nothing we currently run,” that is the check to add first.

If you want a second set of eyes on how an AI-assisted analysis is specified and validated, DASS supports researchers with analysis planning and review.

Related reading

Get new posts by email

One email whenever we publish something new. No spam, unsubscribe anytime.

Check your inbox — click the confirmation link to finish subscribing.

{{ subscribeErrorMsg }}