This lesson is in the early stages of development (Alpha version)

Flow control

Overview

Teaching: 25 min
Exercises: 5 min
Questions
  • How do we get our code to react dynamically?

  • How do we delegate repetitive task and decisions?

  • How do we make our software robust to a wider set of use cases?

Objectives
  • Understand the role of flow control in managing the order of statement execution.

  • Understand the role and combination of logical and comparison operators

Often, we want to perform different operations in our code based upon dynamic conditions. To explore this idea, we are going to pretend we have two sensors. The first representing a temperature, and the second representing if it there is rainfall. Our temperature is a numeric value, and our rainfall is a logical (Boolean). To declare those variables, type:

temp_reading <- 16 

rainfall <- TRUE 

Then place the following code into your script pane:

if (rainfall == TRUE){ 

  print("advise user to take umbrella") 

} 
/Introduction_to_R/Flow%20diagrame%20for%20if%20condition
Figure 1: Flow diagram for an *if* condition

Evaluating the order of operations

Run the script using the ‘run’ button on the top right of the pane (pay attention to where your cursor is in the script pane when you run the script).

"advise user to take umbrella"

From observing the output in the console and from a brief inspection of the code, it should be evident that we are evaluating the variable rainfall. Specifically, we are checking for equivalence (==). If the outcome is of the check is valid then we perform any code within the brackets.

Now modify your code to look like this:

if (rainfall){ 

  print("advise user to take umbrella") 

}else{ 

  print("leave your umbrella at home") 

} 

Note: with logical variables, we don’t have to check for equivalence (if (logical_variable) is if (TRUE/FALSE)).

/Introduction_to_R/Flow%20diagrame%20for%20if%20condition
Figure 2: Flow diagram for an if, else condition

Change the order

Now change the rainfall variable to ‘FALSE’ and run the script again.

rainfall <- FALSE

  "leave your umbrella at home"

Our code now reacts differently to different input values. You can combine ‘if’, ‘else if’, and ‘else’ statements to control the flow of your code.

Conditional statements

Comparison operators

We have encountered ‘==’, it is used to check for equivalence. There are other comparison operators available to us.

Intuitively you might think that some of these operators are going to work only for numeric values (i.e., how can “apple” < FALSE). We could try a few tests to get a feel for how it works:

TRUE == FALSE
FALSE

As we would expect.

TRUE == TRUE
TRUE

As we would expect.

"four" < "five"
FALSE

R is clearly not comparing on the meaning of characters.

"five" < "six"
TRUE

We are comparing alphabetically.

"apple" < FALSE

TRUE

What is happening here? Why is “apple” less than FALSE?

R is attempting to interpret your request; therefore, it is coercing data types. Coercion follows a hierarchy where items can be converted from lower to higher but not higher to lower.

We are comparing “apple” < “FALSE” alphabetically. Try:

"zoo" < FALSE
FALSE

Now the word is alphabetically greater than “FALSE” we should see FALSE as the response.

Question

Why do we care about “apple” < FALSE ?
We are never going to want to compare “apple” to FALSE, that is not the issue. The issue at hand is that if you have made a mistake and you are comparing the wrong variables you may not get an error. You will just be getting unexpected behaviour from your software which can be very difficult to catch. We will look more at coercion and data types when we deal with manipulating data sets.

Declare a new variable:

storm <- TRUE 

Replace the code in your script with this:

if (storm == TRUE){ 

  print("stay at home") 

}else if (temp_reading < 10){ 

  print("take a coat") 

  if (rainfall){ 

    print("and take an umbrella") 

  }else{ 

    print("and leave your umbrella at home") 

  } 

}else{ 

  print("leave your coat") 

  if (rainfall){ 

    print("and take an umbrella") 

  }else{ 

    print("and leave your umbrella at home") 

  } 

} 
/Introduction_to_R/Flow%20diagrame%20for%20if%20condition
Figure 3: Flow diagram for an if, else if, else condition

Keeping things clear

We now have our old conditional statements inside a new conditional statement. This is referred to as ‘nested’. If your code becomes overly nested it can impact readability and maintainability. It is good practice to keep your workflow as simple as possible, this can be made easier by spending time on design and regular refactoring.

Note: Refactoring is the process of restructuring code, not to change the functionality but to improve factors like readability, maintainability, efficiency.

Now you have three variables storm, temp_reading and rainfall. You can modify the values and see how this changes the order statements are executed in a program.

Question

What do you think might be problematic about this example?
There a several concerns that you may have. The ones I want to draw your attention to are

  1. No defensive programming – What happens if we ‘accidently’ switch temp_reading and storm variables?
  2. Hardcoded variables – Should a user preference be hard coded, what implications might this have?
  3. Redundancy - When we have repetition in code it can lead to problems with updating (e.g., if we want to make a change we must edit code in multiple places, this could easily introduce errors).

We will look at these issues in more detail and consider ways to fix them as we go through more examples.

Logical operators

Logical operators can be used to combine multiple comparison operators (e.g., if x < y and if x > z).

The most common are

Let’s try an example, add a new variable and add this new code to your script:

day = "sunday"
if (storm == FALSE && (day == "monday" || day == "tuesday"|| day == "wednesday" || day == "thursday" || day == "friday")){ #only a storm will stop me from working mon-fri
  print("go to work") 
}else{
  print("stay at home")
}

This may look more complicated, but we are combining the conditionals we have already considered but now with logical operators (i.e., IF no storm AND the day is (Monday to Friday)).

Question

What do you think the consequence of removing the brackets will be?
Hint: Change storm <- TRUE, day <- “wednesday”.
What happens?
What should happen?

You need to ensure that you are explicit with the conditional operations by using brackets, this will allow you to combine without encountering unwanted behaviour.

One direction we can take this is to reduce the number of logical operators. We could change the code to:

working_days <- c("monday","tuesday","wednesday","thursday", "friday") 

Then we could use ‘%in%’ (worth checking ‘%in%’ through the help menu):

if (storm == FALSE && day %in% working_days){ #only a storm will stop me from working mon-fri
  print("go to work") 
}else{
  print("stay at home")
}

We have covered a brief introduction to the basic building blocks of flow control. Hopefully you have grasped, with the few tools we have considered, that you can develop sophisticated flow control. There are more tools to consider to be fully competent on all aspects of flow control. However, they fall outside of the scope of this course.

We have included a source to provide a starting point for you to explore advanced flow control if you wish to (e.g., switch statements, loops etc.):

Starting point for advanced flow control

Note: If you have coding experience you may have used loops before. In R loops can be problematic due to performance issue with memory allocation on large datasets. It is not a beginner-friendly topic; this in part, is why we have excluded them from this introduction to workflow.

Key Points

  • Flow control is an important technique you need to learn to create useful software

  • Whenever possible simplicity is the best option

  • Plan & refactor