Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
R Programming By Example

You're reading from   R Programming By Example Practical, hands-on projects to help you get started with R

Arrow left icon
Product type Paperback
Published in Dec 2017
Publisher Packt
ISBN-13 9781788292542
Length 470 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Omar Trejo Navarro Omar Trejo Navarro
Author Profile Icon Omar Trejo Navarro
Omar Trejo Navarro
Omar Trejo Navarro Omar Trejo Navarro
Author Profile Icon Omar Trejo Navarro
Omar Trejo Navarro
Arrow right icon
View More author details
Toc

Table of Contents (12) Chapters Close

Preface 1. Introduction to R 2. Understanding Votes with Descriptive Statistics FREE CHAPTER 3. Predicting Votes with Linear Models 4. Simulating Sales Data and Working with Databases 5. Communicating Sales with Visualizations 6. Understanding Reviews with Text Analysis 7. Developing Automatic Presentations 8. Object-Oriented System to Track Cryptocurrencies 9. Implementing an Efficient Simple Moving Average 10. Adding Interactivity with Dashboards 11. Required Packages

Complex logic with control structures

The final topic we should cover is how to introduce complex logic by using control structures. When I write introduce complex logic, I don't mean to imply that it's complex to do so. Complex logic refers to code that has multiple possible paths of execution, but in reality, it's quite simple to implement it.

Nearly every operation in R can be written as a function, and these functions can be passed through to other functions to create very complex behavior. However, it isn't always convenient to implement logic that way and using simple control structures may be a better option sometimes.

The control structures we will look at are if... else conditionals, for loops, and while loops. There are also switch conditionals, which are very much like if... else conditionals, but we won't look at them since we won't use them in the examples contained in this book.

If… else conditionals

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"

For loops

There are two important properties of for loops. First, results are not printed inside a loop unless you explicitly call the print() function. Second, the indexing variable used within a for loop will be changed, in order, after each iteration. Furthermore, to stop iterating you can use the keyword break, and to skip to the next iteration you can use the next command.

For this first example, we create a vector of characters called words, and iterate through each of its elements in order using the for (word in words) syntax. Doing so will take the first element in words, assign it to word, and pass it through the expression defined in the block defined by the curly braces, which in this case print the word to the console, as well as the number of characters in the word. When the iteration is finished, word will be updated with the next word, and the loop will be repeated this way until all words have been used:

words <- c("Hello", "there", "dear", "reader")
for (word in words) {
    print(word)
    print(nchar(word))
}
#> [1] "Hello"
#> [1] 5
#> [1] "there"
#> [1] 5
#> [1] "dear"
#> [1] 4
#> [1] "reader"
#> [1] 6

Interesting behavior can be achieved by using nested for loops which are for loops inside other for loops. In this case, the same logic applies, when we encounter a for loop we execute it until completion. It's easier to see the result of such behavior than explaining it, so take a look at the behavior of the following code:

for (i in 1:5) {
    print(i)
    for (j in 1:3) {
        print(paste("   ", j))
    }
}
#> [1] 1
#> [1] " 1"
#> [1] " 2"
#> [1] " 3"
#> [1] 2
#> [1] " 1"
#> [1] " 2"
#> [1] " 3"
#> [1] 3
#> [1] " 1"
#> [1] " 2"
#> [1] " 3"
#> [1] 4
#> [1] " 1"
#> [1] " 2"
#> [1] " 3"
#> [1] 5
#> [1] " 1"
#> [1] " 2"
#> [1] " 3"

Using such nested for loops is how people perform matrix-like operations when using languages that do not offer vectorized operations. Luckily, we can use the syntax shown in previous sections to perform those operations without having to use nested for-loops ourselves which can be tricky at times.

Now, we will see how to use the sapply() and lapply() functions to apply a function to each element of a vector. In this case, we will call use the nchar() function on each of the elements in the words vector we created before. The difference between the sapply() and the lapply() functions is that the first one returns a vector, while the second returns a list. Finally, note that explicitly using any of these functions is unnecessary, since, as we have seen before in this chapter, the nchar() function is already vectorized for us:

sapply(words, nchar)
#> Hello there dear reader
#> 5 5 4 6 lapply(words, nchar)
#> [[1]]
#> [1] 5
#>
#> [[2]]
#> [1] 5
#>
#> [[3]]
#> [1] 4
#>
#> [[4]]
#> [1] 6 nchar(words)
#> [1] 5 5 4 6

When you have a function that has not been vectorized, like our distance() function. You can still use it in a vectorized way by making use of the functions we just mentioned. In this case we will apply it to the x list which contains three different numeric vectors. We will use the lapply() function by passing it the list, followed by the function we want to apply to each of its elements (distance() in this case). Note that in case the function you are using receives other arguments apart from the one that will be taken from x and which will be passed as the first argument to such function, you can pass them through after the function name, like we do here with the c(1, 1, 1) and l1_norm arguments, which will be received by the distance() function as the y and norm arguments, and will remain fixed for all the elements of the x list:

x <- list(c(1, 2, 3), c(4, 5, 6), c(7, 8, 9))
lapply(x, distance, c(1, 1, 1), l1_norm)
#> [[1]]
#> [1] 3
#>
#> [[2]]
#> [1] 12
#>
#> [[3]]
#> [1] 21

While loops

Finally, we will take a look at the while loops which use a different way of looping than for loops. In the case of for loops, we know the number of elements in the object we use to iterate, so we know in advance the number of iterations that will be performed. However, there are times where we don't know this number before we start iterating, and instead, we will iterate based on some condition being true after each iteration. That's when while loops are useful.

The way while loops work is that we specify a condition, just as with if…else conditions, and if the condition is met, then we proceed to iterate. When the iteration is finished, we check the condition again, and if it continues to be true, then we iterate again, and so on. Note that in this case if we want to stop at some point, we must modify the elements used in the condition such that it evaluates to FALSE at some point. You can also use break and next inside the while loops.

The following example shows how to print all integers starting at 1 and until 10. Note that if we start at 1 as we do, but instead of adding 1 after each iteration, we subtracted 1 or didn't change x at all, then we would never stop iterating. That's why you need to be very careful when using while loops since the number of iterations can be infinite:

x <- 1
while (x <= 10) {
    print(x)
    x <- x + 1
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10

In case you do want to execute an infinite loop, you may use the while loop with a TRUE value in the place of the conditional. If you do not include a break command, the code will effectively provide an infinite loop, and it will repeat itself until stopped with the CTRL + C keyboard command or any other stopping mechanism in the IDE you're using. However, in such cases, it's cleaner to use the repeat construct as is shown below. It may seem counter intuitive, but there are times when using infinite loops is useful. We will see one such case in Chapter 8, Object-Oriented System to Track Cryptocurrencies, but in such cases, you have an external mechanism used to stop the program based on a condition external to R.

Executing the following example will crash your R session:

# DO NOTE EXCEUTE THIS, IT's AN INFINITE LOOP

x <- 1
repeat {
    print(x)
    x <- x + 1
}

#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 5
...
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at R$50/month. Cancel anytime