As their name states, if…else conditionals will check a condition, and if it is evaluated to be a TRUE value, one path of execution will be taken, but if the condition is evaluated to be a FALSE value, a different path of execution will be taken, and they are mutually exclusive.
To show how if... else conditions work, we will program the same distance() function we used before, but instead of passing it the third argument in the form of a function, we will pass it a string that will be checked to decide which function should be used. This way you can compare different ways of implementing the same functionality. If we pass the l2 string to the norm argument, then the l2_norm() function will be used, but if any other string is passed through, the l1_norm() will be used. Note that we use the double equals operator (==) to check for equality. Don't confuse this with a single equals, which means assignment:
distance <- function(x, y = 0, norm = "l2") {
if (norm == "l2") {
return(l2_norm(x, y))
} else {
return(l1_norm(x, y))
}
}
a <- c(1, 2, 3)
b <- c(4, 5, 6)
distance(a, b)
#> 27
distance(a, b, "l2")
#> 27
distance(a, b, "l1")
#> 9
distance(a, b, "l1 will also be used in this case")
#> 9
As can be seen in the last line of the previous example, using conditionals in a non-rigorous manner can introduce potential bugs, as in this case we used the l1_norm() function, even when the norm argument in the last function call did not make any sense at all. To avoid such situations, we may introduce the more conditionals to exhaust all valid possibilities and throw an error, with the stop() function, if the else branch is executed, which would mean that no valid option was provided:
distance <- function(x, y = 0, norm = "l2") {
if (norm == "l2") {
return(l2_norm(x, y))
} else if (norm == "l1") {
return(l1_norm(x, y))
} else {
stop("Invalid norm option")
}
}
distance(a, b, "l1")
#> [1] 9
distance(a, b, "this will produce an error")
#> Error in distance(a, b, "this will produce an error") :
#> Invalid norm option
Sometimes, there's no need for the else part of the if... else condition. In that case, you can simply avoid putting it in, and R will execute the if branch if the condition is met and will ignore it if it's not.
There are many different ways to generate the logical values that can be used within the if() check. For example, you could specify an optional argument with a NULL default value and check whether it was not sent in the function call by checking whether the corresponding variable still contains the NULL object at the time of the check, using the is.null() function. The actual condition would look something like if(is.null(optional_argument)). Other times you may get a logical vector, and if a single one of its values is TRUE, then you want to execute a piece of code, in that case you can use something like if(any(logical_vector)) as the condition, or in case you require that all of the values in the logical vector are TRUE to execute a piece of code, then you can use something like if(all(logical_vector)). The same logic can be applied to the self-descriptive functions named is.na() and is.nan().
Another way to generate these logical values is using the comparison operators. These include less than (<), less than or equal to (<=), greater than (>), greater than or equal to (>=), exactly equal (which we have seen ,==), and not equal to (!=). All of these can be used to test numerics as well as characters, in which case alphanumerical order is used. Furthermore, logical values can be combined among themselves to provide more complex conditions. For example, the ! operator will negate a logical, meaning that if !TRUE is equal to FALSE, and !FALSE is equal to TRUE. Other examples of these types of operators are the OR operator where in case any of the logical values is TRUE, then the whole expression evaluates to TRUE, and the AND operator where all logical must be TRUE to evaluate to TRUE. Even though we don't show specific examples of the information mentioned in the last two paragraphs, you will see it used in the examples we will develop in the rest of the book.
Finally, note that a vectorized form of the if... else conditional is available under the ifelse() function. In the following code we use the modulo operator in the conditional, which is the first argument to the function, to identify which values are even, in which case we use the TRUE branch which is the second argument to indicate that the integer is even, and which are not, in which case we use the FALSE branch which is the third argument to indicate that the integer is odd:
ifelse(c(1, 2, 3, 4, 5, 6) %% 2 == 0, "even", "odd")
#> [1] "odd" "even" "odd" "even" "odd" "even"