-
Notifications
You must be signed in to change notification settings - Fork 1
/
L3-functions.Rmd
332 lines (255 loc) · 7.59 KB
/
L3-functions.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
---
title: "Functions"
output: html_document
---
```{r setup, echo=FALSE, message=FALSE, warning=FALSE}
rm(list=objects()) # start with a clean workspace
source("knitr_setup.R")
```
> ### 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](https://r4ds.had.co.nz/functions.html) of "R for Data Science", by Garrett Grolemund and Hadley Wickham
> * [Chapters 2.4 - 2.7](https://rstudio-education.github.io/hopr/basics.html#write-functions) 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!
# 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:
```{r, eval=FALSE}
mySqrt <- function(n) {
return(n^0.5)
}
```
---
# Arguments
Here's a function with one argument:
```{r}
square <- function(x) {
y <- x^2
return(y)
}
```
```{r}
square(2)
square(8)
```
Here's a function with multiple arguments:
```{r}
sumTwoValues <- function(x, y) {
value <- x + y
return(value)
}
```
```{r}
sumTwoValues(2, 3)
sumTwoValues(3, 4)
```
Functions don't always have to take arguments. For example:
```{r}
doSomething <- function() {
cat("Carpe diem!") # The cat() function prints whatever's inside it to the console
}
```
```{r}
doSomething()
```
**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:
```{r}
f <- function(x, y=10) {
return(x + y)
}
```
```{r}
f(5) # 15
f(5, 1) # 6
```
---
# The `return()` statement
Here's a basic example of using `return()` to return a value:
```{r}
isPositive <- function(x) {
return (x > 0)
}
```
```{r}
isPositive(5) # TRUE
isPositive(-5) # FALSE
isPositive(0) # FALSE
```
The `return()` statement ends the function immediately:
```{r}
isPositive <- function(x) {
cat("Hello!") # Runs
return(x > 0)
cat("Goodbye!") # Does not run ("dead code")
}
```
```{r}
x <- isPositive(5) # Prints Hello, then assigns TRUE to x
x
```
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**):
```{r}
f <- function(x) {
x + 42
}
```
```{r}
f(5)
```
```{r}
f <- function(x) {
x + 42
x + 7
}
```
```{r}
f(5)
```
---
# 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:
```{r}
printX <- function(x) {
cat("The value of x provided is", x)
}
```
```{r}
printX(7)
printX(42)
```
Mixing up `return()` and `cat()` is a common early mistake. For example:
```{r}
cubed <- function(x) {
cat(x^3)
}
```
```{r}
cubed(2) # Seems to work
2*cubed(2) # Expected 16...didn't work
```
Here's a correct version:
```{r}
cubed <- function(x) {
return(x^3) # That's better!
}
```
```{r}
cubed(2) # Works!
2*cubed(2) # Works!
```
---
# 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:
```{r}
square <- function(x) {
return(x^2)
}
squareRoot <- function(x) {
return(x^0.5)
}
hypotenuse <- function(a, b) {
return(squareRoot(square(a) + square(b)))
}
a = 3
b = 4
hypotenuse(a, b)
```
---
# 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:
```{r}
minSquared <- function(x, y) {
smaller = min(x, y)
return(smaller^2)
}
```
```{r}
minSquared(3, 4)
minSquared(4, 3)
```
If you try to call a local variable in the global environment, you'll get an error:
```{r error=TRUE}
square <- function(x) {
y <- x^2
return(y)
}
y
```
_"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!**):
```{r include=FALSE}
n <- NULL
```
```{r error=TRUE}
printN <- function() {
cat(n) # n is not local -- so it is global (bad idea!!!)
}
printN() # Nothing happens because n isn't defined
```
```{r}
n = 5 # Define n in the global environment
printN()
```
---
# Tips
One particularly useful function is `almostEqual()`:
```{r}
almostEqual <- function(d1, d2) {
epsilon = 0.00001
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:
```{r}
x <- 0.1 + 0.2
x
```
If we compared `x` to `0.3`, we would expect the result to be `TRUE`, right?
```{r}
x == 0.3
```
What went wrong here? Well, what looks like a value of 0.3 is actually a float with a lot of zeros:
```{r}
print(x, digits = 20)
```
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:
```{r}
almostEqual(x, 0.3)
```
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](http://www.kosbie.net/cmu/spring-17/15-112/), by [David Kosbie](http://www.kosbie.net/cmu/) & [Kelly Rivers](https://hcii.cmu.edu/people/kelly-rivers)
- Danielle Navarro's website ["R for Psychological Science"](https://psyr.org/index.html)