-
Notifications
You must be signed in to change notification settings - Fork 5
/
400_functions.Rmd
461 lines (240 loc) · 9.14 KB
/
400_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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
<!--
This file by Martin Monkman
is licensed under a Creative Commons Attribution 4.0 International License
https://creativecommons.org/licenses/by/4.0/
-->
# (PART) Week 4 {-}
# Write your own functions {#functions}
## Objectives
* Understand and describe when and why a function is of value
* Understand the components of a function
![_`function()` function, what's your function?_](static/img/shr_function.jpg)
## Setup
This chunk of R code loads the packages that we will be using.
```{r eval = FALSE}
library(tidyverse)
```
## Introduction to functions
Functions are an important part of programming in R. You have already used many functions—now it's time to write your own.
Functions have particular value in making your code DRY (don't repeat yourself).^[The opposite is WET (we enjoy typing).] If you need to do the same thing over and over again, whether in a single script or to create the a report with the same calculations when next month's data arrives, you can either copy-and-paste with some find-and-replace, or take the time to write a generalized function.
Using functions has the following benefits:
* there's fewer opportunities for error (say you miss a replacement)
* your code will be better organized and easier to read
There are three parts to any function:
* function name
* inputs, which are called "arguments"
* code statement(s) that do something with/to the argument(s)
In R, the function to create a function is `function()`.
The code looks like this:
```
my.function.name <- function(my_argument_1, my_argument_2, ...) {
code_statement
}
```
## A basic example
(from ["Writing your own Functions" in _Hands-on Programming with R_](https://rstudio-education.github.io/hopr/basics.html#write-functions), by Garrett Grolemund)
Let's say you want to simulate rolling 2 six-sided dice. Here's some code that does that:
```{r}
# define the possible outcomes
die <- 1:6
# a random pair of numbers from the `die` object
dice <- sample(die, size = 2, replace = TRUE)
# then add them up
sum(dice)
```
To do this more than once, you would need to write the last two lines of code in again and again:
```{r}
dice <- sample(die, size = 2, replace = TRUE)
sum(dice)
dice <- sample(die, size = 2, replace = TRUE)
sum(dice)
dice <- sample(die, size = 2, replace = TRUE)
sum(dice)
```
_Or_ you could write a function.
In this case, our _code_statement_ is the three lines of code:
```{r}
roll <- function() {
die <- 1:6
dice <- sample(die, size = 2, replace = TRUE)
sum(dice)
}
```
Note that when we run this chunk, the function gets stored in our environment, and appears under Functions.
To run the function we just need the following:
```{r}
roll()
# and if we want to assign the result of the function to an object:
my_roll <- roll()
```
And then if we want to get the distribution of values from rolling a pair of dice 100 times, we can use the `replicate()` function:
```{r}
replicate(100, roll())
```
## Another example
Let's write an equation that converts 50 pounds to the equivalent in kilograms.
1 kilogram (kg) is equal to 2.2046226218488 pounds (lb) (and conversely, 1 pound is equal to 0.45359237 kilograms).
$$ 1 kg = 2.2046226218488 lb $$
When we translate that formula into code that will convert the number of pounds into kilograms, it looks like this, where `kg` is "kilograms" and `lb` is "pounds":
```{r}
lb <- 50
kg <- lb / 2.2046226218488
kg
```
But what if we want to convert the weight of many items of different size? Time for a function.
We call the function with `lb_to_kg_conversion()`, and pass the value of pounds `lb`. The function returns the value of `kg` after calculating the value.
```{r}
lb_to_kg_conversion <- function(lb) {
kg <- lb / 2.2046226218488
return(kg)
}
```
Let's test it with our weight of 50 pounds:
```{r}
lb_to_kg_conversion(lb = 50)
```
Now we can use that function to convert any number of weights. Let's make a tibble with 10 weights, where `weight_lb` is each weight in pounds.
```{r}
# the `set.seed()` function ensures that our pseudo-random number generation
# always returns the same values
set.seed(1)
my_weights <- tibble(
weight_name = letters[1:10], # the letters from a to j
weight_lb = round(runif(n=10, min=1, max=20), 3)
) # a random sequence of numbers between 1 and 20
my_weights
```
What is the weight of those items in kilograms? I can apply my function...
```{r}
my_weights |>
mutate(weight_kg = lb_to_kg_conversion(lb = weight_lb))
```
That's good. But what if we want our result to be rounded to 2 decimal places?
We could rewrite our function to add that additional line of code:
* note that in the 2nd line of the function's code, we are rounding `kg` before returning it.
```{r}
lb_to_kg_conversion <- function(lb) {
kg <- lb / 2.2046226218488
kg <- round(kg, 2)
return(kg)
}
```
Now re-run the `mutate()`:
```{r}
my_weights |>
mutate(weight_kg = lb_to_kg_conversion(lb = weight_lb))
```
What if we want want to have some flexibility in our rounding? We can add a second argument to our function call—`rnd`—which will be then applied in `round()`
```{r}
lb_to_kg_conversion <- function(lb, rnd) {
kg <- lb / 2.2046226218488
kg <- round(kg, rnd)
return(kg)
}
```
And rerun with the area to 3 decimals:
```{r}
my_weights |>
mutate(weight_kg = lb_to_kg_conversion(lb = weight_lb, rnd = 3))
```
### Setting default values
What if we usually want to round to 2, but want to have some flexibility? We can assign the default value of 2 to the rounding argument. If `rnd` doesn't get specified, it will be 2...but we can over-ride that with a specific value assignment.
```{r}
lb_to_kg_conversion <- function(lb, rnd = 2) {
kg <- lb * 2.2046226218488
kg <- round(kg, rnd)
return(kg)
}
```
With no specification:
* note that we don't have to be explicit in telling the function how to use the variable `weight_lb`—it assumes that since it is the first argument passed, it is `lb`. And because we don't pass a second argument, the function will use the default value we've specified, in this case "2".
```{r}
my_weights |>
mutate(weight_kg = lb_to_kg_conversion(weight_lb))
```
Or override with rounding to 3 decimals:
```{r}
my_weights |>
mutate(weight_kg = lb_to_kg_conversion(weight_lb, 3))
```
## Exercises
::: {.rmdtip}
### Temperature
Write a function that converts temperature from degrees Celsius (C) to Fahrenheit (F), using the formula
$$\ F = (C * 9/5) + 32 $$
Now, write and test a function to calculate the temperature in Fahrenheit, where it is automatically rounded to 1 decimal place.
<details>
<summary>
Solution
</summary>
The function for conversion from pounds to kilograms is pasted below:
```{r}
# example
lb_to_kg_conversion <- function(lb, rnd = 1) {
kg <- lb / 2.2046226218488
kg <- round(kg, rnd)
return(kg)
}
```
Now let's edit the code above, but to work with the formula for temperature:
```{r}
# solution
Cel_to_Fah_conversion <- function(Cel, rnd = 2) {
Fah <- (Cel * 9/5) + 32
Fah <- round(Fah, rnd)
return(Fah)
}
```
Now, generate a tibble with the name `my_temperatures`:
```{r}
set.seed(42)
my_temperatures <- tibble(
temp_name = letters[1:10],
temp_Cel = round(runif(10, min = 1, max = 20), 2))
my_temperatures
```
And use the function to convert each temperature to Fahrenheit in the tibble:
```{r}
my_temperatures |>
mutate(temp_Fah = Cel_to_Fah_conversion(temp_Cel))
```
Now round to the nearest integer (zero decimals).
```{r}
my_temperatures |>
mutate(temp_Fah = Cel_to_Fah_conversion(temp_Cel, rnd = 0))
```
</details>
:::
::: {.rmdtip}
### Multiple dice rolls
The dice rolling function we wrote above assumes we are always rolling 2 dice. Modify the function so that the default is 2, but that the number of dice can be varied.
<details>
<summary>
Solution
</summary>
In this solution, an argument `number_of_dice` is added to the `function()`, and specified as "2". Then within the `sample()` function, the `size = ` is specified as `number_of_dice`.
```{r}
roll2 <- function(number_of_dice = 2) {
die <- 1:6
dice <- sample(die, size = number_of_dice, replace = TRUE)
sum(dice)
}
```
Testing our function, first relying on the default of 2:
```{r}
roll2()
```
Then specifying 3 dice:
```{r}
roll2(number_of_dice = 3)
```
</details>
:::
## Readings and reference
["Functions" in _R for Data Science_, 2nd ed.](https://r4ds.hadley.nz/functions) by Hadley Wickham, Mine Çetinkaya-Rundel, and Garrett Grolemund
["Writing your own Functions" in _Hands-on Programming with R_](https://rstudio-education.github.io/hopr/basics.html#write-functions), by Garrett Grolemund
["Functions", from _Introduction to the R Language_](https://www.stat.berkeley.edu/~statcur/Workshop2/Presentations/functions.pdf), Berkley, Biostatistics 140.776
["Creating Functions", from _Programming with R_](https://swcarpentry.github.io/r-novice-inflammation/02-func-R/), Software Carpentry
["Writing simple functions"](http://environmentalcomputing.net/writing-simple-functions/) at Environmental Computing
["Mathematics in R Markdown"](https://rpruim.github.io/s341/S19/from-class/MathinRmd.html) by R Pruim (2016-10-19)
-30-