[1] NA
Fluent, confident, and wrong
I am not going to ban you from using LLMs.
But I am going to try to convince you that how you use them over the next five weeks decides whether this module was worth your time.
Suppose the whole of your R skillset is “I can ask a model for R code”.
Your employer can do that. Their intern can do that. The person who didn’t do the MSc can do that.
The market rate for a skill everyone has is zero.
What is actually scarce — and what people pay for:
That’s the job. The typing was never the job.
Here is R code of the sort an assistant will happily produce.
Some of it is wrong.
None of it errors.
“How do I get the mean of a column that has missing values?”
NA.
There is no na.omit argument to mean(). It was swallowed by ... and silently ignored. The argument is na.rm:
Why you’d miss it: na.omit is a real R function. It reads perfectly.
“Convert this column to numbers.”
1 2 3. Those are the factor level codes, not your data.
Why you’d miss it: you asked for numbers and you got numbers. Small integers, in a plausible range. No warning. This one has made it into published papers.
“Filter out the samples from group a.”
# A tibble: 1 × 2
group value
<chr> <int>
1 b 2
“Join the phenotype table onto the sample table.”
3 rows in, 4 rows out. id == 1 appears twice in pheno, so sample s1 got duplicated. Every downstream mean is now wrong, and weighted towards whichever samples happened to have duplicate records.
# A tibble: 4 × 3
id sample measure
<dbl> <chr> <dbl>
1 1 s1 4.1
2 1 s1 9.9
3 2 s2 5.2
4 3 s3 NA
The habit that saves you: check nrow() before and after every join. Every single one.
An assistant will almost never do this unprompted. You have to.
“Apply my function across the list.”
[1] "matrix" "array"
Works — a matrix, as you’d hope. Now change one value:
A list. sapply() guesses its return type from the data, so your pipeline breaks on a dataset you haven’t seen yet — in three weeks, in a different script.
Type-stable alternatives fail loudly and immediately instead:
Error in `vapply()`:
! values must be length 2,
but FUN(X[[2]]) result is length 1
This is a general principle worth more than the specific example:
Prefer the tool that breaks now over the tool that breaks quietly later.
purrr::map_dbl(), vapply(), if_else() over ifelse() — all the same idea.
“Flag the dates after March.”
[1] NA 20605
“Add a column with the mean of a and b.”
# A tibble: 2 × 3
a b m
<dbl> <dbl> <dbl>
1 1 3 2.5
2 2 4 2.5
Every row got 2.5 — the mean of all four numbers. You wanted rowwise.
Why you’d miss it: the code says mean, you wanted a mean, and a number appeared. With 10,000 rows you would never look.
The model isn’t lying to you. It’s producing the most plausible-looking code, and plausible-looking code is exactly the thing you cannot audit by looking at it.
You cannot check what a model gives you unless you know:
NA does to a comparisonThat’s this module. Not because R is precious, but because that knowledge is the only thing standing between you and a confidently wrong result.
The people who get burned by these tools aren’t the ones who use them. They’re the ones who can’t check them.
Everything so far was about the model being wrong.
This one applies even when it is right.
Learning scientists call this a desirable difficulty: the effort of retrieving something yourself is what makes it stick. Watching it appear costs you that, and costs you it silently.
Nothing in that loop feels bad while it is happening. That is precisely the problem with it.
And you still sit the same paper in week 5, on your own.
You have access to Gemini, NotebookLM and Elicit. They are not interchangeable. They differ in how much room they have to invent.
| Tool | Grounded in | Best for |
|---|---|---|
| NotebookLM | sources you upload | revising from your own notes |
| Elicit | real published papers | finding literature for the report |
| Gemini | nothing in particular | general help, with every caveat so far |
Narrower grounding means less room to invent. It does not mean “safe”.
NotebookLM — grounding cuts invention, it does not remove it. It can still misread the source you handed it. Good for “what did we say about joins?”. No use at all for “why will my code not run?”
Elicit — a 2026 feasibility study in Research Synthesis Methods re-ran the same extractions from different accounts and compared them:
An answer you cannot reproduce is not evidence — and this module is about reproducible pipelines. Use it to find papers. Then read them.
Three things that make a genuine difference.
Most bad generated R comes from the model guessing your data’s shape. So don’t make it guess:
Better still, tell it the things str() can’t show:
“A row is one participant-visit, so IDs repeat.
measureis in mg/dL.-99means missing in theagecolumn.”
That single sentence prevents Exhibit D.
You can skip the copy-paste entirely. The btw package hands your live R session to an assistant — actual data frames, actual package versions, actual docs:
Also worth knowing about: ellmer for calling models from R, and the assistant built into Positron.
An assistant that can see your session is dramatically less likely to invent a column that doesn’t exist.
Vague question, average answer. Compare:
Weak
“How do I summarise this data?”
Better
“Using dplyr only — no new packages — give me mean and SD of measure by treatment_group, keeping groups with fewer than 5 observations visible rather than dropped. Tell me what happens to NAs in measure.”
Notice what the second one is doing: it names the tools, the output, and the edge case you’re worried about. Precision about edge cases is the whole skill.
The highest-value prompts you can learn:
sapply — what happens if the results have different lengths?”Models are far better at critiquing code than at writing it correctly first time. Use that asymmetry.
There’s a prompt on the module site that turns your assistant into something closer to a lab demonstrator: asks what you tried, hints instead of solving, makes you predict the output before it shows you.
Module site → Working with AI assistants
Paste it into Claude’s or ChatGPT’s custom instructions. There’s a Claude Code version too.
You can defeat it in four seconds by opening another tab. I know. It’s not a fence — it’s there for the days you’d rather learn the thing than have it done for you.
Fine — genuinely, use the tools:
Not fine in assessed work:
Not because of purity. Because the 50% report is marked on defending your analytical choices (MLO-5), and you cannot defend a choice you didn’t make. That conversation goes badly in a way that is very obvious from the outside.
Attempt → ask → diff
Try it yourself first. Then ask. Then compare what you wrote to what it wrote, and work out why they differ.
That third step is where the learning actually lives, and it’s the one everyone skips.
Nobody gets stronger watching a forklift.
Five weeks is not long. The version of you that finishes this module having struggled through the wrangling exercises is materially more employable than the version that had them generated — not because of virtue, but because only one of those two can look at Exhibit D and spot it.
I would rather you produced worse code this month and were better at R in October.
btw — give an assistant your R sessionellmer — call LLMs from R?function_name — faster than asking, and always correct
Data for Life Sciences 1 - Slides and code available here