calculate_bmi <- function(weight, height) {
bmi <- weight / (height^2)
return(bmi)
}
calculate_bmi(weight = 70, height = 1.75)[1] 22.85714
EE BIOL C177/C234
{ }When should you write a function? Look for these three signs:
Tip
Writing functions helps you avoid manual errors (e.g., forgetting to update one value in a copy-pasted block).
Here is a simple function to calculate Body Mass Index (BMI):
In R, the value of the last line evaluated in a function is automatically returned.
Therefore, you can omit return():
Note
Pro Tip: The Tidyverse style guide recommends only using return() for early returns, and omitting it otherwise.
If you write your function using vectorized operations (like /, ^, +, *), it will automatically be vectorized!
Our calculate_bmi() is fully vectorized and works with tibbles/vectors out of the box:
Some functions work on entire datasets (e.g., fitting a model) rather than element-by-element. They are not automatically vectorized:
We pair nest() |> rowwise() |> mutate() |> ungroup() to apply non-vectorized calculations to nested sub-tibbles:
In the Tidyverse, we refer to columns directly without quotes (species, not "species"). But what happens if we write a function that takes a column name as an argument?
variable inside the dataset.variable, not the word ‘variable’!”{ } 🪄Wrapping our variable argument in { } tells Quarto / Tidyverse to evaluate it as the passed column name:
{} vs. { }: When to use which? 🤔It is easy to get confused! Here is the difference:
Single { } (R Code Block / Function Body)
if statements).tidy-eval) is incredibly powerful but was notoriously difficult to design.{ } today.calculate_bmi(), plot_trends(), or convert_temp() (not bmi(), trends()).my_function) or camelCase (myFunction).Place the most important arguments first, and provide default values when they make sense:
calculate_bmi <- function(weight, height, units = "metric") {
if (units == "imperial") {
# Convert pounds to kg and inches to meters
weight <- weight * 0.453592
height <- height * 0.0254
}
return(weight / (height^2))
}
calculate_bmi(70, 1.75) # metric (default)
calculate_bmi(154, 69, units = "imperial") # imperial[1] 22.85714
[1] 22.74157
stop() 🛑Help the user (and yourself!) by checking inputs early and throwing helpful error messages with stop():
calculate_bmi <- function(weight, height, units = "metric") {
# Check inputs
if (weight <= 0 || height <= 0) {
stop("Weight and height must be positive numbers")
}
if (units == "imperial") {
weight <- weight * 0.453592
height <- height * 0.0254
} else if (units != "metric") {
stop('Units must be either "metric" or "imperial"')
}
return(weight / (height^2))
}Use roxygen2 comments (#') directly above your function definition:
rowwise() for non-vectorized operations on nested tibbles.{ } for tidy evaluation.stop(), document with roxygen2, and pick clear, verb-based names!Writing Functions