sd
#> function (x, na.rm = FALSE)
#> sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x),
#> na.rm = na.rm))
#> <bytecode: 0x55e1e01e6510>
#> <environment: namespace:stats>
If you find any typos, errors, or places where the text may be improved, please let us know by providing feedback either in the feedback survey (given during class) or by using GitHub.
On GitHub open an issue or submit a pull request by clicking the " Edit this page" link at the side of this page.
Here we will cover the second block, “Workflow” in Figure 7.1.
source()
with or with the Palette (, then type “source”) and restarting R with or with the Palette (, then type “restart”) as a tool and process for developing functions that can be later easily (re-)used.Let’s write a simple example. First, create a new Markdown header called ## Making a function
and create a code chunk below that with or with the Palette (, then type “new chunk”) . Then, inside the function, we’ll write this code out:
add_numbers <- function(num1, num2) {
added <- num1 + num2
return(added)
}
You can use the new function by running the above code and writing out your new function, with arguments to give it.
add_numbers(1, 2)
#> [1] 3
The function name is fairly good… add_numbers
is read as “add numbers”. While we generally want to write code that describes what it does by reading it, it’s also good practice to add some formal documentation to the function. Use the “Insert Roxygen Skeleton” in the “Code” menu, by typing or with the Palette (, then type “roxygen comment”), and you can add template documentation right above the function. It looks like:
#' Title
#'
#' @param num1
#' @param num2
#'
#' @return
#' @export
#'
#' @examples
add_numbers <- function(num1, num2) {
added <- num1 + num2
return(added)
}
In the Title
area, this is where you type out a brief sentence or several words that describe the function. Creating a new paragraph below this line allows you to add a more detailed description. The other items are:
@param num
: These lines describe what each argument (also called parameter) is for and what to give it.@return
: This describes what output the function gives. Is it a data.frame? A plot? What else does the output give?@export
: Tells R that this function should be accessible to the user of your package. Since we aren’t making packages, delete it.@examples
: Any lines below this are used to show examples of how to use the function. This is very useful when making packages, but not really in this case. So we’ll delete it. Let’s write out some documentation for this function:#' Add two numbers together.
#'
#' @param num1 A number here.
#' @param num2 A number here.
#'
#' @return Returns the sum of the two numbers.
#'
add_numbers <- function(num1, num2) {
added <- num1 + num2
return(added)
}
Once we’ve created that, let’s open up the Git Interface with or with the Palette (, then type “commit”) and add and commit these changes to our history.
Now that we have a basic understanding of what a function looks like, let’s apply it to something we’re doing right now: Importing our data.
Making functions is a series of steps:
name <- function() { ... }
, with an appropriate and descriptive name.function(argument1, argument2)
) with appropriate and descriptive names, then replace the code with the argument names where appropriate.return()
function at the end to indicate what the function will output.So, step one. Let’s take the code we wrote for importing the user_info
data and convert that as a function:
user_1_info_data <- vroom(
user_1_info_file,
col_select = -1,
col_types = cols(
gender = col_character(),
weight = col_double(),
height = col_double(),
age = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
Next we wrap it in the function call and give it an appropriate name. In this case, import_user_info
is descriptive and meaningful.
import_user_info <- function() {
user_1_info_data <- vroom(
user_1_info_file,
col_select = -1,
col_types = cols(
gender = col_character(),
weight = col_double(),
height = col_double(),
age = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
}
Then, we add arguments in the function and replace within the code. Here, we have only one thing that we would change: The file path to the dataset. So, a good name might be file_path
.
import_user_info <- function(file_path) {
user_1_info_data <- vroom(
file_path,
col_select = -1,
col_types = cols(
gender = col_character(),
weight = col_double(),
height = col_double(),
age = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
}
Then we clean things up by renaming user_1_info_data
since we would like to also import more than just user_1
. A nice object name would be info_data
. Add the return()
function at the end with the object you want your function to output.
import_user_info <- function(file_path) {
info_data <- vroom(
file_path,
col_select = -1,
col_types = cols(
gender = col_character(),
weight = col_double(),
height = col_double(),
age = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(info_data)
}
Great! Now we need to test it out. Let’s try on two datasets, two user_info.csv
files in the user_1
and user_2
folders.
import_user_info(here("data-raw/mmash/user_1/user_info.csv"))
#> # A tibble: 1 × 4
#> gender weight height age
#> <chr> <dbl> <dbl> <dbl>
#> 1 M 65 169 29
import_user_info(here("data-raw/mmash/user_2/user_info.csv"))
#> # A tibble: 1 × 4
#> gender weight height age
#> <chr> <dbl> <dbl> <dbl>
#> 1 M 95 183 27
Awesome! It works. The final stage is adding the Roxygen documentation.
#' Import MMASH user info data file.
#'
#' @param file_path Path to user info data file.
#'
#' @return Outputs a data frame/tibble.
#'
import_user_info <- function(file_path) {
info_data <- vroom(
file_path,
col_select = -1,
col_types = cols(
gender = col_character(),
weight = col_double(),
height = col_double(),
age = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(info_data)
}
A massive advantage of using functions is that if you want to make a change to all your code, you can very easily do it by modifying the function and it will change all your other code too. Now that we have a working function, run styler with with the Palette (, then type “style file”) and then let’s add and commit it to the Git history with the RStudio Git Interface by using or with the Palette (, then type “commit”).
Time: 15 minutes.
Take the code you created for importing the saliva data set from Section 5.3 (not the code related to using spec()
) and make it into a function. It looks like the code below.
user_1_saliva_data <- vroom(
user_1_saliva_file,
col_select = -1,
col_types = cols(
samples = col_character(),
cortisol_norm = col_double(),
melatonin_norm = col_double()
),
.name_repair = snakecase::to_snake_case
)
A helpful tip: To move around a Quarto / R Markdown or R script more easily, open up the “Document Outline” on the side by clicking the button in the top right corner of the Quarto / R Markdown pane or by using or with the Palette (, then type “outline”).
## Exercise for importing the saliva data as a function
.Wrap it with the function() {...}
Make a meaningful name (use import_saliva
)
Make an argument for the file path (file_path
) and replace user_1_saliva_file
with file_path
in the vroom()
code
Rename the output object to saliva_data
and put it in the return()
function
Run the function and then test that it works
Create the Roxygen documentation by adding it with
or with the Palette (, then type “roxygen comment”)
doc/learning.qmd
file with with the Palette (, then type “style file”).Use the below code as a guide:
# Need to also add the Roxygen documentation
<- function(file_path) {
import_saliva # Paste the code to import saliva data you created
# from previous exercise
<- ___(
saliva_data
___
)return(saliva_data)
}
# Test that the function works
___(here("data-raw/mmash/user_1/saliva.csv"))
#' Import the MMASH saliva dataset.
#'
#' @param file_path Path to the user saliva data file.
#'
#' @return Outputs a data frame/tibble.
#'
import_saliva <- function(file_path) {
saliva_data <- vroom(
file_path,
col_select = -1,
col_types = cols(
samples = col_character(),
cortisol_norm = col_double(),
melatonin_norm = col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(saliva_data)
}
# Test that this works
# import_saliva(here("data-raw/mmash/user_1/saliva.csv"))
We’ve created two functions. Now we need to move those functions from the doc/learning.qmd
file and into the R/
folder by cutting and pasting (not just copying). We do this for a few reasons:
source()
function.We want to store our functions in the file R/functions.R
script so its easier to source them. Cut and paste only the import_user_info()
function we created in doc/learning.qmd
, including the Roxygen documentation, and paste it into the newly created R/functions.R
.
Once we have it in there, let’s test out the workflow. Restart our R session with by either going to the “Session -> Restart R” menu item or by using the keybindings or with the Palette (, then type “restart”). Move back into the doc/learning.qmd
and add source(here("R/functions.R"))
to the code chunk called setup
at the top. Run all the code inside the setup
code chunk. Then go to where you wrote:
import_user_info(here::here("data-raw/mmash/user_1/user_info.csv"))
Now run this line. What happens now? You may get an error about not finding the vroom()
function. If you put library(vroom)
in the setup
code chunk, you might not get an error. If you did get an error, that’s because R doesn’t know what the vroom()
function is. This is where we start getting into package dependency management.
What is package dependency management? Whenever you use an R package, you depend on it for your code to work. The informal way to show what packages you use is by using the library()
function. But if you come back to the project, or get a new computer, or someone else is working on your project too, how will they know which packages your project depends on? Do they have to search through all your files just to find all library()
functions you used and then install those packages individually? A much better way here is to formally indicate your package dependency so that installing dependencies is easy. We do this by making use of the DESCRIPTION
file.
Open up the DESCRIPTION
file. You may or may not see something that looks like:
Type: Project
Package: LearnR3
Version: 0.0.1
Imports:
knitr,
rmarkdown
Encoding: UTF-8
If it doesn’t look like this, replace all of your current text with the text above. Notice the Imports:
key. This is where information about packages are added. To quickly add a package, go to the Console and type out:
usethis::use_package("vroom")
You will see a bunch of text about adding it to Imports
. If you look in your DESCRIPTION
file now, you’ll see something like:
Type: Project
Package: LearnR3
Version: 0.0.1
Imports:
knitr,
rmarkdown,
vroom
Encoding: UTF-8
Now, if you or someone else wants to install all the packages your project depends on, they can do that by going to the Console and running:
pak::pak()
This function finds the DESCRIPTION
file and installs all the packages in Imports
. Let’s add the other dependencies by typing in the Console:
usethis::use_package("here")
usethis::use_package("fs")
usethis::use_package("snakecase")
Since we will also make use of the tidyverse set of packages later in the course, we’ll also add tidyverse as a dependency. Since the tidyverse is a large collection of packages, the recommended way to add this particular dependency is with:
usethis::use_package("tidyverse", type = "Depends")
If you look in the DESCRIPTION
file now, you see that the new Depends
field has been added with tidyverse
right below it. There are fairly technical reasons why we need to put tidyverse in the Depends
field that you don’t need to know about for this course, aside from the fact that it is a common practice in R projects. At least in this context, we use the Depends
field for tidyverse because of one big reason: the usethis::use_package()
function will complain if we try to put tidyverse in the Imports
and it recommends putting it in the Depends
field.
Type: Project
Package: LearnR3
Version: 0.0.1
Depends:
tidyverse
Imports:
fs,
here,
knitr,
rmarkdown,
snakecase,
vroom
Encoding: UTF-8
Great! Now that we’ve formally established package dependencies in our project, we also need to formally declare which package each function comes from inside our own functions.
Alright, let’s go into R/functions.R
and add vroom::
to each of the vroom functions we’ve used:
import_user_info <- function(file_path) {
info_data <- vroom::vroom(
file_path,
col_select = -1,
col_types = vroom::cols(
gender = vroom::col_character(),
weight = vroom::col_double(),
height = vroom::col_double(),
age = vroom::col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(info_data)
}
Test that it works by restart the R session with or with the Palette (, then type “restart”) and source the file with or with the Palette (, then type “source”), then go to the Console and type out:
import_user_info(here::here("data-raw/mmash/user_1/user_info.csv"))
It should work as expected! Now that we’ve done that, let’s add and commit the changes made through the Git interface, using or with the Palette (, then type “commit”).
Time: ~30 minutes.
Repeat this process of making functions by doing this to the rest of the code you worked on previously that imported the RR.csv
and Actigraph.csv
data.
Convert the importing code into functions while in the doc/learning.qmd
file. Include the Roxygen documentation and use packagename::
to be explicit about where the function comes from.
import_rr
and import_actigraph
.Move (by cutting and pasting) only the function into R/functions.R
.
Restart R, source()
the functions file, using or with the Palette (, then type “source”), and test that the functions work by running them in the Console. The below code should run without a problem if you did it right:
Run styler while in the R/functions.R
file with with the Palette (, then type “style file”).
Also update the import_saliva()
function you created by being explicit about where the functions come from (e.g. with the packagename::
). Then cut and paste this function along with its Roxygen documentation over into the R/functions.R
file. Run styler with with the Palette (, then type “style file”) and afterwards, add and commit the changes to the Git history, using or with the Palette (, then type “commit”).
Use this code template as a guide for making the functions.
# Insert Roxygen documentation too
<- function(___) {
___ <- ___::___(
___
___,col_select = ___,
col_types = ___::cols(
___,.delim = ","
),.name_repair = snakecase::to_snake_case
)return(___)
}
#' Import the MMASH saliva dataset.
#'
#' @param file_path Path to the user saliva data file.
#'
#' @return Outputs a data frame/tibble.
#'
import_saliva <- function(file_path) {
saliva_data <- vroom::vroom(
file_path,
col_select = -1,
col_types = vroom::cols(
samples = vroom::col_character(),
cortisol_norm = vroom::col_double(),
melatonin_norm = vroom::col_double(),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(saliva_data)
}
#' Import the MMASH RR dataset (heart beat-to-beat interval).
#'
#' @param file_path Path to the user RR data file.
#'
#' @return Outputs a data frame/tibble.
#'
import_rr <- function(file_path) {
rr_data <- vroom::vroom(
file_path,
col_select = -1,
col_types = vroom::cols(
ibi_s = vroom::col_double(),
day = vroom::col_double(),
# Converts to seconds
time = vroom::col_time(format = ""),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(rr_data)
}
#' Import the MMASH Actigraph dataset (accelerometer).
#'
#' @param file_path Path to the user Actigraph data file.
#'
#' @return Outputs a data frame/tibble.
#'
import_actigraph <- function(file_path) {
actigraph_data <- vroom::vroom(
file_path,
col_select = -1,
col_types = vroom::cols(
axis_1 = vroom::col_double(),
axis_2 = vroom::col_double(),
axis_3 = vroom::col_double(),
steps = vroom::col_double(),
hr = vroom::col_double(),
inclinometer_off = vroom::col_double(),
inclinometer_standing = vroom::col_double(),
inclinometer_sitting = vroom::col_double(),
inclinometer_lying = vroom::col_double(),
vector_magnitude = vroom::col_double(),
day = vroom::col_double(),
time = vroom::col_time(format = ""),
.delim = ","
),
.name_repair = snakecase::to_snake_case
)
return(actigraph_data)
}
Time: 15 minutes.
You’ve learned the basics of making your own, custom function. Now, as a group, brainstorm and discuss some ways that you might make functions in your own work to help reduce repetition. What type of code might you make as a function for your own project? Do you think others, maybe in your research group, might use this function too? Afterwards, all the groups will briefly share what they thought of.
use_package()
for the DESCRIPTION
file as well as packagename::functionname()
to explicit state the packages your function depends on.