Functions

(Sections 3.1 - 3.2)

Introduction to Functions

Saying “Kansas”

Dorothy wants to say “Kansas” four times. This is easy enough:

cat("Kansas\n")
Kansas
cat("Kansas\n")
Kansas
cat("Kansas\n")
Kansas
cat("Kansas\n")
Kansas

Saying “Kansas”, Cleverly

Here’s a way to do it in just two lines:

oneLine <- paste("Kansas", "\n", sep = "")
allLines <- rep(oneLine, times = 4)
cat(allLines, sep = "")
Kansas
Kansas
Kansas
Kansas

Generalize

word <- "Kansas"
n <- 4
oneLine <- paste(word, "\n", sep = "")
allLines <- rep(oneLine, times = n)
cat(allLines, sep = "")
Kansas
Kansas
Kansas
Kansas

Reuse

word <- "Kentucky"
n <- 5
oneLine <- paste(word, "\n", sep = "")
allLines <- rep(oneLine, times = n)
cat(allLines, sep = "")
Kentucky
Kentucky
Kentucky
Kentucky
Kentucky

Encapsulate in a Function

manyCat <- function(word, n) {
  oneLine <- paste(word, "\n", sep = "")
  allLines <- rep(oneLine, times = n)
  cat(allLines, sep = "")
}

Now Just Use the Function!

manyCat(word = "Kentucky", n = 5)
Kentucky
Kentucky
Kentucky
Kentucky
Kentucky

Advantages of Functions

  • They allow us to re-use code instead of repeating it.
  • The more general the function, the more ways it can be reused
  • New feature changes only have to be made in one place—where the function is defined.

DRY

Don’t Repeat Yourself (DRY)

A principle of computer programming that holds that general solutions should be set forth in one place and usable in many places, and that information needed in many places should be defined authoritatively in one place.

Writing functions keeps your code DRY.

A Function is Like A Magic Hat

  • the function = the hat
    • manyCat
    • just the function there (no parentheses)
  • calling the function is like tapping the hat
    • manyCat(word = "Kentucky", n = 5)
    • parentheses make the call
  • then cool stuff happens! (e.g., fancy rabbit comes up)
    • console output

Function Syntax

function is the reserved word in R that permits us to define functions.

The general form of a function definition is:

functionName <- function(parameter, parameter, ...) {
  Body of the Function ...
}
  • name of function is an identifier
  • parameters in parentheses, separated by commas
  • body of function is the encapsulated code
    • it is enclosed in brackets
    • it uses the parameters

Definitions

Parameters of a Function

The parameters of a function (also called the formal arguments of the function) are the names that will be used in the body of the function to refer to the actual arguments supplied to the function when it is called.

Argument

An argument for a function is a value that is assigned to one of the parameters of the function.

Parameters vs. Arguments

manyCat(word = "Kansas", n = 4)
Parameter Argument
word “Kansas”
n 4

The Body

Body of a Function

The body of a function is the code that is executed when the function is called.

In R, when the body consists of more than one expression then it appears inside of curly braces.

No Need For Brackets …

… if the body is just one expression:

add3 <- function(x) x+3
add3(x = 5)
[1] 8
add3(-7)
[1] -4

In the Call, Parameters Need Not Be Named ….

… as long as you keep in mind the order.

These calls all do the same thing:

manyCat(word = "Hello", n = 4)
manyCat(word = "Hello", 4)
manyCat("Hello", n = 4)
manyCat("Hello", 4)
manyCat(n = 4, word = "Hello")

But This is Wrong

manyCat(4, "Hello")
## NAs introduced by coercion
## Error in rep(wordWithNewline, times = n) :
## invalid 'times' argument

R thought:

  • word is 4
  • n is “Hello”

Practice

What would happen if you enter this?

manyCat("Alabama" 7)

Why?

Advice for Writing Functions

Go Slow

DON’T:

  • just start typing code

DO:

  • read the specifications carefully
  • break up your work into small steps

Suggested Procedure

Break your work into five steps:

  1. Read the specifications carefully.
  2. Write a program that performs the stated task, for a specific example. If necessary, revise your program until it is fully general.
  3. Encapsulate your work into a function.
  4. Test the function on a few examples.
  5. Read the specifications again. If necessary, revise the code for the function until it meets all specifications, and test again.

Problem: A Scrambler

Write a function called scramble() that takes a given vector and returns a vector consisting of all the odd-numbered elements in front of all of the even-numbered elements.

The function should tke a single parameter called vec, the vector to be scrambled.

Typical examples of use should be like this:

scramble(vec = c("Raj", "Sita", "Vip", "Nila", "Bhima"))
[1] "Raj"   "Vip"   "Bhima" "Sita"  "Nila" 
scramble(vec = 1:10)
 [1]  1  3  5  7  9  2  4  6  8 10
scramble(vec = letters)
 [1] "a" "c" "e" "g" "i" "k" "m" "o" "q" "s" "u" "w" "y" "b" "d" "f" "h" "j" "l"
[20] "n" "p" "r" "t" "v" "x" "z"

Step 1: Read the Specs Carefully

Read the specifications carefully.

So let’s go back and read them again!

Step 2: Program to Solve for an Example

Write a program that performs the stated task, for a specific example. If necessary, revise your program until it is fully general.

Set up an example:

vec <- c("the", "cat", "is", "in", "my", "chair")

Pro Tip: Your example should have the same name as the desired parameter. (That will make later work easier.)

Step2: Think About Indices

What are the odd indices of vec?

They are 1, 3 and 5.

What are the even indices of vec?

They are 2, 4, and 6.

Step 2: Our First Stab

So we want this:

vec[c(1, 3, 5, 2, 4, 6)]
[1] "the"   "is"    "my"    "cat"   "in"    "chair"

Step 2: Did We Succeed?

“If necessary, revise your program until it is fully general.”

Here is our program:

vec <- c("the", "cat", "is", "in", "my", "chair")
vec[c(1, 3, 5, 2, 4, 6)]
[1] "the"   "is"    "my"    "cat"   "in"    "chair"

It will be “fully general” if the only thing we have to change is the binding to vec.

Step 2: Rats!

vec <- c("the", "cat", "is", "in", "my", "elm", "tree")
vec[c(1, 3, 5, 2, 4, 6)]
[1] "the" "is"  "my"  "cat" "in"  "elm"

Just changing vec to the desired input was not enough!

Our solution won’t work unless vec has length 6.

Step 2: Back to the Drawing Board

We need a program that works for a vector of any length.

Let’s make a name for the length:

n <- length(vec)

Think About Indices More Generally

Construct the odd indices:

oddIndices <- seq(1, n, by = 2)
oddIndices
[1] 1 3 5 7

Construct the even indices:

evenIndices <- seq(2, n, by = 2)
evenIndices
[1] 2 4 6

Step 2: Still Thinking Generally About Indices

All of the indices:

allIndices <- c(oddIndices, evenIndices)
allIndices
[1] 1 3 5 7 2 4 6

Now select, based on all of the indices:

vec[allIndices]
[1] "the"  "is"   "my"   "tree" "cat"  "in"   "elm" 

Step 2: Our Second Stab at It

So it seems this program will work:

vec <- c("the", "cat", "is", "in", "my", "chair")
n <- length(vec)
oddIndices <- seq(1, n, by = 2)
evenIndices <- seq(2, n, by = 2)
allIndices <- c(oddIndices, evenIndices)
vec[allIndices]
[1] "the"   "is"    "my"    "cat"   "in"    "chair"

If the program REALLY works, it will work for a different example just by changing vec.

Step 2: Checking …

So let’s change vec and see:

vec <- letters
n <- length(vec)
oddIndices <- seq(1, n, by = 2)
evenIndices <- seq(2, n, by = 2)
allIndices <- c(oddIndices, evenIndices)
vec[allIndices]
 [1] "a" "c" "e" "g" "i" "k" "m" "o" "q" "s" "u" "w" "y" "b" "d" "f" "h" "j" "l"
[20] "n" "p" "r" "t" "v" "x" "z"

Looking good!

Step 3: Encapsulate Into a Function

Encapsulate your work into a function.

The function-skeleton is this:

scramble <- function(vec) {
  
  ## the "work" involving parameter goes here
  
}

Step 3: Encapuslate Your Work

scramble <- function(vec) {
  n <- length(vec)
  oddIndices <- seq(1, n, by = 2)
  evenIndices <- seq(2, n, by = 2)
  allIndices <- c(oddIndices, evenIndices)
  vec[allIndices]
}

You encapsulate all of the “work” (except the first line that defined vec).

Step 4: Test on a Few Examples

Test the function on a few examples.

scramble(vec = c("Raj", "Sita", "Vip", "Nila", "Bhima"))
[1] "Raj"   "Vip"   "Bhima" "Sita"  "Nila" 
scramble(vec = 1:12)
 [1]  1  3  5  7  9 11  2  4  6  8 10 12
scramble(vec = LETTERS)
 [1] "A" "C" "E" "G" "I" "K" "M" "O" "Q" "S" "U" "W" "Y" "B" "D" "F" "H" "J" "L"
[20] "N" "P" "R" "T" "V" "X" "Z"

Step 5: Read the Specs Again

Read the specifications again. If necessary, revise the code for the function until it meets all specifications, and test again.

OK, let’s do that …

Write a function called scramble() that takes a given vector and returns a vector consisting of all the odd-numbered elements in front of all of the even-numbered elements.

The function should tke a single parameter called vec, the vector to be scrambled.

Typical examples of use should be like this:

scramble(vec = c("Raj", "Sita", "Vip", "Nila", "Bhima"))
[1] "Raj"   "Vip"   "Bhima" "Sita"  "Nila" 
scramble(vec = 1:10)
 [1]  1  3  5  7  9  2  4  6  8 10
scramble(vec = letters)
 [1] "a" "c" "e" "g" "i" "k" "m" "o" "q" "s" "u" "w" "y" "b" "d" "f" "h" "j" "l"
[20] "n" "p" "r" "t" "v" "x" "z"

Step 5: Yippee!

… It seems that we really met all of the problem specifications. We are done!

Question

scrambleWrong <- function(vec) {
  vec <- c("Raj", "Sita", "Vip", "Nila", "Bhima")
  n <- length(vec)
  oddIndices <- seq(1, n, by = 2)
  evenIndices <- seq(2, n, by = 2)
  allIndices <- c(oddIndices, evenIndices)
  vec[allIndices]
}

Why is the above an incorrect solution to the problem?

Two Reasons

  • (small reason) The functions was supposed to be called scramble(), not scrambleWrong().
  • (main reason) The function does not work as required:
scrambleWrong(vec = letters)
[1] "Raj"   "Vip"   "Bhima" "Sita"  "Nila"