## ETC5523: Communicating with Data

### Clearly communicating with code

Lecturer: Emi Tanaka

Department of Econometrics and Business Statistics

• emi.tanaka@monash.edu

• Week 8

Aim

• Understand and formulate code for communication
• Understand R package structure
• Build R package with R code, data, and launching shiny apps

Why

• Sharing code makes your analysis transparent and reproducible to others.
• Writing code that is readable by others make author intent explainable
• An R package makes functions, data or apps accessible, thereby increasing impact of your work

Thanks to Stuart Lee for developing the initial content in this slide, which has been subsequently modified a fair amount by me.

## Example data

Plant growth

An experiment to compare yields under control and two different treatment conditions on plants

str(PlantGrowth)
'data.frame':   30 obs. of  2 variables:
$weight: num 4.17 5.58 5.18 6.11 4.5 4.61 5.17 4.53 5.33 5.14 ...$ group : Factor w/ 3 levels "ctrl","trt1",..: 1 1 1 1 1 1 1 1 1 1 ...

Tooth growth

An experiment to study the effect of vitamin C on tooth growth in guinea pigs

str(ToothGrowth)
'data.frame':   60 obs. of  3 variables:
$len : num 4.2 11.5 7.3 5.8 6.4 10 11.2 11.2 5.2 7 ...$ supp: Factor w/ 2 levels "OJ","VC": 2 2 2 2 2 2 2 2 2 2 ...
$dose: num 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ... ## Which one do you prefer? Code #1 with(PlantGrowth, tapply(weight, group, mean))  ctrl trt1 trt2 5.032 4.661 5.526  Code #2 library(tidyverse) PlantGrowth %>% group_by(group) %>% summarise(weight_avg = mean(weight)) # A tibble: 3 × 2 group weight_avg <fct> <dbl> 1 ctrl 5.03 2 trt1 4.66 3 trt2 5.53 ## What do you expect the output is? Code #1 with(ToothGrowth, tapply(len, list(supp, dose), mean))  0.5 1 2 OJ 13.23 22.70 26.06 VC 7.98 16.77 26.14 Code #2 library(tidyverse) ToothGrowth %>% group_by(supp, dose) %>% summarise(length_avg = mean(len)) # A tibble: 6 × 3 # Groups: supp [2] supp dose length_avg <fct> <dbl> <dbl> 1 OJ 0.5 13.2 2 OJ 1 22.7 3 OJ 2 26.1 4 VC 0.5 7.98 5 VC 1 16.8 6 VC 2 26.1  # Naming matters ## Syntactic sugar Syntactic sugar means using function name or syntax in a programming language that is designed to make things easier to read or to express for humans. my_function(x) What do you think this function is doing? compute_average(x) A human reads your code, so write your function in a way that reads and works well for humans ## Naming cases camelCase • Capitalise all words after first word. • Common in R Shiny and JavaScript. snake_case • All words are lower case and separated by an underscore. • Preferred by R programmers in general (except Shiny). PascalCase • Capitalise all words. • Preferred by C programmers. kebab-case • All words are lower case and separated by a dash. • Common in HTML attribute names and CSS property names. Stick with the style convention of the language as much as possible! ## Syntactically valid names in R • In R, a syntactically valid name consists of letters, numbers and the dot or underline characters and starts with a letter or the dot not followed by a number. • It also cannot be a reserved word, e.g. if, else, TRUE, FALSE, while, function. For full list see ?Reserved. • Anything else is a non-syntactic name in R and you can still use any name in R by surrounding it with backticks: 4 <- 3 • You can use make.names() to make syntatically valid names in R. ## Object names • Variable and function names should use only snake case. • Strive for names that are concise and meaningful. Good day_one day_1 Bad DayOne dayone first_day_of_the_month djm1 ## Function names • Function names should be verbs (with exceptions). • Avoid using . in the names… • …unless writing a function method for S3 object system. Good add_row() permute() add_column() Bad row_adder() permutation() add.column() ## Variable names • Variable names should be nouns. • Consider using a list or data.frame to group variables in a similar context instead of assigning it as separate objects. Good origin fit where • fit[[1]] = fit1, • fit[[2]] = fit2, • and so on. Bad originate fit1 fit2 fit3 fit4 fit5 # Readable code Consistency is key! ## Consistency spacing • Always put a space after a comma, never before. Good x[, 1] Bad x[,1] x[ ,1] x[ , 1] • Do not put spaces inside or outside parentheses for regular function calls. Good mean(x, na.rm = TRUE) Bad mean (x, na.rm = TRUE) mean( x, na.rm = TRUE ) ## Consistency spacing • Place a space before and after () when used with if, for, or while. Good if (debug) { show(x) } Bad if(debug){ show(x) } • Place a space after () (but not before) used for function arguments: Good function(x) {} Bad function (x) {} function(x){} ## Consistency spacing • Most infix operators (+, -, <-, etc.) should be surrounded by spaces… Good height <- (feet * 12) + inches mean(x, na.rm = TRUE) Bad height<-feet*12+inches mean(x, na.rm=TRUE) • …with exceptions of operators with high precedence (::, :::, $, @, [, [[, ^, unary -, unary +, and :).

Good

sqrt(x^2 + y^2)
df$z x <- 1:10 Bad sqrt(x ^ 2 + y ^ 2) df$ z
x <- 1 : 10

## Consistency spacing

• … with exceptions of single-sided formulas when the right-hand side is a single identifier.

Good

~foo
tribble(
~col1, ~col2,
"a",   "b"
)

~ foo
tribble(
~ col1, ~ col2,
"a", "b"
)
• Single-sided formulas with a complex right-hand side do need a space.

Good

~ x + y

~x + y

## Avoid long lines

• Limit your code to 80 characters per line.
• If the arguments to a function don’t all fit on one line, put each argument on its own line and indent.

Good

do_something_very_complicated(
something = "that",
requires = many,
arguments = "some of which may be long"
)

do_something_very_complicated("that", requires, many, arguments, "some of which may be long")

## Sequence of functions

• Avoid deeply nesting functions in one line.
• %>% should always have a space before it, and should usually be followed by a new line.
• After the first step, each line should be indented by two spaces.

Good

shopping_list %>%
prepare() %>%
cook()

cook(prepare(buy(shopping_list)))

prepare() %>% cook()

## R packages for styling code

• styler allows you to interactively restyle selected text, files, or entire projects.
• styler includes an RStudio add-in, the easiest way to re-style existing code.
• lintr performs automated checks to confirm that you conform to the style guide.

# What exactly are R packages?

## R packages can be many things…

A container:

• for a set of R functions,
• to share data,
• to share an app,
• and more, e.g. Rmd templates (out of scope for this unit).

## The anatomy of an R package

• DESCRIPTION file
• R/ directory for R files that contain your functions
• NAMESPACE file (manual creation is out of scope for this unit)

Optionally,

• data/: for binary data available to the user
• data-raw/: for raw data
• inst/: for arbitrary additional files that you want include in your package.
• and others.

## The DESCRIPTION file

• Package name
• Title and description of what the package does
• Authors
• Dependencies (depends, imports and suggests)
• Licencing
• Version number
• Where to report bugs and so on

## Example: dplyrDESCRIPTION file

Type: Package
Package: dplyr
Title: A Grammar of Data Manipulation
Version: 1.0.99.9000
Authors@R:
family = "Wickham",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0003-4757-117X")),
person(given = "Romain",
family = "François",
role = "aut",
comment = c(ORCID = "0000-0002-2444-4226")),
person(given = "Lionel",
family = "Henry",
role = "aut"),
person(given = "Kirill",
family = "Müller",
role = "aut",
comment = c(ORCID = "0000-0002-1416-3412")),
person(given = "RStudio",
role = c("cph", "fnd")))
Description: A fast, consistent tool for working with data frame
like objects, both in memory and out of memory.
URL: https://dplyr.tidyverse.org,
https://github.com/tidyverse/dplyr
BugReports: https://github.com/tidyverse/dplyr/issues
Depends:
R (>= 3.4.0)
Imports:
generics,
glue (>= 1.3.2),
lifecycle (>= 1.0.1.9001),
magrittr (>= 1.5),
methods,
R6,
rlang (>= 1.0.5),
tibble (>= 2.1.3),
tidyselect (>= 1.1.2.9000),
utils,
vctrs (>= 0.4.1.9000),
pillar (>= 1.5.1)
Suggests:
bench,
broom,
callr,
covr,
DBI,
dbplyr (>= 2.2.1),
ggplot2,
knitr,
Lahman,
lobstr,
microbenchmark,
nycflights13,
purrr,
rmarkdown,
RMySQL,
RPostgreSQL,
RSQLite,
stringi (>= 1.7.6),
testthat (>= 3.1.1),
tidyr,
withr
Remotes:
r-lib/tidyselect,
r-lib/vctrs,
r-lib/lifecycle
VignetteBuilder:
knitr
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1
Config/testthat/edition: 3
Config/Needs/website:
tidyverse,
shiny,
pkgdown,
tidyverse/tidytemplate

## The R/ directory

• The functions you create are stored as R scripts that live in the R/ directory.
• Functions can be internal to the package or exported so other users have access to them.
• See for example the dplyr R directory.

## The NAMESPACE file

• The file contains a directive that describes whether an R object is exported from this package or imported from others.
• These directives can be automatically created by roxygen2 (covered next lecture).
# Generated by roxygen2: do not edit by hand

S3method("\$<-",grouped_df)
S3method("[",fun_list)
S3method("[",grouped_df)
S3method("[",rowwise_df)
S3method("[<-",grouped_df)
S3method("[<-",rowwise_df)
S3method("[[<-",grouped_df)
S3method("names<-",grouped_df)
S3method("names<-",rowwise_df)
S3method(anti_join,data.frame)
S3method(arrange,data.frame)
S3method(arrange_,data.frame)
S3method(arrange_,tbl_df)
S3method(as.data.frame,grouped_df)
S3method(as.tbl,data.frame)
S3method(as.tbl,tbl)
S3method(as_join_by,character)
S3method(as_join_by,default)
S3method(as_join_by,dplyr_join_by)
S3method(as_join_by,list)
S3method(as_tibble,grouped_df)
S3method(as_tibble,rowwise_df)
S3method(auto_copy,data.frame)
S3method(cbind,grouped_df)
S3method(collapse,data.frame)
S3method(collect,data.frame)
S3method(common_by,"NULL")
S3method(common_by,character)
S3method(common_by,default)
S3method(common_by,list)
S3method(compute,data.frame)
S3method(copy_to,DBIConnection)
S3method(copy_to,src_local)
S3method(count,data.frame)
S3method(distinct,data.frame)
S3method(distinct_,data.frame)
S3method(distinct_,grouped_df)
S3method(distinct_,tbl_df)
S3method(do,"NULL")
S3method(do,data.frame)
S3method(do,grouped_df)
S3method(do,rowwise_df)
S3method(do_,"NULL")
S3method(do_,data.frame)
S3method(do_,grouped_df)
S3method(do_,rowwise_df)
S3method(dplyr_col_modify,data.frame)
S3method(dplyr_col_modify,grouped_df)
S3method(dplyr_col_modify,rowwise_df)
S3method(dplyr_reconstruct,data.frame)
S3method(dplyr_reconstruct,grouped_df)
S3method(dplyr_reconstruct,rowwise_df)
S3method(dplyr_row_slice,data.frame)
S3method(dplyr_row_slice,grouped_df)
S3method(dplyr_row_slice,rowwise_df)
S3method(filter,data.frame)
S3method(filter,ts)
S3method(filter_,data.frame)
S3method(filter_,tbl_df)
S3method(filter_bullets,"dplyr:::filter_incompatible_size")
S3method(filter_bullets,"dplyr:::filter_incompatible_type")
S3method(filter_bullets,default)
S3method(format,src_local)
S3method(full_join,data.frame)
S3method(group_by,data.frame)
S3method(group_by_,data.frame)
S3method(group_by_,rowwise_df)
S3method(group_by_drop_default,default)
S3method(group_by_drop_default,grouped_df)
S3method(group_data,data.frame)
S3method(group_data,grouped_df)
S3method(group_data,rowwise_df)
S3method(group_data,tbl_df)
S3method(group_indices,data.frame)
S3method(group_indices_,data.frame)
S3method(group_indices_,grouped_df)
S3method(group_indices_,rowwise_df)
S3method(group_keys,data.frame)
S3method(group_map,data.frame)
S3method(group_modify,data.frame)
S3method(group_modify,grouped_df)
S3method(group_nest,data.frame)
S3method(group_nest,grouped_df)
S3method(group_size,data.frame)
S3method(group_split,data.frame)
S3method(group_split,grouped_df)
S3method(group_split,rowwise_df)
S3method(group_trim,data.frame)
S3method(group_trim,grouped_df)
S3method(group_vars,data.frame)
S3method(groups,data.frame)
S3method(inner_join,data.frame)
S3method(intersect,data.frame)
S3method(left_join,data.frame)
S3method(mutate,data.frame)
S3method(mutate_,data.frame)
S3method(mutate_,tbl_df)
S3method(mutate_bullets,"dplyr:::error_incompatible_combine")
S3method(mutate_bullets,"dplyr:::mutate_constant_recycle_error")
S3method(mutate_bullets,"dplyr:::mutate_incompatible_size")
S3method(mutate_bullets,"dplyr:::mutate_mixed_null")
S3method(mutate_bullets,"dplyr:::mutate_not_vector")
S3method(mutate_bullets,default)
S3method(n_groups,data.frame)
S3method(nest_by,data.frame)
S3method(nest_by,grouped_df)
S3method(nest_join,data.frame)
S3method(print,all_vars)
S3method(print,any_vars)
S3method(print,dplyr_join_by)
S3method(print,dplyr_sel_vars)
S3method(print,fun_list)
S3method(print,src)
S3method(pull,data.frame)
S3method(rbind,grouped_df)
S3method(recode,character)
S3method(recode,factor)
S3method(recode,numeric)
S3method(relocate,data.frame)
S3method(rename,data.frame)
S3method(rename_,data.frame)
S3method(rename_,grouped_df)
S3method(rename_with,data.frame)
S3method(right_join,data.frame)
S3method(rows_append,data.frame)
S3method(rows_delete,data.frame)
S3method(rows_insert,data.frame)
S3method(rows_patch,data.frame)
S3method(rows_update,data.frame)
S3method(rows_upsert,data.frame)
S3method(rowwise,data.frame)
S3method(rowwise,grouped_df)
S3method(same_src,data.frame)
S3method(sample_frac,data.frame)
S3method(sample_frac,default)
S3method(sample_n,data.frame)
S3method(sample_n,default)
S3method(select,data.frame)
S3method(select,list)
S3method(select_,data.frame)
S3method(select_,grouped_df)
S3method(semi_join,data.frame)
S3method(setdiff,data.frame)
S3method(setequal,data.frame)
S3method(slice,data.frame)
S3method(slice_,data.frame)
S3method(slice_,tbl_df)
S3method(slice_max,data.frame)
S3method(slice_min,data.frame)
S3method(slice_sample,data.frame)
S3method(slice_tail,data.frame)
S3method(src_tbls,src_local)
S3method(summarise,data.frame)
S3method(summarise,grouped_df)
S3method(summarise,rowwise_df)
S3method(summarise_,data.frame)
S3method(summarise_,tbl_df)
S3method(summarise_bullets,"dplyr:::error_incompatible_combine")
S3method(summarise_bullets,"dplyr:::summarise_incompatible_size")
S3method(summarise_bullets,"dplyr:::summarise_mixed_null")
S3method(summarise_bullets,"dplyr:::summarise_unsupported_type")
S3method(summarise_bullets,default)
S3method(symdiff,data.frame)
S3method(symdiff,default)
S3method(tally,data.frame)
S3method(tbl,DBIConnection)
S3method(tbl,src_local)
S3method(tbl_ptype,default)
S3method(tbl_sum,grouped_df)
S3method(tbl_sum,rowwise_df)
S3method(tbl_vars,data.frame)
S3method(transmute,data.frame)
S3method(transmute_,data.frame)
S3method(ungroup,data.frame)
S3method(ungroup,grouped_df)
S3method(ungroup,rowwise_df)
S3method(union,data.frame)
S3method(union_all,data.frame)
S3method(union_all,default)
export("%>%")
export(.data)
export(across)
export(all_equal)
export(all_of)
export(all_vars)
export(anti_join)
export(any_of)
export(any_vars)
export(arrange)
export(arrange_)
export(arrange_all)
export(arrange_at)
export(arrange_if)
export(as.tbl)
export(as_data_frame)
export(as_label)
export(as_tibble)
export(auto_copy)
export(bench_tbls)
export(between)
export(bind_cols)
export(bind_rows)
export(c_across)
export(case_match)
export(case_when)
export(changes)
export(check_dbplyr)
export(coalesce)
export(collapse)
export(collect)
export(combine)
export(common_by)
export(compare_tbls)
export(compare_tbls2)
export(compute)
export(consecutive_id)
export(contains)
export(copy_to)
export(count)
export(count_)
export(cumall)
export(cumany)
export(cume_dist)
export(cummean)
export(cur_column)
export(cur_data)
export(cur_data_all)
export(cur_group)
export(cur_group_id)
export(cur_group_rows)
export(current_vars)
export(data_frame)
export(db_analyze)
export(db_begin)
export(db_commit)
export(db_create_index)
export(db_create_indexes)
export(db_create_table)
export(db_data_type)
export(db_desc)
export(db_drop_table)
export(db_explain)
export(db_has_table)
export(db_insert_into)
export(db_list_tables)
export(db_query_fields)
export(db_query_rows)
export(db_rollback)
export(db_save_query)
export(db_write_table)
export(dense_rank)
export(desc)
export(dim_desc)
export(distinct)
export(distinct_)
export(distinct_all)
export(distinct_at)
export(distinct_if)
export(distinct_prepare)
export(do)
export(do_)
export(dplyr_col_modify)
export(dplyr_reconstruct)
export(dplyr_row_slice)
export(ends_with)
export(enexpr)
export(enexprs)
export(enquo)
export(enquos)
export(ensym)
export(ensyms)
export(eval_tbls)
export(eval_tbls2)
export(everything)
export(explain)
export(expr)
export(failwith)
export(filter)
export(filter_)
export(filter_all)
export(filter_at)
export(filter_if)
export(first)
export(full_join)
export(funs)
export(funs_)
export(glimpse)
export(group_by)
export(group_by_)
export(group_by_all)
export(group_by_at)
export(group_by_drop_default)
export(group_by_if)
export(group_by_prepare)
export(group_cols)
export(group_data)
export(group_indices)
export(group_indices_)
export(group_keys)
export(group_map)
export(group_modify)
export(group_nest)
export(group_rows)
export(group_size)
export(group_split)
export(group_trim)
export(group_vars)
export(group_walk)
export(grouped_df)
export(groups)
export(id)
export(ident)
export(if_all)
export(if_any)
export(if_else)
export(inner_join)
export(intersect)
export(is.grouped_df)
export(is.src)
export(is.tbl)
export(is_grouped_df)
export(join_by)
export(lag)
export(last)
export(last_col)
export(left_join)
export(location)
export(lst)
export(make_tbl)
export(matches)
export(min_rank)
export(mutate)
export(mutate_)
export(mutate_all)
export(mutate_at)
export(mutate_each)
export(mutate_each_)
export(mutate_if)
export(n)
export(n_distinct)
export(n_groups)
export(na_if)
export(near)
export(nest_by)
export(nest_join)
export(new_grouped_df)
export(new_rowwise_df)
export(nth)
export(ntile)
export(num_range)
export(one_of)
export(order_by)
export(percent_rank)
export(progress_estimated)
export(pull)
export(quo)
export(quo_name)
export(quos)
export(recode)
export(recode_factor)
export(relocate)
export(rename)
export(rename_)
export(rename_all)
export(rename_at)
export(rename_if)
export(rename_vars)
export(rename_vars_)
export(rename_with)
export(right_join)
export(row_number)
export(rows_append)
export(rows_delete)
export(rows_insert)
export(rows_patch)
export(rows_update)
export(rows_upsert)
export(rowwise)
export(same_src)
export(sample_frac)
export(sample_n)
export(select)
export(select_)
export(select_all)
export(select_at)
export(select_if)
export(select_var)
export(select_vars)
export(select_vars_)
export(semi_join)
export(setdiff)
export(setequal)
export(show_query)
export(slice)
export(slice_)
export(slice_max)
export(slice_min)
export(slice_sample)
export(slice_tail)
export(sql)
export(sql_escape_ident)
export(sql_escape_string)
export(sql_join)
export(sql_select)
export(sql_semi_join)
export(sql_set_op)
export(sql_subquery)
export(sql_translate_env)
export(src)
export(src_df)
export(src_local)
export(src_mysql)
export(src_postgres)
export(src_sqlite)
export(src_tbls)
export(starts_with)
export(summarise)
export(summarise_)
export(summarise_all)
export(summarise_at)
export(summarise_each)
export(summarise_each_)
export(summarise_if)
export(summarize)
export(summarize_)
export(summarize_all)
export(summarize_at)
export(summarize_each)
export(summarize_each_)
export(summarize_if)
export(sym)
export(symdiff)
export(syms)
export(tally)
export(tally_)
export(tbl)
export(tbl_df)
export(tbl_nongroup_vars)
export(tbl_ptype)
export(tbl_vars)
export(tibble)
export(top_frac)
export(top_n)
export(transmute)
export(transmute_)
export(transmute_all)
export(transmute_at)
export(transmute_if)
export(tribble)
export(type_sum)
export(ungroup)
export(union)
export(union_all)
export(validate_grouped_df)
export(validate_rowwise_df)
export(vars)
export(with_groups)
export(with_order)
export(wrap_dbplyr_obj)
import(rlang)
import(vctrs, except = data_frame)
importFrom(R6,R6Class)
importFrom(generics,intersect)
importFrom(generics,setdiff)
importFrom(generics,setequal)
importFrom(generics,union)
importFrom(glue,glue)
importFrom(glue,glue_collapse)
importFrom(glue,glue_data)
importFrom(lifecycle,deprecated)
importFrom(magrittr,"%>%")
importFrom(methods,is)
importFrom(pillar,glimpse)
importFrom(pillar,tbl_sum)
importFrom(pillar,type_sum)
importFrom(stats,setNames)
importFrom(stats,update)
importFrom(tibble,as_data_frame)
importFrom(tibble,as_tibble)
importFrom(tibble,data_frame)
importFrom(tibble,is_tibble)
importFrom(tibble,lst)
importFrom(tibble,new_tibble)
importFrom(tibble,tibble)
importFrom(tibble,tribble)
importFrom(tibble,view)
importFrom(tidyselect,all_of)
importFrom(tidyselect,any_of)
importFrom(tidyselect,contains)
importFrom(tidyselect,ends_with)
importFrom(tidyselect,everything)
importFrom(tidyselect,last_col)
importFrom(tidyselect,matches)
importFrom(tidyselect,num_range)
importFrom(tidyselect,one_of)
importFrom(tidyselect,starts_with)
importFrom(utils,tail)
useDynLib(dplyr, .registration = TRUE)

## Creating an R package

• usethis::create_package("mypkg") for creating a skeleton R package
• devtools::load_all() for loading the functions in the R/ directory to the current environment

## Adding functions to an R package

• usethis::use_r("new-r-file") for creating a new R file in the R/ directory
- mypackage
|- R
|- new-r-file.R
|- ...
|- DESCRIPTION
|- ...

## Distribute data via an R package

• usethis::use_data_raw("filename") for adding a file to data-raw/ directory to include code to reproduce data.
• usethis::use_data(mydata) for creating a binary data file in data/ directory.
• More information on this at R Packages (2e) Chapter 8 Data.

## Launcing shiny app via an R package

- mypackage
|- inst
|- myapp
|- app.R
|- R
|- run-app.R
|- ...
|- DESCRIPTION
|- ...

run-app.R

#' @export
run_app <- function() {
app_dir <- system.file("myapp", package = "mypackage")
shiny::runApp(app_dir, display.mode = "normal")
}

Note: for this to work, you need to first install the package!

## Installing the package

• First run devtools::document()
(we will cover this more next lecture).
• Then run devtools::install().
• Now you can call library(mypackage) to use exported functions or data in your package!

## Master the keyboard shortcuts

• Cmd/Ctrl + Shift + L: Load all
• Cmd/Ctrl + Shift + D: Document
• Cmd/Ctrl + Shift + B: Build and Reload
• plus more… see RStudio IDE > Tools > Keyboard Shortcuts Help

## Week 8 Lesson

Summary

• How to enhance communication in code by adopting a consistent styling.
• R packages are flexible containers to share R code, data, and app amongst other things.
• An R package can be set up, documented, and tried out with usethis and devtools.