(Jul 19, 19)
As the last part of R fundamentals series, we will study elements of R programming.
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:
The AND truth table
# AND truth table
TRUE & TRUE : TRUE
TRUE & FALSE : FALSE
FALSE & TRUE : FALSE
FALSE & FALSE : FALSEExample: 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 : FALSEExample: x <- 5 and (x >5) | (x <8) will evaluate to TRUE.
The NOT truth table
# NOT truth table
!TRUE : FALSE
!FALSE : TRUEExample: 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] FALSEUsing the resulted logical vector allows us to extract the values or indeces of elements
Classwork/Homework:
Why do we need functions?
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.
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
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] 1Quiz: 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).