The Modern R Toolchain (2026)
In Lessons 1 and 2, Maria made her sales-analysis project portable and reproducible: the paths work on any machine, and the package versions are pinned so it computes the same answer next year. The project is solid. But the work itself just got harder.
Her company grew. Last year sales.csv had a few hundred rows. This year the orders sit in files with millions of rows, too big to open the old way. The finance team wants revenue reported in euros, which means pulling live exchange rates off the web. And there are thousands of free-text product names nobody has time to categorize by hand.
None of that is what R was famous for ten years ago. All of it is comfortable today, because a handful of newer tools have grown up around R. This lesson is a guided tour of that 2026 toolchain, organized around the four jobs every modern analysis needs to do.
By the end of this lesson you will be able to:
- Name the four jobs of a modern R workflow and the tool that does each one
- Pull data from a web API into R by parsing its JSON response
- Query data too big to load into memory by running SQL through duckdb
- Call a language model from R with ellmer, and judge when its answer can be trusted
Prerequisites: you have a reproducible project from Lessons 1 and 2, you can run R and call a function, and you can install and load a package. The four jobs below are the whole tour.
The editor: Positron
Every analysis starts in an editor, the program you actually type and run R in. For years that was RStudio, and RStudio is still excellent. But in 2026 the same company (Posit) ships a newer editor called Positron, built for the way data work is done now.
Think of Positron as the RStudio idea rebuilt on the engine behind VS Code, the most widely used code editor in the world. Concretely, that buys Maria three things:
- One editor for R and Python. Her teammate writes Python; Maria writes R. Positron runs both in the same window, so a project that mixes the two languages does not need two tools.
- A Data Explorer. Click a data frame and a spreadsheet-like view opens, where she can sort, filter and scan her
ordersdata without writing a line of code to peek at it. - The VS Code extension ecosystem. Themes, Git tools, AI helpers and more, the same add-ons millions of developers already use, work inside Positron.
Pulling data from the web with httr2
Maria needs today's USD-to-EUR exchange rate, and it lives on the web behind an API (an Application Programming Interface). An API is just a web address you can send a request to and get structured data back, instead of a human-readable page. You ask a specific endpoint (one address for one kind of data, here "give me the latest rates"), and it answers.
What it answers with is almost always JSON (JavaScript Object Notation): plain text that writes data as nested name: value pairs, like a labeled box of boxes. The httr2 package is the modern R tool for building and sending that web request. A real call needs a network connection, so here is the shape of it rather than a live run:
library(httr2)
# httr2 builds a request, sends it over the network, and hands back the response.
resp <- httr2::request("https://api.example.com/rates") |>
httr2::req_url_query(base = "USD") |>
httr2::req_perform()
body <- httr2::resp_body_json(resp) # parse the JSON body into an R list
body$rates$EUR
#> [1] 0.92
The response comes back as JSON text. To use it in R you parse it: turn the text into a real R list you can index. The jsonlite package does exactly that, and we can run it for real on a response pasted in as text (no network needed):
Now 0.92 is an ordinary R number Maria can multiply her revenue by.
What does parsing give you?
Maria calls the currency endpoint and gets back the text {"base":"USD","rates":{"EUR":0.92}}. She runs rates <- fromJSON(response_text). Why is that parsing step necessary, instead of just using the text directly?
fromJSON() converts the JSON text into a nested R list, so rates$rates$EUR is the actual number 0.92, ready to multiply, store, or chart.Querying big data with duckdb
Maria's orders no longer fit comfortably in memory. The old habit, "read the whole file into a data frame, then summarize," falls over when the file has tens of millions of rows. Two tools fix this, and they work together.
First the file format. A plain CSV stores data row by row, as text, so to total one column you still have to read every row of every column. Parquet stores data columnar (each column together) and compressed, so a tool can read just the columns a question needs. The arrow package reads and writes Parquet, and crucially open_dataset() does not load the file, it opens a handle to data still sitting on disk:
library(arrow)
# Parquet is a compact, columnar file format. arrow reads and writes it.
arrow::write_parquet(orders, "orders.parquet")
big <- arrow::open_dataset("orders.parquet") # a handle to data still on disk, not loaded
nrow(big) # counts the rows without loading them; the same call scales to millions
#> [1] 12
Second the engine. duckdb is an analytics database that runs in-process: there is no separate server to install or start, it lives inside your R session. You hand it data (a data frame, or a Parquet file it reads straight off disk) and ask questions in SQL, the standard language for querying tables. Here is the whole loop on Maria's orders. First we build the data inline so this page is self-contained:
Now point duckdb at it and ask, in SQL, for total revenue per region. SELECT chooses the columns, GROUP BY collapses the rows into one per region, and SUM(revenue) totals each group:
The win is that duckdb did the grouping itself, reading only what it needed. On a real Parquet file with millions of rows the exact same SQL returns in a blink, without ever loading the file into R.
Group by a different column
Maria's next question is about products, not regions: how many units of each product did the company sell in total? The connection con and the orders table are still live from the query above. Fill in the GROUP BY so the totals come out one row per product.
Show answer
dbGetQuery(con, "
SELECT product, SUM(units) AS total_units
FROM orders
GROUP BY product
ORDER BY total_units DESC
")
#> product total_units
#> 1 Mouse 12
#> 2 Keyboard 11
#> 3 Monitor 4
#> 4 Laptop 3
dbDisconnect(con, shutdown = TRUE) # close the database when you are doneTalking to a language model with ellmer
Maria's last problem is the thousands of free-text product names she needs to sort into categories like Hardware or Accessory. A large language model (the kind of AI behind chat assistants) is good at exactly this sort of fuzzy, language-shaped labeling. The ellmer package lets her send a prompt (the instruction and text she wants handled) to a model and get its response back, all from inside R. A real call needs an API key and a network connection, so here is the shape:
library(ellmer)
# ellmer sends a prompt to a large language model from R (needs an API key + network).
chat <- ellmer::chat_anthropic()
chat$chat("In one word, classify this product as Hardware or Accessory: Wireless Mouse")
#> [1] "Accessory"
Run that across her product list and she has a draft category for every row, in minutes. ellmer can also ask for structured output (a table of fields rather than free text), which is how you would label thousands of rows reliably.
How should Maria use the labels?
ellmer returns a Hardware-or-Accessory label for all 4,000 product names in a few minutes. What is the responsible way to use those labels in her analysis?
One project, four modern tools
Step back and look at what Maria built. The reproducible project from Lessons 1 and 2 is the foundation; on top of it, each job of the new, harder workload has a sharp 2026 tool:
- Positron is where she writes and runs the project, R and Python in one editor.
- httr2 (with jsonlite) pulls live data off the web, and arrow reads files far too big to open.
- duckdb answers questions over millions of rows in plain SQL, without loading them.
- ellmer hands the language-shaped work to a model, which she then verifies.
References
A few authoritative, free places to take each tool further:
- Positron documentation (Posit) - the official guide to installing and using the Positron editor.
- httr2 package site - the official reference for building and sending web requests from R.
- duckdb R client documentation - how to connect, write tables, and run SQL from R, with examples.
- Arrow for R - reading and writing Parquet and querying datasets larger than memory.
- ellmer package site (tidyverse) - the official guide to calling language models from R, including structured output.
Lesson 3 complete
You toured the modern R toolchain and, more importantly, the four jobs it is organized around. Positron is the editor that runs R and Python side by side. httr2 and jsonlite bring data in from web APIs; arrow reads files too big to open. duckdb queries millions of rows with plain SQL without loading them. And ellmer brings a language model into R for the fuzzy, language-shaped work, as a draft you verify, never as gospel. All of it sits on the portable, reproducible project you built in Lessons 1 and 2.
Next, Lesson 4: The Capstone, a reproducible analysis. You will tie the whole course together in one project that imports, tidies, analyzes and reports, reproducibly from start to finish, using exactly the workflow you have built up lesson by lesson.