Lesson 10 of 17 · Week 05 · Thu Oct 22
Functions Explained
Questions
- How can I write a function I can reuse?
- How do I make a function fail safely on bad input?
Objectives
- Define functions with arguments and a return value.
- Build new functions by combining existing ones.
- Check inputs with
stopifnot()andstop(). - Write functions that take a data frame, with default argument values.
Why write functions?
If you've copied and pasted a block of code twice, it's time for a function. A function gives a name to a piece of work, so you can reuse it, test it once, and fix it in one place.
mm_to_inches <- function(mm) {
inches <- mm / 25.4
return(inches)
}
mm_to_inches(254) # 10
mm_to_inches(c(10, 100)) # vectorized for free, because / is
Anatomy: a name, the keyword function, the arguments in parentheses, and the
body in braces. The function returns the value given to return(), or, if there's no
return(), the value of the last line. Variables created inside a function (like inches) exist only
while it runs.
Adapted from Software Carpentry's “Functions Explained” episode (CC BY 4.0, The Carpentries). Text, examples and exercises have been rewritten for this course.
Building functions from functions
inches_to_feet <- function(inches) inches / 12
mm_to_feet <- function(mm) {
inches_to_feet(mm_to_inches(mm))
}
Small functions that do one thing are easy to check. Combining them builds bigger tools you can trust.
Defensive programming
A function should fail loudly and early when it gets input it can't handle, rather than quietly returning nonsense.
stopifnot() stops with an error if any condition is FALSE. stop() lets you write your own
message:
mm_to_inches <- function(mm) {
stopifnot(is.numeric(mm))
if (any(mm < 0)) {
stop("lengths can't be negative")
}
mm / 25.4
}
mm_to_inches("12") # Error: is.numeric(mm) is not TRUE
Functions on data frames, with defaults
country_rows <- function(dat, ctry, yr = NULL) {
keep <- dat$country == ctry
if (!is.null(yr)) {
keep <- keep & dat$year %in% yr
}
dat[keep, ]
}
country_rows(gm, "Kenya") # all years: yr uses its default, NULL
country_rows(gm, "Kenya", yr = 2007)
An argument with a default (yr = NULL) can be left out. NULL is a common way to say “not given”,
and is.null() checks for it. Arguments can be given by position or by name; names make calls easier to read.
Text in, text out
paste("tidy", "data") # "tidy data" (sep = " " by default)
paste0("tidy", "data") # "tidydata"
paste(c("tidy", "data"), collapse = " ") # squash a vector into one string
Exercises
Each exercise gives you up to 10 checked attempts. Use Run as many times as you like to experiment first, then click Check answer when you're ready. Your exercises use numbers/subsets unique to you, so answers won't match a classmate's exactly.
Exercise 1 — Knots to km/h
0 / 10 attempts usedWrite a function knots_to_kmh() that takes a speed in knots and returns it in km/h. One knot is
1.852 km/h.
Exercise 2 — Compose two functions
0 / 10 attempts usedkmh_to_ms() (km/h to metres per second) is already defined for you. Write knots_to_kmh() again,
then write knots_to_ms() that converts knots to m/s by calling both functions, not by using a new
number.
Exercise 3 — Fail loudly
0 / 10 attempts usedMake knots_to_kmh() defensive: it should stop with an error if knots isn't numeric, or if any
speed is negative. It should still convert valid speeds correctly. Test it with knots_to_kmh("fast") and
knots_to_kmh(-3).
Exercise 4 — Total GDP, then and now
0 / 10 attempts usedWrite total_gdp(dat, ctry, yr) that returns a country's total GDP (population × GDP per capita) in one year,
from a gapminder-style data frame. Then use it to store in growth how many times larger your country's total GDP
was in y2 than in y1 (click Run for your country and years).
Exercise 5 — Frame some text
0 / 10 attempts usedWrite frame_text(text, border) that returns one string: the border, a space,
all the words of text joined by spaces, a space, and the border again. For example,
frame_text(c("tidy", "data"), "~~") returns "~~ tidy data ~~".
Key Points
name <- function(args) { body }; the last value (orreturn()) is the result.- Compose small functions instead of writing one big one.
- Fail early with
stopifnot()orstop()when input is wrong. - Use arguments, never global variables like
gm, inside functions. - Defaults (e.g.
yr = NULL) make arguments optional.
Proof of completion
Once you've attempted every exercise above, download a PDF summary of your work on this lesson — your name, the exercises you passed, attempt counts, timing, and your final code for each.