Documenting and storing your functions
March 12, 2025
We can declare new functions in R with the following structure:
We saw last class
purrr::map()
furrr::future_map()
At the end of class, we briefly saw how to source()
a file of functions.
We can always split one R file into multiple files.
This is a convenient way to structure your code and keep it organized.
We can then use source()
to load the code from the other files.
Here is a possible folder structure for a project:
Project
|-- Readme.md
|-- main.R
|-- renv.lock
|-- code
| |-- clean_data.R
| |-- analysis.R
| `-- helpers.R
|-- raw_data
`-- output
Our “main.R” script could then look something like this:
Project
|-- Readme.md
|-- main.R
|-- renv.lock
|-- code
| |-- clean_data.R
| |-- analysis.R
| `-- helpers.R
|-- raw_data
`-- output
This is a good way to keep your code organized and easy to read.
Our “helpers.R” file could look something like this:
helpers.R
## This is a file with all of our custom helper functions
## A function that does x and y
myfunc1 <- function(a, b) {
result <- a + b * a
return(result)
}
## Another function that does z
myfunc2 <- function(a, b) {
result <- a * b
return(result)
}
## A third function that does w
myfunc3 <- function(a, b) {
result <- a / b
return(result)
}
If you have a lot of custom functions, the helpers.R
file can get really long.
This makes finding and editing functions harder.
And it makes your code less readable.
A possible solution:
Now we can split each of our helper functions into their own files.
Project
|-- Readme.md
|-- main.R
|-- renv.lock
|-- code
| |-- clean_data.R
| |-- analysis.R
| `-- helpers
| |-- myfunc1.R
| |-- myfunc2.R
| `-- myfunc3.R
|-- raw_data
`-- output
But how do we load all of these scripts into “main.R”?
This is easy with list.files()
and lapply()
.
Project
|-- Readme.md
|-- main.R
|-- renv.lock
|-- code
| |-- clean_data.R
| |-- analysis.R
| `-- helpers
| |-- myfunc1.R
| |-- myfunc2.R
| `-- myfunc3.R
|-- raw_data
`-- output
list.files()
returns a list of all files at a location. It has options for filtering for specific file types and recursively listing files in subfolders.
source()
workslist.files()
worksWe have used many different R packages throughout the course.
Packages provide
We have seen how you can install packages remotely from CRAN using renv::install()
or install.packages()
.
You can also install packages that are stored either
This can be great for installing packages in development, or for packages you write.
Why would you want to write a package?
If you want to write a package, read R Packages (2e) by Hadley Wickham and Jennifer Bryan.
It starts with a simple example package.
Then goes into detail about every element of an R package.
I will try to give you the highlights today, but we won’t cover everything.
We will be developing the package in a different folder/project than the project where we install/load the package.
devtools
First, we will install a package devtools
.
This package has a bunch of useful “tools” for “developing” packages.
It will also install a package, usethis
, which helps create some templates for us.
First, navigate to where you would like your package to live.
"."
is a filepath that means “here”, which is your current working directory (folder).
The create_package()
function will create some folders and files needed to structure a package.
.Rbuildignore
lists files to ignore when building the package.gitignore
DESCRIPTION
file for metadata about your packageNAMESPACE
file listing dependencies and functions exportedR/
a folder where we will put all of our functionspackageName.Rproj
for Rstudio projects, we won’t use itTo add a function to our package, we simply declare it in the R/
folder in a new R script.
In general, you should have one .R
file for each function, or at least for each family of very similar functions.
As you are writing your functions, you will want to load and test your package.
You can do that by loading the package with devtools::load_all()
in the R terminal.
Then you can run and test your functions.
Later, we will look at installing and loading your package into a separate project, but this load_all()
is for testing during development.
roxygen2
roxygen2
takes the comments we write just before our function and translates them into documentation files for us.
This is a lot easier than writing the documentation files ourselves.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
Here is an example of roxygen2
style documentation.
We will look at each part separately.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
This is the title of the function. It should be one line and short.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
This is the description of the function. It can be one line, or many.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
Documention for each parameter. Can be one line or many.
Often used to note expected input types (data.frame, numeric, etc.).
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
Description of the value returned.
Usually notes the data type of the object.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
This line tells roxygen2
to export the function to the package’s namespace.
If not included, this function will not be available when the package is installed and loaded.
Some functions are not exported because they are only used internally.
crra.R
#' CRRA Utlity
#'
#' Function to calculate constant
#' relative risk aversion utility.
#'
#' @param c consumption
#' @param gamma relative risk aversion.
#' @param beta discount factor.
#' Default is 0.99.
#'
#' @return A numerical vector
#' @export
#'
#' @examples
#' crra(1:10, 0.5)
crra <- function(c, gamma, beta = 0.99) {
utility <-
beta * c ^ (1 - gamma) / (1 - gamma)
return(utility)
}
Example use of the code. Make sure it works when run!
Can have multiple examples.
Especially if you are publishing your package for others, you will want to add some metadata for your package.
DESCRIPTION
fileYou can add things like,
To add a license, call devtools::use_mit_license()
(or a similar license function).
If you want to use another package’s functions in your package, first call usethis::use_package("thatPackage")
in the terminal.
thatPackage
to the list of “Imports” in the NAMESPACE
file.Then you can use that package’s functions in your script:
check()
CRAN has a lot of standards that packages have to pass.
You can (and you should) check to see if your package passes by calling devtools::check()
in the terminal.
It is good to check your package early and often.
Even if you do not plan to submit to CRAN, they are good standards to follow.
Once you start writing packages, you should be writing “unit tests” for each function.
Unit Tests
We will have a whole lecture later about writing unit tests and how they can be helpful for all programming projects.
We will also have a lecture on “GitHub Actions” as a form of “Continuous Integration”.
GitHub Actions can…
check()
whenever you push to GitHubWe will be developing the package in a different folder/project than the project where we install/load the package.
We can install our package using renv
.
We just loaded our own package and our function crra()
!
We can now use it in all of our scripts.
You can easily share your custom package with GitHub.
Then, you (and others) can install your package using
If you want your package to be available on CRAN, follow this guide (it takes more work and maintanence).
If you write a package, first read R Packages (2e).
Packages are great for
crra()
functiondocument()
and check()
helpers.R
source()
the function and use it in main.R
helpers/
folder, write two separate function scripts
list.files()
and lapply()
to source()
all of the functions