## Warning in file(filename, "r", encoding = encoding): URL
## 'https://metrics.rstudioprimers.com/learnr/installClient': status was 'Couldn't
## resolve host name'
[1] “Warning: An error occurred with the client code.”
if and else
Control Flow
Control flow refers to the order in which a function executes the statements in its body of code.
By default, R functions will execute each line of code in the body in order, and then return the result of the last line of code. But it doesn’t have to be this way. You can write functions that run some code in some situations and other code in other situations.
Let’s learn how!
if
Take a look at the code below. What is happening?
## [1] TRUE
## [1] FALSE
## [1] 0
## [1] 0
Try to describe what if
is doing in your own words. Your
best guess is okay!
Data cleaner
Think you have it? Let’s check. We’ll use if
to write a
useful function.
Many data sets use their own symbols to represent missing values. For example, NOAA will often use -99 to represent missing values in weather data sets. Let’s write a function that checks whether a value is -99, and if so replaces the value with NA, like this:
## [1] 1
## [1] NA
Here is a start. clean()
takes an object and returns the
object, but clean()
is missing an important piece of
code.
- Add an
if
statement to the beginning ofclean()
. Your statement should assign NA to x if x equals -99. Then click Submit Answer.
clean <- function(x) {
# add if statement here
x
}
"Don't forget to use == to check for equality."
clean <- function(x) {
if (x == -99) x <- NA
x
}
else
Here is a second version of clean()
that uses on a new
command. Can you tell what else
does?
- Run
clean()
with several different values,22
,-99
,3
. What doesclean()
return in each case? Why?
A word about syntax
Although you can put if
and else
on the
same line, you shouldn’t because it is easy for readers to miss the
trailing else
when they scan the code. Also, placing
if
and else
on the same line can make very
long lines.
It would be more common to write our function like this:
R parses the if
and else
lines as a single
statement as long as else
is the first thing that follows
the if
statement. As a result, R will return the result of
the combined if else statement if it appears at the end of a
function.
{}
You can also pass if
and else
chunks of
code surrounded by braces, {}
. Braces group multiple lines
of code into a single “piece.” When you use braces in an if else
statement, R will run (or not run) everything between the braces.
In this example, R will run all three lines that follow
else
whenever x does not equal -99.
When you use braces, indent everything between the braces by two spaces to make your code more readable. And of course, you can use braces to organize your code even if you have a single line of code between the braces.
If else quiz
SAS often saves missing values as "."
.
- Write a function named
clean2
that takes a value namedx
and returns an NA if the value is"."
(and returns the value ofx
otherwise).
"clean2() should closely resemble clean()."
clean <- function(x) {
if (x == ".") NA
else x
}
else if
In that case, you can use else
to chain together
multiple if
statements.
This does the same thing as
clean()
will:
- Check whether
x == -99
. If soclean()
will return NA and skip the rest of the code. If not,clean()
will… - Check whether
x == "."
. If so,clean()
will return NA and skip the rest of the code. If not,clean()
will… - Evaluate
x
and return its value.
else if
is more readable than nested if else statements,
especially if you use many else if
s.
You can use else
to string together as many if
statements as you like. R will treat the result as a single multi-part
if else statement. Be thoughtful with the order. R will always evaluate
the clauses in order, executing the code in the first clause whose
condition is true and ignoring every clause after that.
Your turn
- Write a function named
clean()
that usesif
,else
, andelse if
statements to replace the following four values with NA before returning x,-99
,"."
,""
,"NaN"
. Then click Submit Answer.
clean <- function(x) {
if (x == -99) NA
else if (x == ".") NA
else if (x == "") NA
else if (x == "NaN") NA
else x
}
Congratulations!
You now know how to use if
and else
in your
code. Let’s look at another way to control flow in R.
return() and stop()
You can tell R to stop executing a function early with
return()
stop()
, andstopifnot()
Each will work only in the context of a function (because they stop the function). You wouldn’t run these directly at the command line, but they provide a powerful way to control the flow of your functions: They can make if else statements unnecessary and they can even make your code less buggy.
return()
When R encounters return()
it will stop executing the
function that called return()
. If you pass a value to
return()
, R will return that value when it stops executing
the function. Let’s see how it works.
R with a python accent
If you are a python user, you might already use return()
…unnecessarily. In python, you explicitly tell each function what to
return, e.g.
Translated to R this becomes:
But in R, this return()
is not needed. R functions
automatically return the result of their last line of code. In R, you
can save return()
for unusual control flow.
Using return()
Remember this function? It didn’t work as expected because we forgot
to link our if statements with else
.
- Fix the function not by adding
else
, but by addingreturn()
in the right places. Then Click Submit Answer.
clean <- function(x) {
if (x == -99) NA
if (x == ".") NA
if (x == "NaN") NA
x
}
clean <- function(x) {
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
NULL
clean()
is a fairly useful function, but it does have
one flaw.
- What happens when
x = NULL
? Run the code and find out.
clean <- function(x) {
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
clean(NULL)
clean <- function(x) {
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
clean(NULL)
clean()
cannot handle NULL
because
if
returns an error when it evaluates
NULL == -99
. And, unfortunately, the error message isn’t
very clear. This is the perfect case for stop()
.
## Error in if (x == -99) return(NA): argument is of length zero
stop()
stop()
behaves like return()
, but instead
of returning a value, stop()
returns an error, complete
with a custom error message. Can you tell how it works?
Use stop()
- Use
if
andis.null()
to add astop()
call at the beginning ofclean()
. The command should return the error message"x is NULL"
whenever x is NULL. - Then click Submit Answer.
clean <- function(x) {
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
clean <- function(x) {
if (is.null(x)) stop("x is NULL")
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
stopifnot()
stopifnot()
is a more readable substitute for statements
that combine if
and stop()
. Can you guess how
it works?
differences
stopifnot()
is different from if
+
stop()
in a few important ways:
Instead of checking whether a condition is met,
stopifnot()
checks whether a condition is not met.stopifnot()
does not pass along a custom error message. Instead,stopifnot()
always explains that the condition was not true:## Error: x >= 0 is not TRUE
Notice that the first argument of stopifnot()
should
always be a logical condition, the inverse of the condition it replaces
in an if
+ stop()
statement.
You can include additional logical conditions for
stopifnot()
to check after the first. Separate each with a
comma.
Think you have it?
- Try replacing the
if
+stop()
statement inclean()
withstopifnot()
. Then click Submit Answer.
clean <- function(x) {
if (is.null(x)) stop("x is NULL")
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
"You can reverse the result of `is.null()` by placing an `!` in front of it: `!is.null()`."
clean <- function(x) {
stopifnot(!is.null(x))
if (x == -99) return(NA)
if (x == ".") return(NA)
if (x == "NaN") return(NA)
x
}
Defensive programming with stopifnot()
You can save yourself debugging time by writing your functions to
fail fast with clear error messages. To do this, think about
situations that will lead to errors and then check for them with
stopifnot()
at the beginning of your code.
clean <- function(x) {
stopifnot(!is.null(x), is.numeric(x), length(x) == 1)
if (x == -99) return(NA)
x
}
If things go wrong, stopifnot()
will help you see what
you need to fix as soon as you run your function. Compare this to what
will happen if you do not use stopifnot()
:
- Your code will run until it triggers a (perhaps unhelpful) error message
- Your code may not trigger an error message, but return an incorrect result that you will think is true. This would very bad.
Congratulations! You know two techniques of control flow, how to:
- Run specific code in specific cases
- Stop execution early
In the Advanced Control Flow tutorial, you’ll learn how to combine logical tests in an if statement as well as how to write if statements that work with vectors, which is a prerequisite if you want to write vectorized functions.