22 AI as Programmer
This chapter is part of a book in active development and has not yet been through the author’s review. Content may change as the review advances.
The research decision. 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.
22.1 Why this decision matters
The decision on the table: which quantity your code computes, and which cases it counts.
“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
An AI writes your analysis code in seconds, and it runs. That is exactly where the danger sits. The tool writes fluent code for the quantity it thinks you asked for, over whatever rows sit in the dataframe you handed it. Paste the printed number into your report and you have signed your name to a quantity you never checked, over a set you never chose. The officer above does not care that the code was clean, only what it counted and about whom.
22.2 The concept
The tool is the programmer. You are the researcher. Two decisions never move to the programmer’s side of the desk, and this chapter is about holding them.
- 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. Example: the fraction of city councils that post their meeting minutes within a week.
- Quantity of interest: the one exact number your code is supposed to produce, pinned down in words before you ask for any code. Example: “the share of municipalities in the state that post council minutes within seven days of the meeting.”
- Frame: the concrete set of cases your data actually covers, which is often smaller than the group your question is about. Example: only the cities that publish through an open-data portal.
- Convenience sample: a set of cases collected because they were easy to reach, not drawn to stand in for a population. Example: those portal cities, which tend to be the largest and best-staffed in the state.
Here is the trap the chapter closes. A tool can compute a flawless share over the wrong frame, and the code still finishes with a green check. A clean run is not a correct result: a cell can execute with no error and still compute a different number than your question needs (Vaithilingam et al. 2022).
22.2.1 Coding with AI is a loop, not a wish
Nobody gets working analysis code from one prompt, and you should not try to. Real AI-assisted coding is a loop: you prompt, you read the output, you interrogate it, you refine, and you run it again. Agentic tools now run that loop by themselves. They write, execute, read the error, patch the code, rerun, and hand you a tidy end state with a paragraph about how well it went.
The loop is a real gain in speed. It is also where your frame goes missing. Any turn can add a filter, drop a join, or reach for a different column, and the code you finally read may not be the code you specified three turns ago. Nothing announces the change. The number just gets prettier.
So verification runs per cycle, not per session. Every time the code changes, you ask the same two questions again: what quantity, over which cases? When a tool runs the cycles for you, make it surrender the choices it made along the way, and read the code rather than its summary of the code (Sandve et al. 2013). You own two things the programmer cannot. First, you define the quantity of interest and the frame in plain words, before you delegate (Blair et al. 2023). Second, you confirm that the code sitting in front of you right now computes that quantity over that frame, and not a plausible neighbor.
22.3 A worked example
You want to answer a simple question about your state: what share of city councils post their meeting minutes within seven days? You have a spreadsheet scraped from municipal websites, and you ask an AI to write the Python that returns the share posting on time.
Turn one. It writes six clean lines. They run. The printout says 95% of councils post their minutes on time, and the temptation is to write “compliance is strong.” Instead you read what the code did.
Turn two. You interrogate it: which rows are in here, and what did you drop? Two things surface. The dataframe holds only the 40 municipalities that publish through an open-data portal, the largest and best-staffed in the state. There are 240 on the roster. That is a convenience sample standing in silently for all of them. The code also calls .dropna(), and the dropped rows are the towns where the scraper came back empty, which are exactly the small places with the thinnest clerical staff. The 95% describes the best-resourced cities with their worst records deleted.
Turn three. You re-anchor to your quantity of interest: the share over every municipality on the state roster, with unretrievable records flagged instead of dropped. You refine the prompt to say precisely that, run again, and the picture changes. At most one in six municipalities is confirmed to post on time, and for most of the state you simply do not know. That “do not know” is a finding, not a gap to paper over.
Now notice what would have happened if an agentic tool had run all three turns on its own. It would have handed you the polished 95% with a confident paragraph, and the decision that shrank your state to 40 cities would have happened inside a step you never saw. The tool wrote correct code for the wrong question. You did the research by deciding what the code was allowed to count.
Code that runs cleanly while answering the wrong question is the documented risk when generated code is accepted on its output alone (Vaithilingam et al. 2022).
The block below reproduces all three turns on the same roster, including the rows the scraper never returned. The 95% is real; read what it is 95% of.
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")22.4 An AI failure case
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.
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.
22.5 It is your turn
You are working inside Studio 7: Produce a reproducible first analysis. Keep what you write here; the studio’s milestone chapter is where it joins the other lessons’ pieces into one artifact you can defend.
Your project has a design, a documented data source, and defined measurements. This step turns them into your first real numbers, and into the habit of never trusting code you have not read.
The hands-on half of this section lives in the chapter’s companion notebook: open it in Colab with the badge at the top, and work the steps there.
Commit your own answer first, then delegate. Each prompt is a checkable job, not a request for a verdict, and each one is a turn in a loop you keep running until the code says what you meant.
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.
Write your quantity of interest in one sentence, and the frame it runs over in a second. Be concrete: not “my data,” but the exact list of cases a stranger could reconstruct without asking you.
Prompt an AI tool for the code that computes that quantity over that frame. Save the transcript. This is turn one of a loop, not a finished answer.
Locate the standard idiom.
Act as a Python data assistant. I need the share of rows above a threshold, computed only over a subset I will name. Before any code, name the standard pandas idiom for a threshold share over a filtered subset and the exact argument that controls whether missing values are dropped. Only name functions you are sure exist.After running, verify: open the pandas docs and confirm the method and that argument exist and default as described. Counters confident fabrication (an invented method or argument arrives as confidently as a real one).
A second angle, optional:
Delegate, then demand the choices back as a table.
Write code that returns the share of municipalities that posted council minutes within seven days of the meeting. Then return a table of every choice you made: which rows you kept, whether you dropped any and why, the exact set the share is computed over, and whether you reported a share or an average.After running, verify: check the row count before and after the code, and confirm the set it computed over is the one your question names. Counters silent scope change (a share quietly computed over the portal cities, not the state).
Read the returned code line by line and ask what each line removes. Interrogate every filter, join, and missing-value call. Then refine your prompt and run the loop again, as many times as it takes.
Make the loop show its work.
You revised this code three times. For each revision, tell me in one line what changed about which rows are included or excluded, and which revision changed the set the number is computed over. Show me the code, not a summary of it.After running, verify: put the first and the last version side by side and confirm the filters match what the tool reported. Counters silent scope change in its sneakiest form, a frame that narrows one revision at a time.
Print the shape of the data the number was computed over, and the row count before and after anything that can drop rows. A number without its row count is not a result yet.
Write one sentence naming what this number does not cover: the cases your frame left out, and what you therefore cannot say about them.
Red-team the number.
Here is my claim: "95% of city councils post their minutes within seven days." Act as a hostile open-records reviewer. Name every way this code could have counted the wrong quantity or this share could mislead. Do not rewrite the claim for me.After running, verify: if it only praises the number, push back and demand the single worst flaw. Counters sycophantic agreement (praise that reviews your ego, not your evidence).
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. An AI reviewer may run the check with you; the decision to accept or reject stays yours.