If you are in this class, you should know how to use if-statements and for-loops.
Here, we will just briefly review them and discuss some aspects about them you may not know.
You are used to if-else if-else statements, the proper syntax of which is:
if (condition1) {
# do something
else if (condition2) {
} # do something else
else {
} # do default behavior
}
Exercise: Suppose our control flow is to print "foo"
if a
is TRUE
, to print bar
if b
is TRUE, and print foobar
if both a
and b
are TRUE
. If neither are TRUE
then it should return an empty string. What is wrong with the following?
<- TRUE
a <- TRUE
b if (a) {
"foo"
else if (b) {
} "bar"
else if (a & b) {
} "foobar"
else {
} ""
}
## [1] "foo"
For short statements, you can use one-liner syntax.
if (a) "foo" else "bar"
## [1] "foo"
If you have a vector of conditions, instead of using a for-loop, use ifelse()
.
<- runif(10)
x <- ifelse(x < 0.5, "low", "high")
y
## equivalent for-loop
<- rep(NA_real_, length.out = length(x))
y for (i in seq_along(x)) {
<- if (x[[i]] < 0.5) "low" else "high"
y[[i]] }
If you have a lot of else if
statements, then you can use switch()
.
<- 1
a if (a == 1) {
"b"
else if (a == 2) {
} "c"
else if (a == 3) {
} "d"
else {
} stop("a not 1, 2, or 3")
}
## [1] "b"
switch(a,
`1` = "b",
`2` = "c",
`3` = "d",
stop("a not 1, 2, or 3"))
## [1] "b"
stop()
call, otherwise switch()
will return NULL
.Exercise (From Advanced R): Explain the following results:
<- 1:10
x if (length(x)) "not empty" else "empty"
## [1] "not empty"
<- numeric()
x if (length(x)) "not empty" else "empty"
## [1] "empty"
Exercise: Explain the following results. What do you think the having an empty right-hand-side does in switch()
?
<- function(a) {
switch_a switch(a,
b = ,
c = ,
d = 3,
e = ,
f = 4,
stop("a not 1, 2, or 3"))
}switch_a("b")
## [1] 3
switch_a("c")
## [1] 3
switch_a("d")
## [1] 3
switch_a("e")
## [1] 4
switch_a("f")
## [1] 4
You are used to the basic for-loop:
for (variable in vector) {
# do stuff
}
You can also do for-loops on one line
for (i in 1:10) print(i)
next
exits the current iteration.
for (i in 1:5) {
if (i == 3) next
print(i)
}
## [1] 1
## [1] 2
## [1] 4
## [1] 5
break
exits the entire for loop.
for (i in 1:3) {
if (i == 3) break
print(i)
}
## [1] 1
## [1] 2
Because of copy-on-modify, make sure to always preallocate your output before using a for-loop.
## BAD
<- c()
x for (i in 1:10) x <- c(x, i)
## GOOD
<- rep(NA_real_, length.out = 10)
x for (i in 1:10) x[[i]] <- i
Let’s quantify this. On my laptop, the first for-loop takes 10 seconds. The second takes 0.009 seconds. That’s about 100 times slower.
## BAD
<- c()
x system.time(
for (i in 1:100000) x <- c(x, i)
)
## GOOD
<- rep(NA_real_, length.out = 100000)
x system.time(
for (i in 1:100000) x[[i]] <- i
)
Never use 1:length(x)
. This will fail if x
has length 0. Instead, use seq_along()
.
<- c()
x 1:length(x)
## [1] 1 0
seq_along(x)
## integer(0)
You can iterate over arbitrary vectors, but I think this is usually bad practice, since it is harder to read and is less standard. You can always iterate over an integer sequence whenever you want to iterate over something else.
## Works, but bad practice
<- c("a", "b", "c")
x for (y in x) {
print(y)
}
## [1] "a"
## [1] "b"
## [1] "c"
## Works, and good practice
<- c("a", "b", "c")
x for (i in seq_along(x)) {
print(x[[i]])
}
## [1] "a"
## [1] "b"
## [1] "c"
Why does this code succeed without errors or warnings?
<- numeric()
x <- vector("list", length(x))
out for (i in 1:length(x)) {
<- x[i] ^ 2
out[i]
} out
## [[1]]
## [1] NA
Why doesn’t this for-loop go on forever?
<- c(1, 2, 3)
xs for (x in xs) {
<- c(xs, x * 2)
xs
} xs
## [1] 1 2 3 2 4 6
What does the following code tell you about when the index is updated?
for (i in 1:3) {
<- i * 2
i print(i)
}
## [1] 2
## [1] 4
## [1] 6