R

Control Structures, Functions, and Objects

If Statements

  • The if statement in R is very C like in it's syntax
    if (condition) {
    } else {
    }
    
  • Addition else if clauses have the syntax of
    else if (condition) {
      }
    
In [ ]:
## This won't work, else needs to be on same line as end of if
if( 4 > 5) {
    print("Bad Math")
}
else {
    print("Seems right to me")
}
In [ ]:
if( 4 > 5) {
    print("Bad Math")
} else {
    print("Seems right to me")
}
In [ ]:
a <- 4
b <- 2
if ( a %% b == 0)
{
    print(paste(a,"is even"))
} else {
     print(paste(a,"is odd"))
}
In [ ]:
## Produces warning only, probably not the intended test condition
vec1 <- c(2,4,6,8)
if( vec1 %% 2 == 0){
    print("All elements are even")
} else{
    print("Not all elements are even")
}
In [ ]:
vec1 <- c(2,4,6,8)
if( all(vec1 %% 2 == 0)){
    print("All elements are even")
} else{
    print("Not all elements are even")
}

If Tricks

  • if on the right hand side of an assignment
    var <- if (condition){
          value 
          }
      else{ 
          value
      }
    
  • The ifelse function to apply to vectors
    ifelse(expression_generating_boolean_vector, value_if_true, value_if_false)
    
In [ ]:
num1 <- 100
num2 <- 1000
largest_num <- if(num1 > num2){
    num1
} else {
    num2
}
print(largest_num)
In [ ]:
float <- 100.004
truncated <- if (float %% 1 != 0){
    float %/% 1
} else{
    float
}
print(truncated)
In [ ]:
vec3 <- 1:10
print(ifelse(vec3 %% 3 == 0, 
             "Is Divisible by 3",
             "Isn't Divisibly by 3" ))
In [ ]:
vec4 <- -5:5
print(ifelse(vec4 < 0,-1,1))

Switch Statement

  • R doesn't have a switch statement, only a switch function
    switch(expression, value1, value2, value3...)
    switch(expression, key1 = value1, key2 = value2, default)
    
  • The switch function takes an expression, followed by a list of things to return if matched
    • With out any parameter keywords, the expression needs to be an integer
    • When using keywords, a parameter with out a keyword is assumed to be a default value
In [ ]:
word <- switch(3,"one","two","three","four","five","six","seven")
print(word)

translation <- switch(word, one="uno",two="dos",
                      three="tres",four="quatro",
                      "un numero")
print(translation)

print(switch("seven", one="uno",two="dos",
             three="tres",four="quatro",
             "un numero"))

For Loops

  • For loops in R look like for-each loops, but are still numeric
  • The function seq_along(X) produces the sequence of indices for a given object to loop through
    for(var in integer_vector){
      }
    
  • Many libraries exist that attempt to produce better, faster for loops
In [ ]:
for(i in 1:5){
    print(i ^ 2)
}
In [ ]:
print(mtcars)
In [ ]:
## How can I make this print thet names of the column?
for (feature in seq_along(mtcars)){
    print(paste("The median is",
                median(mtcars[[feature]])))
}

Logic Controlled Loops

  • R offers only one truly controlled logic loop, the standard while loop
    while(condition){
      }
    
  • R also provides a repeat loop, which repeats forever, and must be broken out of explicitly
    repeat{
           if(condition) break
      }
    
In [ ]:
haystack <- c(1,34,5,5,1,4,6,0)
i <- 1
while(haystack[i] != 6){
        i <- i + 1
}
print(paste("I found 6 at position",i))
In [ ]:
end <- 1
repeat{
    print("This is the song that never ends, yes it goes on and on my friends,
           some people started singing it not knowing what it was, and they will keep on
           singing it forever just because...")
    if (end == 10) break
    end <- end + 1
}

Lapply

  • Often times we are just looping over a data structure in R to apply a function to every member
    • The R function lapply does this without writing out the entire loop
  • This is the first of many functional programming style statements we will encounter in R
    • Entire libraries have been created to further this style of programming
      lapply(data,function)
      
In [ ]:
## What is the return type do you think?
results_l <- lapply(mtcars,median)
print(results_l)
In [ ]:
## What is the return type do you think?
results_s <- sapply(mtcars,median)
print(results_s)

Functions

  • A function in R is declared using the syntax
    function(parameter list){
      function body
      }
    
  • The result of this is assigned to a variable which is then used as the function name
In [ ]:
my_first_function <- function(){
    print("Hello!")
} 

my_first_function()
In [ ]:
my_second_function <- function(a,b,c){
    print(a * b + c)
}
my_second_function(1,2,3)

Returning From Functions

  • To explicitly return from an R function, use the return function
    • Note that this is a function, not a statement, and requires parentheses
      return (x)
      
  • If no return function is called, an R function will return the value of the last expression of the function by default
In [ ]:
explicit_return <- function(a,b)
    {
    return (a %/% b + a %% b)
}
print(explicit_return(20,3))
In [ ]:
implicit_return <- function(a,b)
{
a %/% b + a %% b    
}
print(implicit_return(20,3))

Function Practice

  • Use lapply and a function to return the squares of all numbers from 1 to 25

Function Parameters and Arguments

  • R provides a wide variety of parameter options
    • Keyword parameters
    • Default parameters
    • Positional parameters
  • R also allows a list to provide the arguments to a function, using the do.call function
    do.call(function_name, list_of_arguments)
In [ ]:
param_example <- function(a, b = 0, c = 1){
    result <- if (b == 0){
        a / c
    } else {
        a / b
    }
}
print(param_example(20))
print(param_example(20,c=2))
print(param_example(20,c=2,b=5))
print(param_example(20,2,5))

Keyword Parameters Detailed

  • R is a bit unique in that keyword parameters don't have to be completely spelled out when used
  • The order of evaluation is
    • Attempt to find a perfect match
    • Attempt to find a match of the keyword prefixes
    • Use positional parameters
In [ ]:
keywords <- function(a, power=2, d = 3, donut = 5)
{
        a ^ power + d * donut
}
print(keywords(2))
print(keywords(2,p=3))
print(keywords(2,d=3))
print(keywords(2,do=3))
In [ ]:
keywords_trouble <- function(a, power=2, dollop = 3, donut = 5)
{
        a ^ power + dollop * donut
}
print(keywords_trouble(2,d=5))
print(keywords_trouble(2,do=5))
print(keywords_trouble(2,dol=5))

Lazy Evaluation

  • Parameters in R are not evaluated until they are absolutely needed
    • Evaluation can be forced by using the force function
    • Parameters in R are evaluated with respect to the current function
      • If you pass a function as an argument, it will be evaluated lazily, in whatever environment currently exists in the function
In [ ]:
lazee <- function(a, b)
    {
    "I don't use my parameters"
}
print("String"  * "string")
print(lazee("String" * "string",3))
In [ ]:
not_lazee <- function(a, b,....)
{
    print(length(list(....)))
    force(a)
    "I don't use my parameters"
}
print("String"  * "string")
print(not_lazee("String" * "string",3,43))
In [ ]:
lazier <- function(a,b = 2*a)
    {
    a + b
}
print(lazier(3))
print(lazier(3,4))
In [ ]:
laziest <- function(command = ls())
{
    var1 <- 2
    var2 <- 3
    command
}
print(laziest())
cat("\n")
print(laziest(ls()))

User-Defined Infix Functions

  • It is possible in R to name a function such that it can be called as arg FUNCTION_NAME arg
  • Any function whose name begins and ends with the percent symbol (%) is an infix function
    • When declaring this function, the name must be enclosed in backticks (`)
      `%FUNCTION%` <- function(a,b){}
      
In [ ]:
`%+%` <- function(a,b){
    paste(a,b)
}
print("String" %+% "another")
print("String" %+% 45)

print(45 %+% "String")
In [ ]:
#`% %` <- paste -- this works too
`% %` <- function(a,b){
    paste(a,b)
}
print("String" % % "another")
print("String" % % 45)
print(45 % % "String")

Assignment Functions

  • We have seen many uses of functions to perform assignment
    names(vec) <- c(NAMES)
    
  • These are actually special functions,
    • The assignment operator is the last two characters of the function name
    • Must use backticks again when declaring the function
      `FUNCTION_NAME<-` <- function(object,value){}
      
In [ ]:
`multiply_first<-` <- function(x,value)
{
    x[1] <- x[1] * value
    x
}
vec_to_change <- c(1,2,3,4)
print(vec_to_change)
multiply_first(vec_to_change) <- 4
print(vec_to_change)
multiply_first(vec_to_change) <- 2
print(vec_to_change)

Assignment Function Practice

  • Write an assignment function that sets the first x values of a vector to zero, where x is the RHS of the assignment

Operator Overriding

  • Every operator in R is just a call to a function
  • To override the definition, enclose the operator in backticks and assign a new function to is
    `+` <- function(a,b){ a - b}
    
In [ ]:
print(3 + 4)
print(`+`(3,4))

cat("\n")
vec_again <- 10:15
print(vec_again[2])
print(`[`(vec_again,2))
In [ ]:
`+` <- function(a,b){ a - b}
print(3 + 4)
print(100+1)
rm(`+`)
cat("\n")
print(3 + 4)
print(100+1)
In [ ]:
`[` <- function(obj,index){
    "Sorry, you can't do that"
}
print(vec_again[2])
print(vec_again[[2]])
rm(`[`)
cat("\n")
print(vec_again[2])

Objects in R

  • R supports three different types of objects, all declared and used in different ways
    • S3 objects
    • S4 objects
    • RC objects

S3 Objects

  • S3 objects are the simplest and most common type of object in R
  • Based of the design of objects in the third version of the S language
    • Came out in 1988
    • Switched from FORTRAN to C
  • Methods don't belong to objects, uses a form of object-oriented programming known as generics

Creating an S3 Object

  • Any existing object can be converted into an S3 object
    • Use the structure function and assign the results to a variable
    • Use the assignment version of the class function to give an existing variable a class attribute
  • Both of these methods create a single instance at a time
In [ ]:
my_first_instance <- structure(1:5,class="specialVector")
print(my_first_instance)
print(str(my_first_instance))
In [ ]:
my_second_instance <- list(a_member = 2, another= "A String")
print(my_second_instance)

class(my_second_instance) <- "listClass"
print(str(my_second_instance))

S3 Constructor

  • An S3 constructor is one that simply hides the call to structure or class inside of a function
  • By convention, it should have the same name as the class, although this isn't strictly necessary
    class_name <- function(parameters){
      structure(list(parameters),class="class_name")
      }
    
In [ ]:
vehicle <- function(n_wheels,color){
    structure(list(m_n_wheels = n_wheels, m_color = color ),
              class="vehicle")
}

myCar <- vehicle(4,'black')
print(class(myCar))

Inheritance

  • The class attribute of an object cab actually be a vector
    • We can use this to simulate inheritance
    • In the previous examples, we are inheriting from the list class
      child_class <- function(parameters)
      {
        self <- parent_class(parameters)
        class(self) <- append("child_class",class(self))
        self
      }
      
In [ ]:
car <- function(color){
    self <- vehicle(4,color)
    class(self) <- append("car",
                          class(self))
    self
}
my_new_car <- car('black')
print(class(my_new_car))

Methods

  • R uses a style of OOP known as generics
    • An object is passed to a function, which then acts on the object
    • By writing multiple different "versions" of the same function, we can specify how the function should interact on a given object
  • Most functions we have seen so far are actually generics, ie
    t(df) # actually t.data.frame(df)
    
In [ ]:
mm <- as.data.frame(matrix(1:20,ncol=4))
print(t(mm))
print(t.data.frame(mm))
In [ ]:
print(t)

print(t.data.frame)

The Generic Function

  • The top level function must be created and follows a very standard format.
    • The UseMethod function denotes that this function should actually dispatch to a more appropriate function, based on the object that was passed in
  • The generic function for t might look like
    t <- function(obj){
          UseMethod("t")
      }
    

User-Defined Generics

  • Write a generic function with the name of the function you want
  • For each class you want to define a different version of your function for, name it as function_name.class_name
    • The generic function will use the class attribute of the function passed to it to determine which to call
  • A function named function_name.default can be defined to be run in the event no match is found
In [ ]:
print(my_new_car)
print.vehicle <- function(x)
{
    "My vehicle is " % % x[['m_color']] % % "in color and  has" % % x$m_n_wheels % % "wheels."
}
print(my_new_car)
#print.vehicle <- print.default
rm(print.vehicle)
print(my_new_car)
In [ ]:
makeNoise <- function(x){
    print(class(x))
    UseMethod("makeNoise")
}

makeNoise.vehicle <-function(x){
    "Generic Vehicle Noise"
}

makeNoise.car <- function(x){
    "BEEP BEEP"
}

makeNoise.default <- function(x){
    "You can't make a noise"
    }
In [ ]:
print(makeNoise(myCar))
print(makeNoise(my_new_car))
print(makeNoise("Random String"))

S4 Classes

  • S4 is based on the object system from the 4th version of S, released in 1998
  • Not as commonly found, but some more complex libraries do make uses of it
  • Very similar to S3, but more formal
    • Classes must be initialized using the new function
    • The properties of the classes are part of the definition (called slots in R)
    • Inheritance is done through use of the contains keyword

Reference Classes

  • Reference classes are the newest object system in R
    • Released around 2010
  • Behave much more like traditional classes in other languages
    • Methods now belong to objects