R fundamentals 03: Elements of programming

(Jul 19, 19)

As the last part of R fundamentals series, we will study elements of R programming.

Conditional statements and operators in R

R provides syntax to evaluate conditions.

  if (condition) {
    expression
  }

As an example,

  # Assign x to -8
  x <- -8
  # Print if x is negative
  if (x < 0) {
    print("x is a negative number")
  }
## [1] "x is a negative number"


Classwork/Homework: Change x to 5 and re-run the above code. What does that print?


if...else statements are evaluated thus:

  if (condition) {
    expression 1
  } else {
    expression 2
  }


Classwork/Homework: Repeat the previous classwork, this time adding an else statement that prints “x is positive or zero”, when needed.


We can also make else if statements as follows:

  if (condition 1) {
    expression
  } else if (condition 2) {
    expression
  } else {
    expression
  }

R provides logical operators:

  • AND with symbol  &
  • OR with symbol      |
  • NOT with symbol   !


The AND truth table

  # AND truth table
  TRUE  & TRUE   : TRUE
  TRUE  & FALSE  : FALSE
  FALSE & TRUE   : FALSE
  FALSE & FALSE  : FALSE

Example: x <- 5 and (x >3) & (x <8) will evaluate to TRUE.

The OR truth table

  # OR truth table
  TRUE  | TRUE   : TRUE
  TRUE  | FALSE  : TRUE
  FALSE | TRUE   : TRUE
  FALSE | FALSE  : FALSE

Example: x <- 5 and (x >5) | (x <8) will evaluate to TRUE.

The NOT truth table

  # NOT truth table
  !TRUE  : FALSE
  !FALSE : TRUE

Example: x <- 5 and !((x >5) | (x <8)) will evaluate to FALSE.


Classwork/Homework: Create an R script that returns the max value of a vector x with length 3. Don’t use the aid of an auxiliary variable.

Note: R comes out of the if-else section as soon as the condition is met. It will not evaluate further conditions down the line even if they satisfy.

  # Assign x to 6
  x <- 6
  # Check if x is divisible by 2 or 3, if so print it is
  # Otherwise print it is not
  if (x %% 2 == 0) {
    print("x is divisible by 2")
  } else if(x %% 3 == 0) {
    print("x is divisible by 3")
  } else {
    print("x is neither divisible by 2 nor 3")
  }
## [1] "x is divisible by 2"

We can apply logical operators on vectors and matrices - works element wise:

c(TRUE,TRUE,FALSE) & c(FALSE,TRUE,TRUE) will evaluate to FALSE TRUE FALSE.

c(TRUE,TRUE,FALSE) | c(FALSE,TRUE,TRUE) will evaluate to TRUE TRUE TRUE.

Also, we have && and || operators in R. The difference between single &and &&is that, && will only look at the first elements of the vectors. Similarly for ||. Thus,

c(TRUE,TRUE,FALSE) && c(FALSE,TRUE,TRUE) will evaluate to FALSE and

c(TRUE,TRUE,FALSE) || c(FALSE,TRUE,TRUE) will evaluate to TRUE.

We can check two objects are equal by the == sign or inequality using != operator.

  # Check the quality of logical objects
  TRUE == TRUE
  # Check the quality of logical objects
  TRUE == FALSE
  # Compare strings
  "hi" == "hello"
  # Or numbers
  2 == 2
  # Check inequlaity
  2 != 3
## [1] TRUE
## [1] FALSE
## [1] FALSE
## [1] TRUE
## [1] TRUE

In fact, we can use comparison operators for objects!

  # Use comparison on objects
  "hello" >= "goodbye"
## [1] TRUE
  # How about on logicals?
  TRUE < FALSE
## [1] FALSE

Here are two other useful functions to evaluate logical vectors: any() and all().

any() is comparable to OR;
it is enough that there is one TRUE to return TRUE
all() is comparable to AND;
it is enough that there is one FALSE to return FALSE

Example:

# Are any of the values TRUE?
any(c(FALSE, FALSE, TRUE, FALSE))
## [1] TRUE
# Are all of the values TRUE?
all(c(TRUE, TRUE, TRUE, FALSE))
## [1] FALSE

The inclusion operator %in% , search for an element within a vector:

# Here we have a list (a vector) of all sort of complex bacterial famalies 
bacFamVec <- c("Cyclobacteriaceae", "Leuconostocaceae", 
               "Leptotrichiaceae", "Pirellulaceae",
               "Rhizobiaceae", "Methylobacteriaceae", 
               "Mycobacteriaceae", "Staphylococcaceae" )

# Does the "Clostridiaceae" one of them?
( "Clostridiaceae" %in% bacFamVec )
## [1] FALSE

Using the resulted logical vector allows us to extract the values or indeces of elements


Classwork/Homework:

  1. Compare two vectors of equal length and output the result.
  2. How about vectors of unequal length? Why does the result makes sense?
  3. Compare two matrices and lists and explain how R handles such comparisons.
  4. Explore further the example of the “inclusion operator”, does it matter if you reverse the order of the compared elements? If yes, what the difference is?


Functions in R


Why do we need functions?

  • Functions save us the tadious typing of the same text for repeated operation
  • Functions make code cleaner and easier to read
  • Functions save us the need to repeat challanges that we have solved already
  • Functions improve the memory efficiency of the code

The general syntax of a function:

  my_function <- function(arg 1, arg 2) {
    body of the function
  }

Unlike some other languages, in R, there is no special syntax for naming a function. One just defines like any other variable

Consider a simple function,

  # The add function
  add <- function(x, y=1) {
    x+y
  }

If y is not given any value, it will take the default argument.

There is no difference between the functions we define and R supplied functions.

There are three components for every function, the formal, the body and the environment.

  # The add function
  add <- function(x, y=1) {
    x+y
  }
  
  # Print the formals arguments
  formals(add)
  # Print the body 
  body(add)
  # Print the environment
  environment(add)
## $x
## 
## 
## $y
## [1] 1
## {
##     x + y
## }
## <environment: R_GlobalEnv>

Environment is typically invisible, but it is important on how the function behaves.

We will soon see scope for functions.

In this case the environment is the global environment.

How do we use (call) our function?

  # Defining the function "add"
  add <- function(x, y=1) {
    x+y
  }

  # Main code
  add(8)
## [1] 9
  # Or 
  add(8,2)
## [1] 10


Classwork/Homework: What does this function do?

  f <- function(x) {
          if (x < 0) {
              -x
          } else {
               x
            }
        }


Functions are just objects in R and act like any other object.

You can assign a function to any other object and the object will behave exactly like the assigned function.

Function need not have any name - they are called anonymous functions.

Anonymous functions has to be called in one line. For example, (function (x) {x+1})(2) is an increment function that will produce the output 3.

Scoping

R looks into the function scope for values.

  # Assign the value 10 for x
  x <- 10
  # Define a function f that returns a vector
  f <- function() {
    x <- 1
    y <- 3
    c(x,y)
  }
  # Print the function
  f()
## [1] 1 3

If the value is not in the scope, it looks one level above.

  # Assign the value 10 for x
  x <- 10
  # Define a function f that returns a vector
  f <- function() {
    y <- 3
    c(x,y)
  }
  # Print the function 
  # Note x is not in the scope
  f()
## [1] 10  3


Classwork/Homework: Use rm(x) in the above code to remove x from the global environment and report what happens when you call the function.


Lookup works the same for functions when it comes to scoping.

  # Define a function l
  l <- function(x) x + 20
  # Define another function and define l inside f
  f <- function() {
        l <- function(x) x + 15
        l(20)
  }
  f()
## [1] 35

Each call to a function has its own clean environment.

  # Define a function
  f <- function() {
    if (!exists("a")) {
      a <- 10
    } else {
      a <- a + 1
    }
    print(a)
  }
 f()
## [1] 10
The environment of a function is cleared at the end of each call.
By default, a function returns to the calling environment the value of the variable referred last.
Therefore, every call of this function is going to print only 10!

Classwork/Homework
How do we get to advance a in 1 at each call?

Another important object in R is the NULL that specifies the absence of elements in a data structure. Compare this with NA that specifies the absense of values in a data structure.

This can be seen as follows:

  # Find the type and length of NULL
  typeof(NULL)
## [1] "NULL"
  length(NULL)
## [1] 0
  
  # Find the type and length of NA
  typeof(NA)
## [1] "logical"
  length(NA)
## [1] 1

Quiz: How do you test for NA in a vector?

In fact there are many types of NA depending on the data type, one for each atomic type (like NA for integer etc.).

Missing values are contagious - any mathematical operation involving NA will result in NA.

Any logical comparison involving NA will result in NA too (like NA == NA).

Questions?

Selected materials and references