From ef6ae48e66cc0af6d2ab18956740b4070554fe82 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 23 Jun 2026 11:02:04 +0100 Subject: [PATCH 1/7] init --- modules/writing_functions/index.qmd | 252 ++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 modules/writing_functions/index.qmd diff --git a/modules/writing_functions/index.qmd b/modules/writing_functions/index.qmd new file mode 100644 index 0000000..f7fbaf3 --- /dev/null +++ b/modules/writing_functions/index.qmd @@ -0,0 +1,252 @@ +--- +title: "Writing functions" +description: "" +author: "Etienne Bacher" +date: "2026-06-18" +categories: [r, package development] +format: + html: default + revealjs: + output-file: index-slides.html +execute: + warning: false + message: false + freeze: true +editor: + markdown: + wrap: 72 +--- + +# Functions in R + +If you have performed any kind of analysis with R, you have used functions. +They are everywhere and you can't do anything without them. +R comes with hundreds of functions by default, and thousands more are available via user-written packages. + +It is possible to create entire R projects without ever needing to write your own functions. +However, knowing how to write a custom function can be extremely useful. + +# Demo: standardizing values + +Let's say that you want to [standardize values](https://en.wikipedia.org/wiki/Standard_score), i.e. run this formula on multiple columns in your data: + +$$z = \frac{x - mean(x)}{sd(x)}$$ + +We could run this by hand on a list of columns in our data: + +```{r} +iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - + mean(iris[["Petal.Length"]])) / + sd(iris[["Petal.Length"]]) +iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - + mean(iris[["Sepal.Length"]])) / + sd(iris[["Sepal.Length"]]) +iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - mean(iris[["Sepal.Width"]])) / + sd(iris[["Petal.Width"]]) +iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - mean(iris[["Sepal.Width"]])) / + sd(iris[["Sepal.Width"]]) +``` + +This works, but what if we want to ignore missing values? We have to add 8 parameters: + +```{r} +iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - + mean(iris[["Petal.Length"]], na.rm = TRUE)) / + sd(iris[["Petal.Length"]], na.rm = TRUE) +iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - + mean(iris[["Sepal.Length"]], na.rm = TRUE)) / + sd(iris[["Sepal.Length"]], na.rm = TRUE) +iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - + mean(iris[["Sepal.Width"]], na.rm = TRUE)) / + sd(iris[["Petal.Width"]], na.rm = TRUE) +iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - + mean(iris[["Sepal.Width"]], na.rm = TRUE)) / + sd(iris[["Sepal.Width"]], na.rm = TRUE) +``` + +Do you notice anything weird about the code in the two code blocks above? + +Look again... + +On the third line, we used `mean(iris$Sepal.Width)` instead of `mean(iris$Petal.Width)`! By duplicating lines of code with very small variations, we make it harder to notice mistakes like this one, small in size but with important consequences for our results. + +Let's write a function instead of repeating this formula four times. + + +## The most basic function + +We start with the template of a function: + +```{r} +my_std <- function() {} +``` + +- `my_std` is the **function name**, meaning that we can use it with `my_std()`; +- `function()` is the **function definition**. In the next steps we will add **function arguments** (or **function parameters**) in `()`; +- `{}` contains the **function body**. This is where we will add the code that runs every time we call the function. + +For now, this function is useless: + +```{r} +my_std() +``` + +Let's add a simple message: + +```{r} +my_std <- function() { + print("Hello from my_std()!") +} + +my_std() +``` + +This doesn't take any user input, so it always does the same thing, which is not very interesting for us. +Let's add function parameters! + + +## Introducing function parameters + +The body of a function never changes, the variation comes from the inputs passed by the user, *aka* function parameters. Therefore, to move existing code into a function, we need to identify its moving components and its "stable" components. + +In the code above, the parts in [red]{style="color:red;"} change across lines and the rest stays identical across lines: + +
+
+

iris[["Petal.Length"]] <- (iris[["Petal.Length"]] - mean(iris[["Petal.Length"]])) / sd(iris[["Petal.Length"]]) +iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - mean(iris[["Sepal.Length"]])) / sd(iris[["Sepal.Length"]]) +iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - mean(iris[["Petal.Width"]])) / sd(iris[["Petal.Width"]]) +iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - mean(iris[["Sepal.Width"]])) / sd(iris[["Sepal.Width"]])

+
+
+ +These red parts indicate the column name used in the computation, which will become our function parameter: + +```{r} +my_std <- function(column) {} +``` + + +## Adding the function body + +We added the moving parts as function parameters, now we need to move the stable parts in the function body, replacing the moving parts by the name of the new function parameter: + +```{r} +my_std <- function(column) { + (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / + sd(iris[[column]], na.rm = TRUE) +} +``` + +Note that we don't assign the output *inside* the function: the function merely makes the computation and we assign its output to our object when we call the function: + +```{r} +iris[["Petal.Length_std"]] <- my_std("Petal.Length") +iris[["Sepal.Length_std"]] <- my_std("Sepal.Length") +iris[["Petal.Width_std"]] <- my_std("Petal.Width") +iris[["Sepal.Width_std"]] <- my_std("Sepal.Width") +``` + +Notice how it is much easier to check whether we made a typo in this code. + + +## Validating function parameters + +The user can now pass custom column names to the function, but this also means that they can pass wrong values! +This is what happens if they pass a column name that doesn't exist in the data: +```{r, warning = TRUE} +my_std("non_existing_column") +``` + +Note that this doesn't error, it just throws a warning and returns a useless result. +To prevent that, we should add some validation steps in our function body to ensure that we don't run nonsensical code and that we clearly tell the user when they have given wrong inputs to the function. +It is good to first list all the requirements of the input so that we can then add one check per requirement. In our case: + +- the column should be a single value, e.g. `my_std(c("Sepal.Length", "Petal.Width"))` should fail; +- the column should be a character value, e.g. `my_std(1)` should fail; +- the column should exist in the data, e.g. `my_std("column_1")` should fail. + +Now that we have clarified are requirements, we can add one `if()` condition for each of them: + +```{r} +my_std <- function(column) { + if (length(column) != 1) { + stop("The `column` parameter must be of length 1.") + } + if (!is.character(column)) { + stop("The `column` parameter must be a character.") + } + if (!(column %in% names(iris))) { + stop("Column '", column, "' doesn't exist in the data.") + } + + (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / + sd(iris[[column]], na.rm = TRUE) +} +``` + +And now we have proper error messages and avoid nonsensical results! + +```{r, error = TRUE} +my_std(c("Sepal.Length", "Petal.Width")) +my_std(1) +my_std("column_1") +``` + +[Maybe mention in a quarto callout that there are many packages to simplify the checks above, e.g. `rlang`, `checkmate`, `dreamerr`, etc.] + + +## Setting default value for parameters + +So far, we used `na.rm = TRUE` in the function body but maybe we also want to let the user determine this option. +We can add a function parameter `na.rm` and use its value in `mean()` and `sd()`: + + +```{r} +my_std <- function(column, na.rm) { + # [parameter checks] + + (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) / + sd(iris[[column]], na.rm = na.rm) +} +``` + +*For conciseness, I hid the parameter checks we have written above.* + +This works fine but it forces the user to explicitly pass the parameter when calling the function, no matter whether it is `TRUE` or `FALSE`: + +```{r} +head(my_std("Sepal.Length", na.rm = FALSE)) +``` + +This isn't a bad approach since it forces the user to be explicit, but let's say we want to follow the `mean()` function and never remove `NA` by default. +We can set the default value in the function definition: + +```{r} +my_std <- function(column, na.rm = FALSE) { + # [parameter checks] + + (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) / + sd(iris[[column]], na.rm = na.rm) +} +``` + +and then this default value is implicitly used (i.e. we don't need to write it in the call): +```{r} +head(my_std("Sepal.Length")) +``` + + +## Exiting a function early + +## Documenting functions + +# Conclusion + +Now that you have a function that does something useful, maybe you want to share it with colleagues. +You could put it in an `.R` file and send it via email, but what happens if you notice a small mistake and want to fix it in the future? +You'd have to tell all the people to which you sent the original function. +This is fine if you shared it with a couple of colleagues, but what if they also shared it with other people? + +The best way to share functions with other people is to wrap them in a package. +This might sound scary but you're in luck, we have an [entire module dedicated to package development]()! \ No newline at end of file From 0513284c319ca57f76b0cba2a24a39db5e3572d1 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 23 Jun 2026 11:10:39 +0100 Subject: [PATCH 2/7] minor --- modules/writing_functions/index.qmd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/writing_functions/index.qmd b/modules/writing_functions/index.qmd index f7fbaf3..9f1e5ba 100644 --- a/modules/writing_functions/index.qmd +++ b/modules/writing_functions/index.qmd @@ -241,6 +241,9 @@ head(my_std("Sepal.Length")) ## Documenting functions + +[ Mention the [Tidyverse design](https://design.tidyverse.org/) book ] + # Conclusion Now that you have a function that does something useful, maybe you want to share it with colleagues. From 7f8679297c3127f24b663686133eb65a54a71376 Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 13:14:39 +0100 Subject: [PATCH 3/7] move doc --- {modules/writing_functions => writing_functions}/index.qmd | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {modules/writing_functions => writing_functions}/index.qmd (100%) diff --git a/modules/writing_functions/index.qmd b/writing_functions/index.qmd similarity index 100% rename from modules/writing_functions/index.qmd rename to writing_functions/index.qmd From 998bc70eec7a9c106cd38bdbefeb9277879b9b2d Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 13:15:06 +0100 Subject: [PATCH 4/7] fix yaml --- writing_functions/index.qmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/writing_functions/index.qmd b/writing_functions/index.qmd index 9f1e5ba..460c763 100644 --- a/writing_functions/index.qmd +++ b/writing_functions/index.qmd @@ -4,6 +4,7 @@ description: "" author: "Etienne Bacher" date: "2026-06-18" categories: [r, package development] +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 d40237e3c272373b22adb01e948db548eb7b648a Mon Sep 17 00:00:00 2001 From: etiennebacher Date: Tue, 30 Jun 2026 14:04:41 +0100 Subject: [PATCH 5/7] add placeholder image and description --- writing_functions/R_logo.png | Bin 0 -> 30682 bytes writing_functions/index.qmd | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 writing_functions/R_logo.png diff --git a/writing_functions/R_logo.png b/writing_functions/R_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5505a9e4c484b2adb7558e283ba8b9ee5f357c GIT binary patch literal 30682 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 Date: Wed, 15 Jul 2026 10:23:58 +0100 Subject: [PATCH 6/7] callout on packages for input checking --- writing_functions/index.qmd | 82 +++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/writing_functions/index.qmd b/writing_functions/index.qmd index 0f3a3d5..f3cad6b 100644 --- a/writing_functions/index.qmd +++ b/writing_functions/index.qmd @@ -37,11 +37,9 @@ $$z = \frac{x - mean(x)}{sd(x)}$$ We could run this by hand on a list of columns in our data: ```{r} -iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - - mean(iris[["Petal.Length"]])) / +iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - mean(iris[["Petal.Length"]])) / sd(iris[["Petal.Length"]]) -iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - - mean(iris[["Sepal.Length"]])) / +iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - mean(iris[["Sepal.Length"]])) / sd(iris[["Sepal.Length"]]) iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - mean(iris[["Sepal.Width"]])) / sd(iris[["Petal.Width"]]) @@ -52,25 +50,21 @@ iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - mean(iris[["Sepal.Width"]] This works, but what if we want to ignore missing values? We have to add 8 parameters: ```{r} -iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - - mean(iris[["Petal.Length"]], na.rm = TRUE)) / +iris[["Petal.Length_std"]] <- (iris[["Petal.Length"]] - mean(iris[["Petal.Length"]], na.rm = TRUE)) / sd(iris[["Petal.Length"]], na.rm = TRUE) -iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - - mean(iris[["Sepal.Length"]], na.rm = TRUE)) / +iris[["Sepal.Length_std"]] <- (iris[["Sepal.Length"]] - mean(iris[["Sepal.Length"]], na.rm = TRUE)) / sd(iris[["Sepal.Length"]], na.rm = TRUE) -iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - - mean(iris[["Sepal.Width"]], na.rm = TRUE)) / +iris[["Petal.Width_std"]] <- (iris[["Petal.Width"]] - mean(iris[["Sepal.Width"]], na.rm = TRUE)) / sd(iris[["Petal.Width"]], na.rm = TRUE) -iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - - mean(iris[["Sepal.Width"]], na.rm = TRUE)) / +iris[["Sepal.Width_std"]] <- (iris[["Sepal.Width"]] - mean(iris[["Sepal.Width"]], na.rm = TRUE)) / sd(iris[["Sepal.Width"]], na.rm = TRUE) ``` -Do you notice anything weird about the code in the two code blocks above? +Do you notice anything weird about the code in the previous two blocks? Look again... -On the third line, we used `mean(iris$Sepal.Width)` instead of `mean(iris$Petal.Width)`! By duplicating lines of code with very small variations, we make it harder to notice mistakes like this one, small in size but with important consequences for our results. +On the third line, we used `mean(iris[["Sepal.Width"]])` instead of `mean(iris[["Petal.Width"]])`! By duplicating lines of code with very small variations, we make it harder to notice mistakes like this one, small in size but with important consequences for our results. Let's write a function instead of repeating this formula four times. @@ -83,7 +77,7 @@ We start with the template of a function: my_std <- function() {} ``` -- `my_std` is the **function name**, meaning that we can use it with `my_std()`; +- `my_std` is the **function name**, meaning that we can use it like `my_std()`; - `function()` is the **function definition**. In the next steps we will add **function arguments** (or **function parameters**) in `()`; - `{}` contains the **function body**. This is where we will add the code that runs every time we call the function. @@ -111,7 +105,7 @@ Let's add function parameters! The body of a function never changes, the variation comes from the inputs passed by the user, *aka* function parameters. Therefore, to move existing code into a function, we need to identify its moving components and its "stable" components. -In the code above, the parts in [red]{style="color:red;"} change across lines and the rest stays identical across lines: +In the code below, the parts in [red]{style="color:red;"} change across lines and the rest stays identical across lines:
@@ -135,8 +129,7 @@ We added the moving parts as function parameters, now we need to move the stable
 
 ```{r}
 my_std <- function(column) {
-  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) /
-    sd(iris[[column]], na.rm = TRUE)
+  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / sd(iris[[column]], na.rm = TRUE)
 }
 ```
 
@@ -157,7 +150,7 @@ Notice how it is much easier to check whether we made a typo in this code.
 The user can now pass custom column names to the function, but this also means that they can pass wrong values!
 This is what happens if they pass a column name that doesn't exist in the data:
 ```{r, warning = TRUE}
-my_std("non_existing_column")
+my_std("unknown_column")
 ```
 
 Note that this doesn't error, it just throws a warning and returns a useless result.
@@ -182,8 +175,7 @@ my_std <- function(column) {
     stop("Column '", column, "' doesn't exist in the data.")
   }
 
-  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) /
-    sd(iris[[column]], na.rm = TRUE)
+  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / sd(iris[[column]], na.rm = TRUE)
 }
 ```
 
@@ -195,7 +187,47 @@ my_std(1)
 my_std("column_1")
 ```
 
-[Maybe mention in a quarto callout that there are many packages to simplify the checks above, e.g. `rlang`, `checkmate`, `dreamerr`, etc.]
+::: {.callout-note collapse="true" title="Packages for input checking"}
+Several packages provide functions to easily check inputs and report useful error messages. 
+There are too many to list all of them here, but you could for instance look at [`rlang`](https://rlang.r-lib.org/reference/index.html#function-arguments) and [`checkmate`](https://mllg.github.io/checkmate/).
+
+#### With `rlang`
+
+```{r, error = TRUE}
+### With rlang
+my_std <- function(column) {
+  rlang::check_string(column)
+  if (!(column %in% names(iris))) {
+    stop("Column '", column, "' doesn't exist in the data.")
+  }
+
+  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / sd(iris[[column]], na.rm = TRUE)
+}
+
+my_std(c("Sepal.Length", "Petal.Width"))
+my_std(1)
+my_std("column_1")
+```
+
+#### With `checkmate`
+
+```{r, error = TRUE}
+my_std <- function(column) {
+  checkmate::assert_string(column)
+  checkmate::assert_subset(column, names(iris))
+
+  (iris[[column]] - mean(iris[[column]], na.rm = TRUE)) / sd(iris[[column]], na.rm = TRUE)
+}
+
+my_std(c("Sepal.Length", "Petal.Width"))
+my_std(1)
+my_std("column_1")
+
+```
+
+See also [`dreamerr`](https://lrberge.github.io/dreamerr/), [`arg`](https://ngreifer.github.io/arg/), [`chk`](https://poissonconsulting.github.io/chk/), and more!
+
+:::
 
 
 ## Setting default value for parameters
@@ -208,8 +240,7 @@ We can add a function parameter `na.rm` and use its value in `mean()` and `sd()`
 my_std <- function(column, na.rm) {
   # [parameter checks]
 
-  (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) /
-    sd(iris[[column]], na.rm = na.rm)
+  (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) / sd(iris[[column]], na.rm = na.rm)
 }
 ```
 
@@ -228,8 +259,7 @@ We can set the default value in the function definition:
 my_std <- function(column, na.rm = FALSE) {
   # [parameter checks]
 
-  (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) /
-    sd(iris[[column]], na.rm = na.rm)
+  (iris[[column]] - mean(iris[[column]], na.rm = na.rm)) / sd(iris[[column]], na.rm = na.rm)
 }
 ```
 

From 92f012c145cb2f6747567739ea4ee532e3d2d961 Mon Sep 17 00:00:00 2001
From: etiennebacher 
Date: Wed, 15 Jul 2026 11:07:49 +0100
Subject: [PATCH 7/7] more

---
 writing_functions/index.qmd | 62 +++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 3 deletions(-)

diff --git a/writing_functions/index.qmd b/writing_functions/index.qmd
index f3cad6b..c724729 100644
--- a/writing_functions/index.qmd
+++ b/writing_functions/index.qmd
@@ -6,6 +6,7 @@ date: "2026-06-18"
 categories: [r, package development]
 difficulty: Intermediate
 image: R_logo.png
+toc: true
 format:
   html: default
   revealjs:
@@ -271,17 +272,72 @@ head(my_std("Sepal.Length"))
 
 ## Exiting a function early
 
+Sometimes, we want to return an object early on instead of executing the rest of the function.
+
+For example, suppose we have a function where we want to return `NA` if any of the input values is `NA`. 
+In this case, we could use `return()` in an `if` condition to skip the rest of the function:
+
+```{r}
+f <- function(x) {
+
+  if (anyNA(x)) {
+    return(NA)
+  }
+
+  # if x doesn't contain NA, then we continue the function here...
+}
+```
+
+Note that the last value in the function is implicitly returned, so you don't need to wrap it in `return()` (but you can if you want, it's just a matter of preferences).
+Note that if the last statement in the function is an assignment, then the function returns the value of this assignment but doesn't print it when you call it:
+
+```{r}
+# Returns the value of `x` and prints it
+f <- function() {
+  x <- 1
+  x
+}
+f()
+result_1 <- f()
+
+# Also returns the value of `x`, but doesn't print it!
+f <- function() {
+  x <- 1
+}
+f()
+result_2 <- f()
+
+identical(result_1, result_2)
+```
+
+
 ## Documenting functions
 
+[UNSURE: this could fit better in the package development module]
 
-[ Mention the [Tidyverse design](https://design.tidyverse.org/) book ]
+If you plan to keep using custom functions in your project, or if you want to share those with colleagues, then it is important to write documentation.
+The documentation should include:
+
+- an explanation of the function's purpose
+- an explanation of each function parameter, including the expected type (e.g. "a character value of length 1")
+- details on the expected output
+
+# Going further
+
+Designing functions and ensuring their robustness is not trivial.
+There are many choices one can make, such as using positional or named arguments, harmonising parameter names, and whether to use default values.
+
+If you want to explore more, we recommend reading the [Tidyverse design](https://design.tidyverse.org/) book. 
+While the primary audience is package developers, a lot of advice there apply to any custom function.
 
 # Conclusion
 
+In this module, we have seen how to convert existing, duplicated code into a function. 
+
 Now that you have a function that does something useful, maybe you want to share it with colleagues. 
 You could put it in an `.R` file and send it via email, but what happens if you notice a small mistake and want to fix it in the future? 
 You'd have to tell all the people to which you sent the original function.
 This is fine if you shared it with a couple of colleagues, but what if they also shared it with other people?
 
-The best way to share functions with other people is to wrap them in a package. 
-This might sound scary but you're in luck, we have an [entire module dedicated to package development]()!
\ No newline at end of file
+The best way to share functions with other people is to wrap them in an R package. 
+This might sound intimidating but don't worry, we have an [entire module dedicated to package development]()!
\ No newline at end of file