HONR 46400 · Evidence-Driven Research

Studio 7 — Produce a reproducible first analysis

Davi Moreira

What you can defend when you leave

Studio 7

Produce one honest result you can reproduce from a clean start, with its uncertainty attached.

The milestone ahead

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.

The lessons in this studio

Studio 7 · Road map

  • Lesson 22 — AI as Programmer: your first computed number, its delegated code read line by line, and the sentence naming what the number does not cover.
  • Lesson 23 — AI as Analytical Assistant: your headline estimate with its uncertainty statement and cycle log, two independently re-derived numbers, a placebo run where licensed, the clean-restart record, the environment record, and the claim-to-output trace.

AI as Programmer

Lesson 1 of this studio · Chapter 22

which quantity your code computes, and which cases it counts

The research decision

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.

The officer wants to know which cities the number was computed over

Chapter 22 · Why this decision matters

  • The decision on the table: which quantity your code computes, and which cases it counts.
  • He does not care that the code was clean, only what it counted and about whom.

“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

A cell that runs without an error is a draft you have not read

Chapter 22 · Why this decision matters

  • An AI writes your analysis code in seconds, and it runs.
  • That is exactly where the danger sits.
  • It writes fluent code for the quantity it thinks you asked for.
  • Paste the printed number into your report and you have signed for a quantity you never checked.

Pin the number down in words before you ask for any code

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

  • The tool is the programmer. You are the researcher.
  • Example: “the share of municipalities in the state that post council minutes within seven days of the meeting.”

Your frame is the set your data covers, not the group you asked about

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

  • Example: only the cities that publish through an open-data portal.
  • Those tend to be the largest and best-staffed in the state.

A clean run is not a correct result

Chapter 22 · The concept

  • A tool can compute a flawless share over the wrong frame.
  • The code still finishes with a green check.
  • A cell can execute with no error and compute a different number than your question needs (Vaithilingam et al. 2022).

Coding with AI is a loop, not a wish

Chapter 22 · The concept

  • Nobody gets working analysis code from one prompt, and you should not try to.
  • You prompt, read the output, interrogate it, refine, and run it again.
  • Agentic tools now run that loop themselves and hand you a tidy end state.
  • The loop is a real gain in speed. It is also where your frame goes missing.

Verification runs per cycle, not per session

Chapter 22 · The concept

  • Any turn can add a filter, drop a join, or reach for a different column.
  • Nothing announces the change. The number just gets prettier.
  • Every time the code changes, ask again: what quantity, over which cases?
  • Read the code, not the tool’s summary of the code (Sandve et al. 2013).

Turn one: six clean lines and a reassuring 95%

Chapter 22 · A worked example

  • Your question: what share of city councils post their meeting minutes within seven days?
  • You have a spreadsheet scraped from municipal websites.
  • You ask an AI for the Python that returns the share posting on time.
  • The printout says 95%, and the temptation is to write “compliance is strong.”

Turn two: 40 portal cities standing in for 240

Chapter 22 · A worked example

  • The dataframe holds only the 40 municipalities that publish through an open-data portal.
  • There are 240 on the state roster. That is a convenience sample standing in silently.
  • The code also calls .dropna(), and the dropped rows are the towns the scraper came back empty on.
  • The 95% describes the best-resourced cities with their worst records deleted.

Turn three: not knowing is a finding

Chapter 22 · A worked example

  • You re-anchor to your quantity: the share over every municipality on the state roster.
  • Unretrievable records get flagged, not dropped.
  • At most one in six municipalities is confirmed to post on time.
  • For most of the state you simply do not know.

The tool wrote correct code for the wrong question

Chapter 22 · A worked example

  • An agentic run would have handed you the polished 95% with a confident paragraph.
  • The decision that shrank your state to 40 cities would sit inside a step you never saw.
  • Code that runs cleanly while answering the wrong question is the documented risk of accepting output alone (Vaithilingam et al. 2022).
  • You did the research by deciding what the code was allowed to count.

The 95% is real. Read what it is 95% of.

Chapter 22 · A worked example

  • Watch three shares in order: portal cities only, the same after .dropna(), then the whole roster.
  • The roster line is a range, because unretrievable records are flagged instead of deleted.
  • A fourth line counts the records the scraper never returned.
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")

An AI failure case

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.

How it failed

Chapter 22 · An AI failure case

  • You catch it by reading the code, not its summary.
  • Printing the dataframe’s shape shows 40 municipalities, not 240.
  • Printing the row count before and after .dropna() shows how many towns vanished, and looking at which ones reveals the smallest governments in the state.
  • Recompute over the full roster, flag the unretrievable records instead of deleting them, and the reassuring 95% collapses.
  • A green check certified that the code ran, never that it answered your question.

Do not delegate

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.

It is your turn

Chapter 22 · Your move

  1. Write your quantity of interest in one sentence, and the frame it runs over in a second.
  2. Prompt an AI tool for the code that computes that quantity over that frame.
  3. Read the returned code line by line and ask what each line removes.
  4. Print the shape of the data the number was computed over, and the row count before and after anything that can drop rows.
  5. Write one sentence naming what this number does not cover: the cases your frame left out, and what you therefore cannot say about them.
  6. Log this first number in your AI Research Ledger, and verify it with a named method from the Verification Guide. Alternative code is the natural pick here: recompute the same quantity a second way and confirm the two roads give one answer.

Work it in the companion notebook with Chapter 22 open beside it. Log every delegation in your AI Research Ledger.

AI as Analytical Assistant

Lesson 2 of this studio · Chapter 23

which analytical work you hand off, and which numbers you personally re-derive before you believe them

The research decision

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.

The discussant does not care that a model ran the checks

Chapter 23 · Why this decision matters

  • A discussant at a labor economics seminar, reading your results section.
  • The decision on the table: which analytical work you hand off.
  • And which numbers you personally re-derive before you believe them.

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.

Paste the table and you have signed your name to numbers you never checked

Chapter 23 · Why this decision matters

  • An AI tool can write your analysis code and run a dozen checks in seconds.
  • It hands back a clean table.
  • That speed is exactly why a loose habit is dangerous.
  • This chapter gives you the habit that survives the discussant’s question.

A task is checkable; a verdict is not its job

Chapter 23 · The concept

  • An AI analytical assistant takes a well-specified analytical task, never your judgment (Vaithilingam et al. 2022).
  • “Recompute this gap after dropping the three people who enrolled with an offer already in hand.”
  • It writes and runs that one check.

You attack your headline estimate before you defend it

Chapter 23 · The concept

  • Your headline estimate is the single number that stands in for your whole finding.
  • “People who went through the program received a first offer about 0.7 standard deviations sooner.”
  • Before you defend that number, you try to break it.
  • Breaking it is the labor you delegate.

Two attacks do most of the work

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

  • You reported a mean time to first offer, so also compute the median (Simonsohn et al. 2020).
  • Shuffle the “program” and “no program” labels at random, many times (Lipsitch et al. 2010).
  • Random labels always differ a little, so that pile of fake gaps is not zeros.
  • What matters is whether your real gap looks ordinary in that pile, or sits far outside.

A number that survived turn four says nothing about turn seven’s code

Chapter 23 · The concept

  • You will not get your analysis from one prompt.
  • You prompt, read the output, interrogate it, refine, and run it again.
  • Agentic tools now run those cycles on their own, writing and patching until something looks finished.
  • Verification attaches to each cycle, not to the last one.

Re-asking until the estimate looks better is a specification search

Chapter 23 · The concept

  • Every re-prompt is a fork in your analysis.
  • Keep re-asking until the number improves and you have searched, without telling anyone, including yourself.
  • The fix is cheap: keep a running log, one line per cycle.
  • Say what you asked, what came back, and what you changed and why.
  • An assistant that ran ten silent variants and reported the nicest one is an unlogged search.

A tool that ran without an error has not proven its result correct

Chapter 23 · The concept

  • The assistant proposes checks, writes code, and prints numbers, cycle after cycle.
  • You decide which checks count, and you log the cycles.
  • You verify every number before it reaches your claim.
  • Verify the number, not the paragraph about the number.

A four-week program, and a 0.7 standard deviation gap you try to break

Chapter 23 · A worked example

  • The program helps people target applications and rehearse interviews.
  • Outcome: weeks from enrollment to first offer.
  • Headline: participants reached an offer about 0.7 standard deviations sooner.
  • You delegate a grid with three handles: the sample, the measurement, the specification.

Eight numbers, two re-derived by hand, and an honest range

Chapter 23 · A worked example

  • The assistant returns eight numbers. You do not trust them yet.
  • You recompute two rows by hand with a second, simpler expression, and they match.
  • The direction holds across all eight; the magnitude runs about 0.6 to 0.7 standard deviations.
  • Your honest headline is a direction plus a range, not one flattering number.
  • Three cycles went into getting the grid to run, and all three are in your log.

The flag the reviewer named was refuted; the flaw it missed was real

Chapter 23 · A worked example

  • Shuffle the program labels and re-run many times. Your real gap sits far out in the tail.
  • That lowers your worry that the machinery alone is manufacturing the effect, as far as this check sees.
  • Asked as a hostile reviewer for the worst flaw, it confidently blames the most experienced workers.
  • You already dropped the already-employed group and the gap held, so the data refute that flag.
  • The flaw it missed: weeks to an offer is not the quality of the offer.

Build the grid, then run the shuffle yourself

Chapter 23 · A worked example

  • Watch the eight rows point the same way, then read the printed range.
  • Recompute two rows by hand before you believe any of them.
  • The last line counts how many of 2000 label shuffles produced a gap this large.
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")

An AI failure case

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.

How it failed

Chapter 23 · An AI failure case

  • You catch it by recomputing one “median” row by hand.
  • The true median gap is 0.55, not 0.70.
  • The grid overstated how much your choices agreed.
  • A green check is not a correct result.

Do not delegate

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.

It is your turn

Chapter 23 · Your move

  1. Before the assistant touches anything, write your headline estimate in one sentence and the answer you expect.
  2. Hand over one well-specified task at a time.
  3. Keep a cycle log as you go: one line per turn with what you asked, what came back, and what you changed and why.
  4. Re-derive at least two numbers yourself, by hand or with a second simple expression, before any of them enter a claim.
  5. Run a placebo: shuffle your group labels, re-run the same code unchanged, and confirm the fake gap lands in the ordinary part of that pile.
  6. Close with the milestone’s three checks: restart and run everything from a clean state and confirm the headline numbers match, record your environment (versions, packages, data files), and trace your provisional claim to the exact output that supports it.
  7. Log the analysis in your AI Research Ledger, cycle log attached, and verify at least one output with a named method from the Verification Guide. Alternative code fits a computed gap well.

Work it in the companion notebook with Chapter 23 open beside it. Log every delegation in your AI Research Ledger.

Milestone 7: Your first reproducible analysis

Studio 7 closes here

What the lessons handed you becomes one artifact you can defend.

What this milestone produces

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.

What you bring

Milestone 7 · Check before you start

  • Lesson 22 — AI as Programmer: your first computed number, its delegated code read line by line, and the sentence naming what the number does not cover.
  • Lesson 23 — AI as Analytical Assistant: your headline estimate with its uncertainty statement and cycle log, two independently re-derived numbers, a placebo run where licensed, the clean-restart record, the environment record, and the claim-to-output trace.

The practice

Milestone 7 · In the studio

  1. Write the analysis you declared, and only that analysis, before looking at anything else.
  2. Produce the result with its uncertainty statement, in the form your Contract specified.
  3. Restart and run everything from a clean state; if the numbers move, the pipeline is the finding.
  4. Record your environment, and check that every claim you plan to make traces to a specific output.

The four rails, here

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.

A version, not a pass

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.

The one rule

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.