Studio 7 — Produce a reproducible first analysis
Studio 7
Produce one honest result you can reproduce from a clean start, with its uncertainty attached.
Studio 7
This studio closes with Milestone 7: Your first reproducible analysis, a short chapter of its own after the lessons. What it asks you to produce. A route-specific result with an uncertainty statement, a restart-and-run-all verification, an environment record, and a claim-to-output check.
Studio 7 · Road map
Lesson 1 of this studio · Chapter 22
which quantity your code computes, and which cases it counts
Chapter 22
You decide which quantity your code must compute and over which set of cases, and you keep deciding it at every turn of the loop, because the tool rewrites the code each time you press enter. A cell that runs without an error is not a result you have earned. It is a draft you have not read.
Chapter 22 · Why this decision matters
“Do not read me the percentage. Tell me which cities it was computed over. If your code only saw the towns with an open-data portal, you have not described the state. You have described the portals.” — a state open-records officer, asked to act on a transparency report
Chapter 22 · Why this decision matters
Chapter 22 · The concept
Descriptive summary
a number or picture that reports what is actually in the cases you can see, such as a share, an average, or a full distribution
Quantity of interest
the one exact number your code is supposed to produce, pinned down in words before you ask for any code
Chapter 22 · The concept
Frame
the concrete set of cases your data actually covers, which is often smaller than the group your question is about
Convenience sample
a set of cases collected because they were easy to reach, not drawn to stand in for a population
Chapter 22 · The concept
Chapter 22 · The concept
Chapter 22 · The concept
Chapter 22 · A worked example
Chapter 22 · A worked example
.dropna(), and the dropped rows are the towns the scraper came back empty on.Chapter 22 · A worked example
Chapter 22 · A worked example
Chapter 22 · A worked example
.dropna(), then the whole roster.import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
roster = 240
big = np.arange(roster) < 40 # the open-data portal cities
on_time = np.zeros(roster, dtype=bool)
on_time[rng.choice(np.where(big)[0], 38, replace=False)] = True # 38 of 40
on_time[rng.choice(np.where(~big)[0], 110, replace=False)] = True # 110 of 200
# the scraper comes back empty exactly where clerical staff is thinnest
scraped = rng.random(roster) < np.where(big, 0.92, 0.62)
portal_only = on_time[big].mean()
after_dropna = on_time[big & scraped].mean()
best_case = (on_time | ~scraped).mean() # unretrievable counted as on time
worst_case = (on_time & scraped).mean() # unretrievable counted as late
print(f"turn one, portal cities only : {portal_only*100:.0f}%")
print(f"the same after .dropna() : {after_dropna*100:.0f}%")
print(f"whole roster, unretrievable flagged : between "
f"{worst_case*100:.0f}% and {best_case*100:.0f}%")
print(f"records the scraper never returned : {(~scraped).sum()} of {roster}")
print("\nthe clean run was never wrong about its own rows. it was answering")
print("a different question than the one you asked")Chapter 22
Where the tool failed
You ask for the on-time share and the tool returns tidy code that prints, with full confidence, “95% of councils post minutes on time. Compliance is strong.” The code has no error and reads beautifully. The trap is buried in two lines: the input dataframe was already limited to cities with an open-data portal, and a .dropna() silently removed the towns where no posting date came back. The share is real arithmetic over the wrong set.
Chapter 22 · An AI failure case
.dropna() shows how many towns vanished, and looking at which ones reveals the smallest governments in the state.Chapter 22
This stays yours
You define what quantity the code computes and which set of cases it runs over, and you decide what the number is allowed to claim. The tool can write the filter, the aggregation, and the plot, and it can run its own loop until everything is clean. It cannot decide that 40 portal cities speak for a state of 240, or that a compliance rate with the unreachable towns deleted is honest. You own the final sentence, its frame, and its boundary.
Chapter 22 · Your move
Work it in the companion notebook with Chapter 22 open beside it. Log every delegation in your AI Research Ledger.
Lesson 2 of this studio · Chapter 23
which analytical work you hand off, and which numbers you personally re-derive before you believe them
Chapter 23
You decide which analytical tasks the assistant runs and which numbers you re-derive yourself before any of them reach a claim. The assistant supplies labor by the bucket. Which checks count, which flags are real, and what the surviving number means: those stay on your side of the desk.
Chapter 23 · Why this decision matters
I do not care that a model ran your robustness checks. I care which ones you chose, which numbers you re-derived yourself, and which flags you confirmed against the data. Show me that trail and I trust your judgment. Show me the model’s transcript and I trust neither.
Chapter 23 · Why this decision matters
Chapter 23 · The concept
Chapter 23 · The concept
Chapter 23 · The concept
Robustness check
re-runs the same finding under a different but equally defensible choice and asks whether the answer holds
Placebo test
runs your exact analysis where the effect cannot exist and asks whether what comes back is ordinary for a world with nothing in it
Chapter 23 · The concept
Chapter 23 · The concept
Chapter 23 · The concept
Chapter 23 · A worked example
Chapter 23 · A worked example
Chapter 23 · A worked example
Chapter 23 · A worked example
import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
n = 300
experience = rng.normal(0, 1, size=n)
enrolled = rng.random(n) < 0.5
weeks = (18 - 6.0 * enrolled - 1.6 * experience
+ rng.normal(0, 6.5, size=n))
had_offer_already = np.zeros(n, dtype=bool)
had_offer_already[rng.choice(np.where(enrolled)[0], 3, replace=False)] = True
weeks[had_offer_already] = 1.0
df = pd.DataFrame({"enrolled": enrolled, "weeks": weeks,
"experience": experience, "pre_offer": had_offer_already})
def gap(d, stat): # in standard deviations, sign flipped
f = getattr(d.groupby("enrolled")["weeks"], stat)()
return (f[False] - f[True]) / d.weeks.std()
grid = []
for sample, d in (("all enrollees", df), ("dropping pre-offers", df[~df.pre_offer])):
for measure in ("mean", "median"):
raw = gap(d, measure)
adj_resid = d.weeks - np.polyval(np.polyfit(d.experience, d.weeks, 1),
d.experience)
adj = ((adj_resid[~d.enrolled].agg(measure)
- adj_resid[d.enrolled].agg(measure)) / d.weeks.std())
grid += [{"sample": sample, "measure": measure, "spec": "raw", "gap (sd)": raw},
{"sample": sample, "measure": measure, "spec": "adj. experience",
"gap (sd)": adj}]
g = pd.DataFrame(grid)
print(g.round(2).to_string(index=False))
print(f"\nall eight point the same way; magnitude runs "
f"{g['gap (sd)'].min():.1f} to {g['gap (sd)'].max():.1f} sd")
# placebo: shuffle the labels and re-run
fake = [gap(df.assign(enrolled=rng.permutation(df.enrolled)), "mean")
for _ in range(2000)]
beat = int((np.array(fake) >= gap(df, "mean")).sum())
print(f"placebo: of 2000 label shuffles, {beat} produced a gap this large")Chapter 23
Where the tool failed
You hand an assistant your grid and it returns a polished eight-row table, every row near 0.7, and declares the result “fully robust.” The run threw no error. Here is the trap: its “median” rows silently call the same mean function as the “mean” rows, so four of the eight numbers are duplicates wearing different labels. The table agrees with itself because it never varied the handle it claims to vary. This is the illusion of completeness, a thorough-looking output missing the one thing that matters, shading into confident fabrication, a number stated with certainty that never came from the code path it names.
Chapter 23 · An AI failure case
Chapter 23
This stays yours
Three calls never leave your hands. You decide which checks count for the claim you want to make, which flagged problems the data actually confirm, and the final claim you defend, with its boundary and its range. The assistant proposes and computes. The evidence decides, and you are the one who reads the evidence.
Chapter 23 · Your move
Work it in the companion notebook with Chapter 23 open beside it. Log every delegation in your AI Research Ledger.
Studio 7 closes here
What the lessons handed you becomes one artifact you can defend.
Milestone 7
The artifact
What this milestone produces. A route-specific result with an uncertainty statement, a restart-and-run-all verification, an environment record, and a claim-to-output check.
Milestone 7 · Check before you start
Milestone 7 · In the studio
Milestone 7 · Every studio, these four
Ethics, permissions, and data exposure
What you send to a tool during analysis is a disclosure decision each time.
Evidence, provenance, and reproducibility
Every number in your result traces to a data cell and a line of code.
AI activity, verification, and human decisions
Delegate the writing of code freely; verify every returned number against the data yourself.
Uncertainty, claim boundary, and revision history
A result reported without uncertainty is not yet a result.
Milestone 7
How the record works
Your milestone artifact is a dated, numbered version with the reason for the version attached. When later evidence changes it, you write the next version rather than editing the last one, because the sequence of changes is itself part of your research record.
AI is your arm and your research assistant, not your brain.
AI can review AI, and a second model is a real auditor of the first. The last decision is always human.

EDR|AI · Studio 7 — Produce a reproducible first analysis