if()
, ifelse()
, switch()
.for
, while
, repeat
.if()
statementsif (condition) true_action
if (condition) true_action else false_action
if
statements:
TRUE
, true_action
is evaluated.FALSE
, the optional false_action
is evaluated.x <- 87
if (x > 90) {
print("A")
} else if (x > 80) {
print("B")
} else if (x > 50) {
print("C")
} else {
print("F")
}
## [1] "B"
if
returns a value so that you can assign the results:
x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2
c(x1, x2)
## [1] 1 2
if
without else
:
NULL
if the condition is FALSE
.c()
/paste()
dropping NULL
inputs.greet <- function(name, birthday = FALSE) {
paste0("Hi ", name, if (birthday) " and HAPPY BIRTHDAY")
}
greet("Maria", FALSE)
## [1] "Hi Maria"
greet("Jaime", TRUE)
## [1] "Hi Jaime and HAPPY BIRTHDAY"
TRUE
or FALSE
:if ("x") 1
#> Error in if ("x") 1: argument is not interpretable as logical
if (logical()) 1
#> Error in if (logical()) 1: argument is of length zero
if (NA) 1
#> Error in if (NA) 1: missing value where TRUE/FALSE needed
if (c(TRUE, FALSE)) 1
## Warning in if (c(TRUE, FALSE)) 1: the condition has length > 1 and only the
## first element will be used
## [1] 1
>=3.5.0+
, you can turn this into an error (good practice):Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "true")
if (c(TRUE, FALSE)) 1
#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1
if()
statementsif
only works with a single TRUE
or FALSE
.ifelse()
, a vectorised function with test, yes
, and no
vectors (recycled to the same length).
ifelse()
only when the yes
and no
are vectors (otherwise hard to predict the output type).x <- 1:10
ifelse(x %% 5 == 0, "XXX", as.character(x))
## [1] "1" "2" "3" "4" "XXX" "6" "7" "8" "9" "XXX"
ifelse(x %% 2 == 0, "even", "odd")
## [1] "odd" "even" "odd" "even" "odd" "even" "odd" "even" "odd" "even"
switch()
statementsswitch()
lets you replace code like:
x_option <- function(x) {
if (x == "a") {
"option 1"
} else if (x == "b") {
"option 2"
} else {
stop("Invalid `x` value")
}
}
with:
x_option <- function(x) {
switch(x,
a = "option 1",
b = "option 2",
stop("Invalid `x` value")
)
}
(switch("c", a = 1, b = 2))
## NULL
legs <- function(x) {
switch(x,
cow = ,
horse = ,
dog = 4,
human = ,
chicken = 2,
plant = 0,
stop("Unknown input")
)
}
legs("cow")
## [1] 4
legs("dog")
## [1] 4
switch()
with a numeric x
is not recommended.for
loops are used to iterate over items in a vector.
for (item in vector) perform_action
vector, perform_action
is called once; updating the value of item
each time.for (i in 1:3) {
print(i)
}
## [1] 1
## [1] 2
## [1] 3
i, j
, or k
by convention.for
assigns the item
to the current environment.i <- 100
for (i in 1:3) {}
i
## [1] 3
Two ways to terminate a for
loop early: - next
exits the current iteration. - break
exits the entire for
loop.
for (i in 1:10) {
if (i < 3)
next
print(i)
if (i >= 5)
break
}
## [1] 3
## [1] 4
## [1] 5
Three common pitfalls to watch out for when using for
: - Preallocation. - Iteration over e.g. 1:length(x)
. - Iteration over S3 vectors.
vector()
function is helpful.means <- c(1, 50, 20)
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
1:length(x)
Next, beware of iterating over 1:length(x)
, which will fail in unhelpful ways if x
has length 0.
means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
#> Error in rnorm(10, means[[i]]): invalid arguments
# The reason? `:` works with both increasing and decreasing sequences.
1:length(means)
seq_along(x)
instead.means <- c()
seq_along(means)
## integer(0)
out <- vector("list", length(means))
for (i in seq_along(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
print(x)
}
## [1] 18262
## [1] 14610
[[
.for (i in seq_along(xs)) {
print(xs[[i]])
}
## [1] "2020-01-01"
## [1] "2010-01-01"
A function has three parts:
formals()
, list of arguments controlling how you call the function.body()
, the code inside the function.environment()
, the data structure that determines how the function finds the values associated with the names.f02 <- function(x, y) {
# A comment
x + y
}
How are those are defined? - Explicitly for the formals and body. - Implicitly for the environment (where the function was defined).
formals(f02)
## $x
##
##
## $y
body(f02)
## {
## x + y
## }
environment(f02)
## <environment: R_GlobalEnv>
attributes()
.srcref
, short for source reference.
body()
, it contains code comments and other formatting.attr(f02, "srcref")
## function(x, y) {
## # A comment
## x + y
## }
sum
## function (..., na.rm = FALSE) .Primitive("sum")
`[`
## .Primitive("[")
builtin
or special
.typeof(sum)
## [1] "builtin"
typeof(`[`)
## [1] "special"
formals(), body()
, and environment()
are all NULL
.formals(sum)
## NULL
body(sum)
## NULL
environment(sum)
## NULL
function
).<-
.f01 <- function(x) {
sin(1 / x ^ 2)
}
lapply(mtcars, function(x) length(unique(x)))
## $mpg
## [1] 25
##
## $cyl
## [1] 3
##
## $disp
## [1] 27
##
## $hp
## [1] 22
##
## $drat
## [1] 22
##
## $wt
## [1] 29
##
## $qsec
## [1] 30
##
## $vs
## [1] 2
##
## $am
## [1] 2
##
## $gear
## [1] 3
##
## $carb
## [1] 6
integrate(function(x) sin(x) ^ 2, 0, pi)
## 1.570796 with absolute error < 1.7e-14
funs <- list(
half = function(x) x / 2,
double = function(x) x * 2
)
funs$double(10)
## [1] 20
mean(1:10, na.rm = TRUE)
## [1] 5.5
args <- list(1:10, na.rm = TRUE)
do.call(mean, args)
## [1] 5.5
What if you want to apply a function to the output of another function?
sqrt()
and mean()
.square <- function(x) x^2
deviation <- function(x) x - mean(x)
x <- runif(100)
sqrt(mean(square(deviation(x))))
## [1] 0.2845536
x <- runif(100)
out <- deviation(x)
out <- square(out)
out <- mean(out)
out <- sqrt(out)
out
## [1] 0.2903238
magrittr
package:
%>%
, called pipe and pronounced as “and then”.x <- runif(100)
library(magrittr)
## Warning: package 'magrittr' was built under R version 4.0.3
##
## Attaching package: 'magrittr'
## The following object is masked from 'package:purrr':
##
## set_names
## The following object is masked from 'package:tidyr':
##
## extract
x %>%
deviation() %>%
square() %>%
mean() %>%
sqrt()
## [1] 0.2791327
x %>% f
is equivalent to f(x)
x %>% f(y)
is equivalent to f(x, y)
x %>% f(y) %>% g(z)
is equivalent to g(f(x, y), z)
x <- 1:10
y <- x + 1
z <- y + 1
f <- function(x, y) x + y
x %>% sum
## [1] 55
x %>% f(y)
## [1] 3 5 7 9 11 13 15 17 19 21
x %>% f(y) %>% f(z)
## [1] 6 9 12 15 18 21 24 27 30 33
x %>% f(y, .)
is equivalent to f(y, x)
x %>% f(y, z = .)
is equivalent to f(y, z = x)
x <- 1:10
y <- 2 * x
f <- function(z, y) y / z
x %>% f(y, .)
## [1] 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
x %>% f(y, z = .)
## [1] 2 2 2 2 2 2 2 2 2 2
f(g(x))
:
y <- f(x); g(y)
:
x %>% f() %>% g()
:
h01 <- function(x) {
10
}
h01(stop("This is an error!"))
## [1] 10
x + y
, giving rise to the delayed computation.y <- 10
h02 <- function(x) {
y <- 100
x + 1
}
h02(y)
## [1] 11
y <- 10
h02 <- function(x) {
y <- 100
x + 1
}
h02(y <- 1000)
## [1] 1001
y
## [1] 1000
x <- 1:10
double <- function(x) {
message("Calculating...")
x * 2
}
h03 <- function(x) {
c(x, x)
}
h03(double(x))
## Calculating...
## [1] 2 4 6 8 10 12 14 16 18 20 2 4 6 8 10 12 14 16 18 20
h04 <- function(x = 1, y = x * 2, z = a + b) {
a <- 10
b <- 100
c(x, y, z)
}
h04()
## [1] 1 2 110
h05 <- function(x = ls()) {
a <- 1
x
}
# ls() evaluated in global environment:
h05(ls())
## [1] "args" "deviation" "double" "f" "f01" "f02"
## [7] "funs" "greet" "h01" "h02" "h03" "h04"
## [13] "h05" "i" "legs" "means" "out" "square"
## [19] "x" "x_option" "x1" "x2" "xs" "y"
## [25] "z"
# ls() evaluated inside h05:
h05()
## [1] "a" "x"
missing()
to determine if an argument’s value comes from the user or from a default.h06 <- function(x = 10) {
list(missing(x), x)
}
# default
str(h06())
## List of 2
## $ : logi TRUE
## $ : num 10
# user supplied
str(h06(10))
## List of 2
## $ : logi FALSE
## $ : num 10
args(sample)
## function (x, size, replace = FALSE, prob = NULL)
## NULL
NULL
to indicate that size
is not required but can be supplied.sample <- function(x, size = NULL, replace = FALSE, prob = NULL) {
if (is.null(size)) {
size <- length(x)
}
x[sample.int(length(x), size, replace = replace, prob = prob)]
}
i01 <- function(y, z) {
list(y = y, z = z)
}
i02 <- function(x, ...) {
i01(...)
}
str(i02(x = 1, y = 2, z = 3))
## List of 2
## $ y: num 2
## $ z: num 3
x <- list(c(1, 3, NA), c(4, NA, 6))
str(lapply(x, mean, na.rm = TRUE))
## List of 2
## $ : num 2
## $ : num 5
print(factor(letters), max.levels = 4)
## [1] a b c d e f g h i j k l m n o p q r s t u v w x y z
## 26 Levels: a b c ... z
list(...)
evaluates the arguments and stores them in a list.i04 <- function(...) {
list(...)
}
str(i04(a = 1, b = 2))
## List of 2
## $ a: num 1
## $ b: num 2
...
comes with two downsides:
sum(1, 2, NA, na_rm = TRUE)
## [1] NA
j01 <- function(x) {
if (x < 10) {
0
} else {
10
}
}
j01(5)
## [1] 0
j01(15)
## [1] 10
return()
.j02 <- function(x) {
if (x < 10) {
return(0)
} else {
return(10)
}
}
j03 <- function() 1
j03()
## [1] 1
invisible()
to the last value prevents this.j04 <- function() invisible(1)
j04()
# Verify that the value exists with `print` or `()`.
print(j04())
## [1] 1
(j04())
## [1] 1
<-
.a <- 2
(a <- 2)
## [1] 2
a <- b <- c <- d <- 2
<-, print()
, or plot()
) should return an invisible value (often the value of the first argument).stop()
:
j05 <- function() {
stop("I'm an error")
return(10)
}
j05()
#> Error in j05(): I'm an error
j06 <- function(x) {
cat("Hello\n")
on.exit(cat("Goodbye!\n"), add = TRUE)
if (x) {
return(10)
} else {
stop("Error")
}
}
j06(TRUE)
## Hello
## Goodbye!
## [1] 10
j06 <- function(x) {
cat("Hello\n")
on.exit(cat("Goodbye!\n"), add = TRUE)
if (x) {
return(10)
} else {
stop("Error")
}
}
j06(FALSE)
#> Hello
#> Error in j06(FALSE): Error
#> Goodbye!
on.exit()
add = TRUE
:
on.exit()
overwrites previous ones.add = TRUE
.on.exit()
is useful because it allows to place clean-up code directly next to the code that requires clean-up.cleanup <- function(dir, code) {
old_dir <- setwd(dir)
on.exit(setwd(old_dir), add = TRUE)
old_opt <- options(stringsAsFactors = FALSE)
on.exit(options(old_opt), add = TRUE)
}
on.exit()
with_dir <- function(dir, code) {
old <- setwd(dir)
on.exit(setwd(old), add = TRUE)
force(code)
}
getwd()
## [1] "C:/Users/susuz/Dropbox/Programming_course/Slides"
with_dir("~", getwd())
## [1] "C:/Users/susuz/Documents"
getwd()
## [1] "C:/Users/susuz/Dropbox/Programming_course/Slides"
force()
isn’t strictly necessary here as simply referring to code will force its evaluation.