From a54203c186e2cd5a5ae93f2df9feef2c56bcf431 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 10:44:50 +0100 Subject: [PATCH 1/8] init --- modules/browser_reprex/index.qmd | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 modules/browser_reprex/index.qmd diff --git a/modules/browser_reprex/index.qmd b/modules/browser_reprex/index.qmd new file mode 100644 index 0000000..f566c75 --- /dev/null +++ b/modules/browser_reprex/index.qmd @@ -0,0 +1,51 @@ +--- +title: "Debugging with `browser()` and `reprex()`" +description: "" +author: "Etienne Bacher" +date: "2026-06-25" +categories: [r] +format: + html: default + revealjs: + output-file: index-slides.html +execute: + warning: false + message: false + freeze: true +editor: + markdown: + wrap: 72 +--- + +[intro] + + +# Reducing the problem + +The longer the code you try to debug, the more potential causes you have to explore. The first thing to do when you can't find the bug in your code right away is to build a minimal **repr**oducible **ex**ample, aka a *reprex* (this is also known as a MRE, for "Minimal Reproducible Example"). + + +## Reduce the size of the code + + +## Reduce the size of the data + + +## How to ensure that your reprex actually is a reprex + +We have reduced the size of the problem, both in data size and in code size. It seems like we have done everything we could to make the problem easier to reproduce, even for other people (e.g. package developers). However, there is one thing that still needs to be cleaned: our **environment**. + +Our environment may have tons of variables, datasets, and options that were defined before but are not relevant to our problem. Some of those objects may interact in some way with our problematic code, meaning that we need to clean our environment to ensure that we can correctly reproduce the issue. + +One way to do this is to restart the R session, but this can easily be forgotten. A more robust solution is to use the package [`reprex`](https://reprex.tidyverse.org/). This package provides a function called `reprex()` in which we can put all the code related to our problem. `reprex()` will start a separate R session in the background to run this code and then report its results. Since this is a completely separate R session, it will error if our code isn't completely self-contained, and this would indicate that our reproducible example isn't actually... reproducible. + +This is extremely useful to solve the problem yourself, but it is even more useful when reporting a bug to colleagues and package developers: if your example runs in `reprex()`, then it is almost guaranteed that it can be easily reproduced by other people with different environments. + + +# Debugging a function + + +## `browser()` + + +## Breakpoints in the IDE \ No newline at end of file From 39336cc5bad6c07c898249cafea3a6d48cb8cde2 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 13:13:53 +0100 Subject: [PATCH 2/8] move doc --- {modules/browser_reprex => browser_reprex}/index.qmd | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {modules/browser_reprex => browser_reprex}/index.qmd (100%) diff --git a/modules/browser_reprex/index.qmd b/browser_reprex/index.qmd similarity index 100% rename from modules/browser_reprex/index.qmd rename to browser_reprex/index.qmd From a1096ba6887dc02d4c33e7d2d311c2b260373f41 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 13:15:38 +0100 Subject: [PATCH 3/8] fix yaml --- browser_reprex/index.qmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser_reprex/index.qmd b/browser_reprex/index.qmd index f566c75..f952e57 100644 --- a/browser_reprex/index.qmd +++ b/browser_reprex/index.qmd @@ -4,6 +4,7 @@ description: "" author: "Etienne Bacher" date: "2026-06-25" categories: [r] +difficulty: Intermediate format: html: default revealjs: @@ -11,7 +12,7 @@ format: execute: warning: false message: false - freeze: true + freeze: auto editor: markdown: wrap: 72 From 2f37fda8f5bfdc4fa4f718391f9f682e5e90ca02 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 14:03:25 +0100 Subject: [PATCH 4/8] minor --- browser_reprex/R_logo.png | Bin 0 -> 30682 bytes browser_reprex/index.qmd | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 browser_reprex/R_logo.png diff --git a/browser_reprex/R_logo.png b/browser_reprex/R_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5505a9e4c484b2adb7558e283ba8b9ee5f357c GIT binary patch literal 30682 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 Date: Tue, 30 Jun 2026 14:12:00 +0100 Subject: [PATCH 5/8] remove code formatting in title --- browser_reprex/index.qmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser_reprex/index.qmd b/browser_reprex/index.qmd index db2a81d..d026431 100644 --- a/browser_reprex/index.qmd +++ b/browser_reprex/index.qmd @@ -1,5 +1,5 @@ --- -title: "Debugging with `browser()` and `reprex()`" +title: "Debugging with browser() and reprex()" description: "How to efficiently solve bugs." author: "Etienne Bacher" date: "2026-06-25" From 92544c71d43201e51f2baaf5576a231f76cdb00b Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Wed, 1 Jul 2026 14:03:33 +0100 Subject: [PATCH 6/8] more --- browser_reprex/index.qmd | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/browser_reprex/index.qmd b/browser_reprex/index.qmd index d026431..3750040 100644 --- a/browser_reprex/index.qmd +++ b/browser_reprex/index.qmd @@ -43,11 +43,43 @@ One way to do this is to restart the R session, but this can easily be forgotten This is extremely useful to solve the problem yourself, but it is even more useful when reporting a bug to colleagues and package developers: if your example runs in `reprex()`, then it is almost guaranteed that it can be easily reproduced by other people with different environments. +```{r} +#| eval: false +reprex({ + # our code here +}) +``` + # Debugging a function +Debugging a function that you wrote (either in a package or as part of a project), can be annoying because finding the source of the problem can be hard. While in the previous example we could debug line by line, it is less intuitive how to do so in a function. + +One reflex might be to put various `print()` statement in the function body to see which ones get evaluated (meaning that the code ran fine so far) and which ones didn't because the function errored before. + +However, a more robust and convenient approach is to use a function that will stop the evaluation and put us in the evaluating environment: `browser()`. + ## `browser()` +Let's say we have this function: + +```{r} +f <- function() { + +} +``` + +Instead of putting `print()` statements in multiple places, I can add a `browser()` call at the start: + +```diff +f <- function() { ++ browser() +} + +``` + + + ## Breakpoints in the IDE \ No newline at end of file From 9b8b0c2726417372214ffa5aa5b4c0aecc5ed2a0 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Wed, 1 Jul 2026 15:55:18 +0100 Subject: [PATCH 7/8] more --- browser_reprex/index.qmd | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/browser_reprex/index.qmd b/browser_reprex/index.qmd index 3750040..223cda1 100644 --- a/browser_reprex/index.qmd +++ b/browser_reprex/index.qmd @@ -70,15 +70,17 @@ f <- function() { } ``` -Instead of putting `print()` statements in multiple places, I can add a `browser()` call at the start: +Instead of putting `print()` statements in multiple places, we can add a `browser()` call at the start: -```diff +``` diff f <- function() { -+ browser() ++browser() } - ``` +Now, the next time we call `f()`, the execution will stop at `browser()` and we will be able to explore the current environment in the console. We can see exactly the environment the code has access to when it reaches this step, which makes it easier to for us to find the source of the bug. + + From 51f3ad61dfa93482d9a37c60d0c773faa9cc47bc Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Wed, 15 Jul 2026 15:11:31 +0100 Subject: [PATCH 8/8] more in the reprex section --- browser_reprex/index.qmd | 275 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 265 insertions(+), 10 deletions(-) diff --git a/browser_reprex/index.qmd b/browser_reprex/index.qmd index 223cda1..f5182af 100644 --- a/browser_reprex/index.qmd +++ b/browser_reprex/index.qmd @@ -1,11 +1,12 @@ --- -title: "Debugging with browser() and reprex()" +title: "Debugging with reprex() and browser()" description: "How to efficiently solve bugs." author: "Etienne Bacher" date: "2026-06-25" categories: [r] difficulty: Intermediate image: R_logo.png +toc: true format: html: default revealjs: @@ -19,23 +20,183 @@ editor: wrap: 72 --- -[intro] +[ TODO: why is it useful to know how to debug in the age of AI? ] + + +The following code is taken from the ["Latitudinal trends in Phanerozoic reefs" vignette](https://palaeoverse.palaeoverse.org/articles/phanerozoic-reefs.html), but let's pretend we have written this code from scratch: + +```{r, error = TRUE} +library(palaeoverse) + +data(reefs) +reef_counts <- group_apply(occdf = reefs, group = "interval", fun = nrow) +# Assign a common time scale based on an interval key +reefs <- look_up(occdf = reefs, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +reefs <- subset(reefs, interval_max_ma <= 541) +# Extract Phanerozoic stage-level stages for time bins +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs, bins = bins, method = "mid") +``` + +We now have an error, but its cause is not immediately clear. +Therefore, we'll have to do some debugging. # Reducing the problem The longer the code you try to debug, the more potential causes you have to explore. The first thing to do when you can't find the bug in your code right away is to build a minimal **repr**oducible **ex**ample, aka a *reprex* (this is also known as a MRE, for "Minimal Reproducible Example"). +In our example above, we can reduce the problem in two ways: we can reduce the data first, and then reduce the code. + + +## Reduce the size of the data + +In the debugging process, we will likely run the code at least a handful of times. +Therefore, we want to make sure that it runs as fast as possible so that we don't lose our focus. +The easiest way to do this is to reduce the size of our input data. + +In addition to making the error appear faster, it is also easier to compare the actual and expected outputs when the data is as small as possible. + +In this example, the dataset we are using has thousands of rows and 14 columns: + +```{r} +dim(reefs) +``` + +Let's try to reduce the size of our data: + +```{r, error = TRUE} +reefs2 <- head(reefs) + +reef_counts <- group_apply(occdf = reefs2, group = "interval", fun = nrow) +# Assign a common time scale based on an interval key +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +reefs2 <- subset(reefs2, interval_max_ma <= 541) +# Extract Phanerozoic stage-level stages for time bins +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs2, bins = bins, method = "mid") +``` + +We still see the same error but now we only have 6 rows left, much better! +Let's try to reduce that to one row and to remove some columns. + + +```{r, error = TRUE} +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:5] + +reef_counts <- group_apply(occdf = reefs2, group = "interval", fun = nrow) +# Assign a common time scale based on an interval key +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +reefs2 <- subset(reefs2, interval_max_ma <= 541) +# Extract Phanerozoic stage-level stages for time bins +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs2, bins = bins, method = "mid") +``` + +Hmm, we don't have the same error anymore, so we removed too many columns. +Let's try to keep 6 instead of 5: + +```{r, error = TRUE} +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:6] + +reef_counts <- group_apply(occdf = reefs2, group = "interval", fun = nrow) +# Assign a common time scale based on an interval key +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +reefs2 <- subset(reefs2, interval_max_ma <= 541) +# Extract Phanerozoic stage-level stages for time bins +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs2, bins = bins, method = "mid") +``` + +Bingo! We have the same error as before, but now we only have one row and six columns. + +Let's now try to reduce the size of the code. + ## Reduce the size of the code +This is an important process: we want to remove as much code as possible to lower the number of potential causes leading to the error. -## Reduce the size of the data +Starting from this: +```{r, error = TRUE} +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:6] +reef_counts <- group_apply(occdf = reefs2, group = "interval", fun = nrow) +# Assign a common time scale based on an interval key +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +reefs2 <- subset(reefs2, interval_max_ma <= 541) +# Extract Phanerozoic stage-level stages for time bins +bins <- time_bins(interval = "Phanerozoic", rank = "stage") -## How to ensure that your reprex actually is a reprex +bin_time(occdf = reefs2, bins = bins, method = "mid") +``` -We have reduced the size of the problem, both in data size and in code size. It seems like we have done everything we could to make the problem easier to reproduce, even for other people (e.g. package developers). However, there is one thing that still needs to be cleaned: our **environment**. +We see that `reef_counts` is defined but never actually used, so this cannot be the root cause. Let's remove it. + +The `subset()` call is also suspicious: now that he have only one row left in our test data, does this `subset()` actually do anything? + +```{r} +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:6] + +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) + +identical(reefs2, subset(reefs2, interval_max_ma <= 541)) +``` + +It doesn't affect `reefs2`, so let's remove it as well. + +We now have this: +```{r, error = TRUE} +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:6] + +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs2, bins = bins, method = "mid") +``` + +We have removed two out of five function calls, not bad. +We still don't really understand the cause though. + +Let's assume that the documentation also doesn't give any hint about this error message. +If we are completely stuck, then we may want to ask other people, such as package maintainers on Github or people on StackOverflow. We have done our best to reduce the size of the problem, making it easier for external people to help us. + +Still, we need to ensure that our reprex is correctly made and we must find a nice way to share it. + + +# How to ensure that your reprex actually is a reprex + +It seems like we have done everything we could to make the problem easier to reproduce, even for other people (e.g. package developers). However, there is one thing that still needs to be cleaned: our **environment**. Our environment may have tons of variables, datasets, and options that were defined before but are not relevant to our problem. Some of those objects may interact in some way with our problematic code, meaning that we need to clean our environment to ensure that we can correctly reproduce the issue. @@ -43,21 +204,115 @@ One way to do this is to restart the R session, but this can easily be forgotten This is extremely useful to solve the problem yourself, but it is even more useful when reporting a bug to colleagues and package developers: if your example runs in `reprex()`, then it is almost guaranteed that it can be easily reproduced by other people with different environments. +```{r} +reprex::reprex({ + reefs2 <- head(reefs, n = 1) + reefs2 <- reefs2[, 1:6] + + reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) + bins <- time_bins(interval = "Phanerozoic", rank = "stage") + + bin_time(occdf = reefs2, bins = bins, method = "mid") +}) +``` + +``` r +reefs2 <- head(reefs, n = 1) +#> Error: +#> ! object 'reefs' not found +reefs2 <- reefs2[, 1:6] +#> Error: +#> ! object 'reefs2' not found + +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +#> Error in `look_up()`: +#> ! could not find function "look_up" +bins <- time_bins(interval = "Phanerozoic", rank = "stage") +#> Error in `time_bins()`: +#> ! could not find function "time_bins" + +bin_time(occdf = reefs2, bins = bins, method = "mid") +#> Error in `bin_time()`: +#> ! could not find function "bin_time" +``` + +Our reprex fails, but not with the error we experienced so far! +We forgot to add the calls to `library()` and `data()`. + + ```{r} #| eval: false -reprex({ - # our code here +reprex::reprex({ + library(palaeoverse) + data(reefs) + reefs2 <- head(reefs, n = 1) + reefs2 <- reefs2[, 1:6] + + reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) + bins <- time_bins(interval = "Phanerozoic", rank = "stage") + + bin_time(occdf = reefs2, bins = bins, method = "mid") }) ``` +``` r +library(palaeoverse) +data(reefs) +reefs2 <- head(reefs, n = 1) +reefs2 <- reefs2[, 1:6] + +reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) +bins <- time_bins(interval = "Phanerozoic", rank = "stage") + +bin_time(occdf = reefs2, bins = bins, method = "mid") +#> Error in `[.data.frame`: +#> ! undefined columns selected +``` + +This is the situation we had so far, so we can now share this output with other people and they should be able to reproduce the issue. + +Note that it is good practice to show more information on our session, including the version of packages used. This can be done with the argument `si` in `reprex()`: + +```{r} +#| eval: false +reprex::reprex({ + library(palaeoverse) + data(reefs) + reefs2 <- head(reefs, n = 1) + reefs2 <- reefs2[, 1:6] + + reefs2 <- look_up(occdf = reefs2, + early_interval = "interval", + late_interval = "interval", + int_key = interval_key) + bins <- time_bins(interval = "Phanerozoic", rank = "stage") + + bin_time(occdf = reefs2, bins = bins, method = "mid") +}, si = TRUE) +``` + +You now know how to use `reprex()`! Note that the steps before actually using it are key: very often, other people will ask you to minimize your issue as much as possible before reporting it. In many cases, you can also find the issue on your own just by reducing it. + # Debugging a function -Debugging a function that you wrote (either in a package or as part of a project), can be annoying because finding the source of the problem can be hard. While in the previous example we could debug line by line, it is less intuitive how to do so in a function. +Debugging a function that you wrote (either in a package or as part of a project) can be annoying because an entire (internal) block of code gets executed and you only see the final error. -One reflex might be to put various `print()` statement in the function body to see which ones get evaluated (meaning that the code ran fine so far) and which ones didn't because the function errored before. +One reflex is to put various `print()` statement in the function body to see which ones get evaluated (meaning that the code ran fine so far) and which ones didn't because the function errored before. -However, a more robust and convenient approach is to use a function that will stop the evaluation and put us in the evaluating environment: `browser()`. +However, a more robust and convenient approach is to use a function that will stop the evaluation and put us in the evaluating environment: `browser()`. This allows us to run line by line *in the function body*, and it is therefore a key tool when you start writing your own functions. ## `browser()`