The Institute for Evaluation of Labour Market and Education Policy (IFAU)
2026-04-23
Errors everywhere
Debugging
Pre-empt the bugs: fail usefully
IDE code assistance
Agentic development
Programming is a process of constant errors.
Error in if (x) { : missing value where TRUE/FALSE needed
Error: object 'panel_2023' not found
Error in library(httr2) : there is no package called 'httr2'
Error in [ : subscript out of bounds
Your job: debug the code and find the bad assumption.
str() is probably the most useful inspection command in R.
.RDataR offers to save your workspace on exit and reload it on startup
Turn it off.
Let’s inspect:
drop = FALSE forces R to keep the data frame structure:
Sometimes you cannot figure out or resolve the error on your own.
I was planning on talking about Stack Overflow here, but…
Just ask an LLM instead - I’ll teach you how in a few slides!
https://blog.pragmaticengineer.com/stack-overflow-is-almost-dead/
Errors everywhere
Debugging
Pre-empt the bugs: fail usefully
IDE code assistance
Agentic development
traceback() to see the call stack after an errorbrowser() to step through code interactivelydebug(FUN) to run browser() on FUN() callstraceback()# in_class_examples/lecture_5/01_traceback_test.R
prepare_series <- function(x) growth_rates(trimws(x))
growth_rates <- function(x) log_change(x)
log_change <- function(x) diff(log(x))
average_growth <- function(x) mean(prepare_series(x))
average_growth(c("100", "120", "oops", "150"))
f <- function(x) {
apply(x, 1, mean)
}Error in log(x) : non-numeric argument to mathematical function
traceback() prints the call stackThe error message tells you what went wrong; the traceback helps you locate where the error occurred.
traceback() (cont.)Calls: source ... prepare_series -> growth_rates -> log_change -> diff
11: (function ()
traceback(2))()
10: diff(log(x))
9: log_change(x)
8: growth_rates(trimws(x))
7: prepare_series(x)
6: mean(prepare_series(x))
5: average_growth(c("100", "120", "oops", "150"))
4: eval(ei, envir)
3: eval(ei, envir)
2: withVisible(eval(ei, envir))
1: source("/home/runner/work/datascience-course/datascience-course/in_class_examples/lecture_5/01_traceback_test.R",
chdir = TRUE)
traceback()# in_class_examples/lecture_5/02_traceback_realistic.R
get_municipality_history <- function(panel, municipality_code) {
panel[panel$municipality_code == municipality_code, , drop = FALSE]
}
get_latest_rate <- function(municipality_panel) {
latest_year <- max(municipality_panel$year)
municipality_panel[
municipality_panel$year == latest_year,
"unemployment_rate"
][[1]]
}
get_reference_rate <- function(municipality_panel, reference_year) {
reference_rate <- municipality_panel[
municipality_panel$year == reference_year,
"unemployment_rate"
]
reference_rate[[1]]
}
compute_change_from_reference <- function(municipality_panel, reference_year) {
latest_rate <- get_latest_rate(municipality_panel)
reference_rate <- get_reference_rate(municipality_panel, reference_year)
latest_rate - reference_rate
}
build_municipality_report <- function(
panel,
municipality_code,
reference_year
) {
municipality_panel <- get_municipality_history(panel, municipality_code)
change_pp <- compute_change_from_reference(municipality_panel, reference_year)
data.frame(
municipality_code = municipality_code,
municipality_name = municipality_panel$municipality_name[1],
reference_year = reference_year,
latest_year = max(municipality_panel$year),
change_pp = change_pp
)
}
panel <- read.csv(
here::here("in_class_examples/lecture_5/data/mini_panel.csv"),
colClasses = c(municipality_code = "character")
)
build_municipality_report(
panel,
municipality_code = "0180",
reference_year = 2020
)Error in reference_rate[[1]] : subscript out of bounds
traceback() (cont.)Calls: source ... compute_change_from_reference -> get_reference_rate
8: (function ()
traceback(2))()
7: get_reference_rate(municipality_panel, reference_year)
6: compute_change_from_reference(municipality_panel, reference_year)
5: build_municipality_report(panel, municipality_code = "0180",
reference_year = 2020)
4: eval(ei, envir)
3: eval(ei, envir)
2: withVisible(eval(ei, envir))
1: source("/home/runner/work/datascience-course/datascience-course/in_class_examples/lecture_5/02_traceback_realistic.R",
chdir = TRUE)
traceback() shows you where the error happenedprint() objects at critical lines inside functionsbrowser() to step through the code interactively municipality_code municipality_name year unemployment_rate risk_group
3 0114 Upplands Väsby 2023 12.1 low
6 0180 Stockholm 2023 7.8 high
9 0184 Solna 2023 10.0 high
12 0380 Uppsala 2023 10.6 low
Notice anything wrong?
browser() to step through the code interactivelyDrop a call to browser() into the function where things look suspicious. When R hits it, execution pauses and the prompt changes to Browse[1]>. From there:
n: next expressions: step into (function)f: finish current functionc: continue until the end or the next breakpointQ: quitdebug()debug(<function_name>) browses the function every time it’s calleddebugonce(<function_name>) enters browser() only on the next callundebug(<function_name>) turns debugging offbrowser() calls. Same underlying tools.recoverErrors everywhere
Debugging
Pre-empt the bugs: fail usefully
IDE code assistance
Agentic development
Do you know what the code is supposed to do? Why not verify that it actually does?
stop(), warning(), message()Use:
stop() to generate errorwarning() to warn about risky statemessage() to inform about progressReading data/mini_panel.csv
Error:
! Can't find data/mini_panel.csv
browser()Issue stop() or warning() when:
Debugging
Testing
After a bug fix, add the smallest check that would have caught it earlier.
Errors everywhere
Debugging
Pre-empt the bugs: fail usefully
IDE code assistance
Agentic development
any(is.na(x)) → suggests the faster anyNA(x)return(), stop(), or breakCopilot suggests the rest in greyTab to accept, Esc to dismiss, or keep typing and the suggestion adaptsgives you a suggestion like:
💡 (or press Cmd/Ctrl + I) to ask Copilot to modify or reviewErrors everywhere
Debugging
Pre-empt the bugs: fail usefully
IDE code assistance
Agentic development
A language model given tools: read files, write files, run commands, read the output — then loop.
air, jarl, CI — the agent sees failures and corrects itselfGood context has four parts:
Under the hood, agents are just LLMs in a loop — their reasoning is only as good as the context you give them.
AGENTS.md: house rules for the repoAgents read instruction files before acting. Tell them which packages you prefer, how the code is structured, how to run the tests.
<!-- in_class_examples/lecture_5/agent-example/AGENTS.md -->
# Agent guide
A small playground for practicing Copilot Chat in **agent mode** inside VS Code
or Positron. Each file in `scripts/` has a comment block at the top with the
task and a suggested prompt.
## How this repo steers the agent
Three files tell the agent how to behave here. Open them, read them, edit them
and watch the behaviour change:
- `AGENTS.md` — this file. Read by most agents (Copilot, Claude, Codex, Cursor).
- `.github/copilot-instructions.md` — included automatically in every Copilot chat.
- `.github/instructions/r-scripts.instructions.md` — scoped to `scripts/**/*.R` via `applyTo`.
## Rules for the agent
- Stay in base R unless the script already imports another package.
- Keep diffs small. Do not rewrite whole scripts or rename files.
- Before proposing a fix, name the *first bad assumption* in one or two sentences.
- Use `stop()`, `warning()`, `message()` for guards — not `assertthat` or `assertr`.
- Use `testthat` for tests. One `test_that(...)` block per behaviour.
- Never invent packages or functions. If unsure, say so.
- If the task is vague, ask for the smallest useful next step instead of guessing.
## How students should use this
1. Open this folder as its own workspace in VS Code or Positron.
2. Open Copilot Chat and switch to **Agent** mode.
3. Work through `scripts/01_…` to `scripts/04_…` in order.
4. For each: read the comment block, try the suggested prompt, read the diff
before accepting, then run the code.AGENTS.md doesn’t have to say everything — it can point.
.RprofileA skill is a reusable bundle of instructions (± helper scripts) for a specific workflow the agent should run the same way every time.
The agent picks up the skill when the task matches — you stop re-explaining the same workflow in every prompt.
Assume anything you paste may be logged or used for training.
01_traceback_test.RassertrFail-fast style. Halts the pipeline on the first bad row.
verify(): one logical expression — row counts, column namesassert() / insist(): predicate / data-driven bounds per columnvalidateReport style. Define rules, confront the data, inspect — nothing crashes.
testthatValidation checks data. Tests check code. Remember the silent bug in classify_unemployment()?
── Failure: high unemployment is classified as high risk ─────────────────────────────────
Expected `result$risk_group` to equal "high".
Differences:
1/1 mismatches
x[1]: "low"
y[1]: "high"
Error:
! Test failed with 1 failure and 0 successes.
testthat (cont.)testthat::test_file() — works in plain projects, not just packagesAutomatic checks on every change. Like a spell-checker for your code, but it can run tests and linters too.
usethis::use_github_action("check-standard") for packages