1. Creating a simple function 

In R, everything is either an object or a function. We have experimented with data simulation and data creation, so we have created our own objects. However, we have not yet created our own functions. In this session, we will explore the basics of creating our own functions in R. 

To create a function in R, you create a new function by assigning a name to a function() call, and then defining the function arguments and telling the function what to do.

The basic structure of a function is this:

function_name <- function(){
  # stuff the function does goes in here
}

Note the two curly braces: “{” and “}” - these define the start and the end of the function and what it will do. You can include anything you want inside the function, including creating objects and using other functions.

CREATE CODE:

Using the framework above, create a function named hello.world that will print “Hello World” to the console when run.

hello.world <- function(){
  print("Hello World")
}

To run your function, you need to include the brackets at the end, just like any other function.

hello.world()
## [1] "Hello World"

2. Adding arguments to your function

The hello.world function above does not have any arguments, but we can change that. When you define your function, you can also define the number of arguments it takes, their names, and whether they have default values.

function_name <- function(argument1 = default, argument2){
  # stuff the function does goes in here
}

CREATE CODE:

Make a function named printer which takes one argument named input with the default value “need input”.  Inside your function, ask it to print input.

printer <- function(words = "need input"){
  print(words)
}

Call your printer function without supplying an argument. What happens?

printer()
## [1] "need input"

Now call printer, but this time give it some input for the words argument. 

printer(words = 'Hello World')
## [1] "Hello World"
printer(1234)
## [1] 1234

3. Conditional logic inside functions 

Our printer function is not very special or even very smart to make, because the print() command already exists - we are just reinventing the wheel. However, we can use functions to define conditional logic suited to our needs.

For instance, let’s say we only wanted to print something if a certain condition is met. We can do that by using if and else statements inside of a function.

For example, the following function takes two arguments, checks to see if they are equal, and if they are equal, sends a message to the console indicating a match was found.

match.finder <- function(argument1, argument2){
  if(argument1 == argument2){
    message('match found')
  }
}

Let’s try it out:

match.finder(1,2)
match.finder(1,1)
## match found

Note that the if statement inside match.finder included its own set of curly brackets, which define the scope of that particular if statement. At this point it is crucial to note that the brackets must always match (open and close) where you want them to match, or else your conditionals may not work (or more likely, you function will just not run properly).

4. Expand match.finder

CREATE CODE:

Add a second if statement to match.finder which will send a message to the console if a match is not found. Then run the function with a match and without a match. You can put the second if statement below the first, but make sure the second if is not inside the curly brackets of the first if.

match.finder <- function(argument1, argument2){
  if(argument1 == argument2){
    message('match found')
  }
  if (argument1 != argument2){
    message('match not found')
  }
}

Let’s try it out:

match.finder(1,2)
## match not found
match.finder(1,1)
## match found

5. What else?

The function above works but is pretty lazy and prone to breaking if we expand it. Our function only has a binary decision to make (do the arguments match, yes or no) so it is not necessary to have two separate if statements. We can do the same thing above using else. The benefit is that using else() means we don’t need to specify the condition to be met - else() will automatically trigger if the first if returns FALSE. This is exactly how if_else() and ifelse() worked, from our prior sessions.

# a much more elegant version of match.finder()

match.finder2 <- function(argument1, argument2){
  if(argument1 == argument2){
    message('match found')
  } else {
    message('match not found')
  }
}

Let’s try it out:

match.finder2(1,2)
## match not found
match.finder2(1,1)
## match found

6. If inception

We can put if statements within if statements to have conditional logic trigger based on other conditional logic. For example, we could have a function first check if two numbers match. If they match, the function then checks to see if their sum is greater than 10, if so, it prints a message. (Note again how the curly brackets must be nested.)

useless.function <- function(num1, num2) {
  if(num1 == num2){
    if(num1 + num2 > 10){
      message('numbers match and sum is greater than ten')
    }
  }
}

Try it

useless.function(1,2)
useless.function(6,6)
## numbers match and sum is greater than ten
useless.function(100,200)

CREATE CODE:

Our useless.function is pretty useless, and it only tells us something if the numbers match! Add a new else statement to useless.function which prints a message when the numbers do not match. you should put your else statement after the second last bracket.

useless.function <- function(num1, num2) {
  if(num1 == num2){
    if(num1 + num2 > 10) {
      message('numbers match and sum is greater than ten')
    }
  } else {
    message('numbers do not match')
  }
}
useless.function(6,6)
## numbers match and sum is greater than ten
useless.function(4,5)
## numbers do not match

7. Else inception

CREATE CODE:

Now add an else statement so that the user is informed when the numbers match but are not greater than ten. You will want to add your else statement after the bracket that comes after your message which triggers when the numbers are greater than ten.

useless.function <- function(num1, num2) {
  if(num1 == num2){
    if(num1 + num2 > 10) {
      message('numbers match and sum is greater than ten')
    } else {
      message('numbers match but sum is not greater than ten')
    }
  } else {
    message('numbers do not match')
  }
}
useless.function(6,6)
## numbers match and sum is greater than ten
useless.function(4,4)
## numbers match but sum is not greater than ten
useless.function(4,5)
## numbers do not match

8. Make it stop

CREATE CODE:

Finally, modify useless.function so that it also checks whether a number is greater than ten when the numbers do not match. You should have four possible messages: 

# don't show.
useless.function <- function(num1, num2) {
  if(num1 == num2){
    if(num1 + num2 > 10) {
      message('numbers match and sum is greater than ten')
    } else {
      message('numbers match but sum is not greater than ten')
    }
  } else {
    if(num1 + num2 > 10) {
    message('numbers do not match and sum is greater than ten')
  } else {
    message('numbers do not match and sum is not greater than ten')
    }
  }
}
useless.function(6,6)
## numbers match and sum is greater than ten
useless.function(4,4)
## numbers match but sum is not greater than ten
useless.function(100,120)
## numbers do not match and sum is greater than ten
useless.function(1,2)
## numbers do not match and sum is not greater than ten

9. local variables inside functions

Maybe we could perform the tests first, and then use those results in subsequent conditions? We can save local variables within our function, check it out:

# the space in message is just for readability
more.useless.function <- function(num1, num2){
  check.match <- num1 == num2
  check.sum <- ((num1 + num2) > 10)
  message(check.match, ' ', check.sum)
}
more.useless.function(6,6)
## TRUE TRUE
more.useless.function(7,5)
## FALSE TRUE

CREATE CODE:

Modify more.useless.function to output the same messages as useless.function, but use the saved variables check.match and check.sum within the more.useless.function

# do not show
# the space in message is just for readability
more.useless.function <- function(num1, num2){
  check.match <- num1 == num2
  check.sum <- ((num1 + num2) > 10)
  
  if(check.match == TRUE) {
    if(check.sum == TRUE){
      message('numbers match and sum is greater than ten')
    } else {
      message('numbers match and sum is not greater than ten')
    } 
    
    } else {
      if(check.sum == TRUE){
      message('numbers do not match and sum is greater than ten')
    } else {
      message('numbers do not match and sum is not greater than ten')
      }
    } 
  }

test it:

more.useless.function(1,1)
## numbers match and sum is not greater than ten
more.useless.function(6,6)
## numbers match and sum is greater than ten
more.useless.function(7,9)
## numbers do not match and sum is greater than ten
more.useless.function(1,2)
## numbers do not match and sum is not greater than ten

10. else if

Let’s add one more final thing here: else if. We have been doing that above, although poorly. The else if allows us to continue checking multiple conditions through a function, whereas else only fires if one other condition is not met, and anything afterwards does not work unless we nest a bunch more if statements..

See below, we could add an many else if conditions as we wanted to check for a variety of conditions.

else.if.example <- function(x){
  if(x < 2){
    message('x is less than 2')
  } else if(x > 2){
    message('x is greater than 2')
  } else {
    message('x must be equal to 2')
  }
}
else.if.example(2)
## x must be equal to 2

11. are we done yet?

CREATE CODE:

Create a function named check.string.length with a single argument input. Your function should produce three different messages, depending on the input: 

I want you to use length() to check the size, so that you can check numbers and strings. To save us a headache, you should include this as the first line within the function:

effect <- sapply(strsplit(as.character(input), ''), length)

The above line will allow us to count the length of the word when split into separate characters - otherwise we would always get a length of 1 for any word we put into the function. In the function, use effect to control your conditional statements.

# don't show
check.length <- function(input){
  effect <- sapply(strsplit(as.character(input), ''), length)
  if(effect <= 5) {
    message(effect, ': small effect')
  } else if(effect > 5 & effect < 11){
    message(effect, ': medium effect')
  } else if(effect > 10){
    message(effect, ': large effect')
  }
}

Now, I want you to figure out how to get check.length to output a small effect, a medium effect, and a small effect.

check.length(1)
## 1: small effect
check.length('cat')
## 3: small effect
check.length('banana')
## 6: medium effect
check.length(12345678987654321)
## 17: large effect
check.length('New Zealand')
## 11: large effect
check.length('United States of America')
## 24: large effect