<- function(n) {
mySqrt return(n^0.5)
}
5 Creating Functions
Learning Objectives
- Know the basic syntax of custom functions in R.
- Understand how function arguments work in R.
- Understand the different types of return statements in R.
- Create your own custom functions.
- Understand the concept of helper functions.
- Understand the distinction between local and global variables.
Suggested Readings
- Chapter 19 of “R for Data Science”, by Garrett Grolemund and Hadley Wickham
- Chapters 2.4 - 2.7 of “Hands-On Programming with R”, by Garrett Grolemund
We already know how to use built-in functions like sum()
, round()
, sqrt()
, etc. And we can access other functions by installing external packages. But many times there just isn’t a function out there to do what you need. Fortunately, you can write your own!
5.1 Basic syntax
Here’s the syntax that you use to create a function:
FNAME <- function(ARG1, ARG2, ETC) {
STATEMENT1
STATEMENT2
return(VALUE)
}
What this does is create a function with the name FNAME
, which has arguments ARG1
, ARG2
, etc. Whenever the function is called, R executes the statements within the curly braces {}
, and then returns the VALUE
inside the return()
statement.
There’s a lot of different pieces to making a function. The way I like to remember how they all go together is to read the following English sentence:
“function name” is a function of () that does…
Each piece of the above sentence corresponds with a piece of code for writing a function:
“function name” | is a | function | of () | that does… |
---|---|---|---|---|
FNAME |
<- |
function |
(ARG1, ARG2, ETC) |
{} |
All the commands your function will execute go in the {}
.
For example, here’s the function mySqrt(n)
, which returns the square root of n
:
“function name” | is a | function | of () | that does… |
---|---|---|---|---|
mySqrt |
<- |
function |
(n) |
{ return(n^0.5) } |
And here’s mySqrt(n)
written in the typical format:
5.2 Arguments
Here’s a function with one argument:
<- function(x) {
square <- x^2
y return(y)
}
square(2)
#> [1] 4
square(8)
#> [1] 64
Here’s a function with multiple arguments:
<- function(x, y) {
sumTwoValues <- x + y
value return(value)
}
sumTwoValues(2, 3)
#> [1] 5
sumTwoValues(3, 4)
#> [1] 7
Functions don’t always have to take arguments. For example:
<- function() {
doSomething cat("Carpe diem!") # The cat() function prints whatever's inside it to the console
}
doSomething()
#> Carpe diem!
Default arguments:
Sometimes, a function has a parameter that has a natural default. We can specify that default value in the function definition, then choose whether or not to include it in the function call:
<- function(x, y=10) {
f return(x + y)
}
f(5) # 15
#> [1] 15
f(5, 1) # 6
#> [1] 6
5.3 The return()
statement
Here’s a basic example of using return()
to return a value:
<- function(x) {
isPositive return (x > 0)
}
isPositive(5) # TRUE
#> [1] TRUE
isPositive(-5) # FALSE
#> [1] FALSE
isPositive(0) # FALSE
#> [1] FALSE
The return()
statement ends the function immediately:
<- function(x) {
isPositive cat("Hello!") # Runs
return(x > 0)
cat("Goodbye!") # Does not run ("dead code")
}
<- isPositive(5) # Prints Hello, then assigns TRUE to x x
#> Hello!
x
#> [1] TRUE
Notice that in the above example, the cat("Goodbye!")
statement is ignored.
If you don’t include a return()
statement, R will return the value of the last statement by default (Don’t do this):
<- function(x) {
f + 42
x }
f(5)
#> [1] 47
<- function(x) {
f + 42
x + 7
x }
f(5)
#> [1] 12
5.4 The cat()
statement
The cat()
(short for “concatenating”) statement prints whatever arguments it is given to the console. The arguments can be of mixed types and it will convert them all to a concatenated string:
<- function(x) {
printX cat("The value of x provided is", x)
}
printX(7)
#> The value of x provided is 7
printX(42)
#> The value of x provided is 42
Mixing up return()
and cat()
is a common early mistake. For example:
<- function(x) {
cubed cat(x^3)
}
cubed(2) # Seems to work
#> 8
2*cubed(2) # Expected 16...didn't work
#> 8
#> numeric(0)
Here’s a correct version:
<- function(x) {
cubed return(x^3) # That's better!
}
cubed(2) # Works!
#> [1] 8
2*cubed(2) # Works!
#> [1] 16
5.5 Helper functions
It is often useful to break down more complicated problems into smaller “helper functions”. These helpers can be called in other functions. Here’s an example of using the helper functions square()
and squareRoot()
to compute the hypotenuse of a triangle:
<- function(x) {
square return(x^2)
}
<- function(x) {
squareRoot return(x^0.5)
}
<- function(a, b) {
hypotenuse return(squareRoot(square(a) + square(b)))
}
= 3
a = 4
b hypotenuse(a, b)
#> [1] 5
5.6 Local vs. global variables
All variables inside a function are called “local” variables and will NOT be created in the working environment. They can only be used locally within the function. For example:
<- function(x, y) {
minSquared = min(x, y)
smaller return(smaller^2)
}
minSquared(3, 4)
#> [1] 9
minSquared(4, 3)
#> [1] 9
If you try to call a local variable in the global environment, you’ll get an error:
<- function(x) {
square <- x^2
y return(y)
} y
#> Error in eval(expr, envir, enclos): object 'y' not found
“Global” variables are those in the global environment. These will show up in the “Environment” pane in RStudio. You can call these inside functions, but this is BAD practice. Here’s an example (Don’t do this!):
<- function() {
printN cat(n) # n is not local -- so it is global (bad idea!!!)
}printN() # Nothing happens because n isn't defined
= 5 # Define n in the global environment
n printN()
#> 5
5.7 Tips
One particularly useful function is almostEqual()
:
<- function(d1, d2) {
almostEqual = 0.00001
epsilon return(abs(d1-d2) <= epsilon)
}
This is useful when comparing numbers that are stored as floats and have lots of trailing zeros. For example, let’s do some simple addition:
<- 0.1 + 0.2
x x
#> [1] 0.3
If we compared x
to 0.3
, we would expect the result to be TRUE
, right?
== 0.3 x
#> [1] FALSE
What went wrong here? Well, what looks like a value of 0.3 is actually a float with a lot of zeros:
print(x, digits = 20)
#> [1] 0.30000000000000004441
By default, R doesn’t print out all these zeros, but they are the result of many small rounding errors that occur when computers do calculations.
This is where almostEqual()
comes in handy:
almostEqual(x, 0.3)
#> [1] TRUE
It only compares numbers out to a predefined decimal place, after which it ignores everything else. This will come in handy in your homework problems where you might get unexpected results.
Page sources
Some content on this page has been modified from other courses, including:
- CMU 15-112: Fundamentals of Programming, by David Kosbie & Kelly Rivers
- Danielle Navarro’s website “R for Psychological Science”