Tuesday, June 23, 2026
Today: Tuesday June 23
Before class
Plan for today
ellmer package.Helpful links
An LLM (ChatGPT, Claude, Gemini, etc.) predicts likely text from a prompt. For an R user that’s useful for:
ggplot of X by Y, faceted by Z.”across() do, with a small example?”Tip
The skill that matters most is not “getting an answer.” It’s judging whether the answer is right.
| Where | Examples | Best for |
|---|---|---|
| Chat window | Claude, ChatGPT, Gemini | one-off questions, explaining errors, learning |
| In the IDE | Positron / RStudio assistants, GitHub Copilot | inline autocomplete, “fix this” on selected code |
| From R code | the ellmer package |
doing the same LLM call on every row |
A vague prompt gets a vague (often wrong) answer. A good R prompt includes:
str(), glimpse(), or dput(head(x)).Warning
Don’t paste private or sensitive data. Anything you send leaves your machine and may be stored. Share a tiny fake example with the same structure, not the real dataset.
The single most useful thing you can hand an LLM (or a human, or Stack Overflow) is a reprex, which is a tiny, self-contained, runnable snippet that reproduces the problem.
Built-in datasets (mtcars, iris, penguins) make great reprexes. This helper can run them too.
Weak
“my ggplot doesn’t work, fix it”
You’ll get a generic guess that may not match your situation.
Stronger
“Using
penguins, I want a scatter ofbill_length_mmvsflipper_length_mmcolored byspecies. My codeggplot(penguins) + geom_point(bill_length_mm, flipper_length_mm)errors with ‘object not found’. Use tidyverse to tackle this.”
Goal + data + code + error + constraint.
ellmerellmer isellmer is the tidyverse package that provides a unified interface for accessing various LLMs from R:
chat_anthropic(): Claudechat_openai(): GPTchat_google_gemini(): GeminiNote
The newest, most capable Claude models are Fable 5 (claude-fable-5) and the Claude 4.x family: Opus 4.8 (claude-opus-4-8), Sonnet 4.6 (claude-sonnet-4-6), Haiku 4.5 (claude-haiku-4-5). Bigger models are smarter but cost more per call.
Calling a provider needs an API key (a secret password tied to a paid account).
# DON'T: a key pasted in a script gets committed to GitHub forever.
chat_anthropic(api_key = "sk-ant-REAL-KEY")
# INSTEAD: store it once in an environment variable ellmer can read automatically.
usethis::edit_r_environ() # opens ~/.Renviron; add: ANTHROPIC_API_KEY=sk-ant-...
# restart R, then:
chat <- chat_anthropic(model = "claude-opus-4-8") # key picked up from the envWarning
A leaked key can run up real charges. Keep it out of .R, .qmd, and anything you push to git (.Renviron is git-ignored by default).
The real power for us isn’t chatting, but rather running the same prompt over every row of a data frame. This approach can help turn messy text into structured columns since ellmer can return typed, structured output:
library(ellmer)
chat <- chat_anthropic(model = "claude-opus-4-8")
reviews <- tibble(text = c(
"Loved it, arrived early and works great!",
"Broke after two days. Very disappointed.",
"It's fine. Does the job, nothing special."
))
# ask for a schema; ellmer enforces the shape
sentiment <- type_object(
mood = type_string("one of: positive, negative, neutral"),
reason = type_string("a 5-word justification")
)
reviews |>
mutate(parsed = map(text, ~ chat$chat_structured(.x, type = sentiment))) |>
unnest_wider(parsed)This is purrr + a prompt: text in, tidy columns out. That’s the data-science use.
An LLM generates plausible, not 100% true, text. You should expect:
dplyr::summarise_with() that don’t exist.funs(), old gather()/spread().For any code an LLM gives you:
Tip
NAs, one row, or a weird category.Note
Treat LLMs like very fast, occasionally-wrong collaborators. They’re not to be blindly trusted.
Use whatever LLM you have access to (chat window is fine, no API key needed).
Explain an error. Run this, then ask the LLM to explain and fix it. Verify the fix runs:
(Why NaN/NA? What’s the one-argument fix?)
Generate a function. Ask for an R function that takes a numeric vector and returns its mean, median, and SD as a named tibble. Then break it: feed it c(1, 2, NA) and a length-1 vector. Did the LLM handle those?
Catch a mistake. Find at least one thing the LLM got wrong, outdated, or could improve.
What we covered
ellmer from code.ellmer calls Claude/GPT/Gemini from R; keep API keys in .Renviron; structured output = LLM as a tidy data step.Upcoming