2  Kickstarting an R package

NoteAims
  • Kickstart an R package
  • Write functions
  • Document functions
Tip

At any time, if you are lost or do not understand how functions in the proposed solution work, type ?<function> in the R console and a help menu will appear.

You can also check the help tab in the corresponding quadrant.

2.1 Proposed package functionalities

In this section, we are going to create a package which aims at:

  1. Importing .bigwig coverage track files as well as .bed files of genomic coordinates
  2. Filtering imported GRanges to make sure they all are contained within the coverage tracks
  3. Calculating the average (+/- quantiles) of coverage of the .bigwig file over the GRanges of interest
  4. Plot the aggregated .bigwig coverage over the GRanges of interest

Eventually, the functions shipped in this package should be able to turn a pair of .bigwig/.bed files into an aggregated coverage profile — exactly the kind of plot you already produced by hand at the end of Day 1 (mean signal, with its confidence interval, as a function of the distance to the center of the features).

2.2 Starting a new package

To start developing a new package, one may want to make sure all useful developer resources are already set-up.

Question

Make sure to install the developer toolkit packages.

HintHint

CRAN packages are installed with remotes::install_cran(), and Bioconductor packages with BiocManager::install(). You will need, among others, devtools, usethis, available (from CRAN) and biocthis, BiocCheck (from Bioconductor).

install.packages("remotes")
remotes::install_cran(
    c(
        "available",
        "devtools",
        "rcmdcheck", 
        "knitr",
        "pkgdown",
        "RefManageR",
        "rmarkdown",
        "rstudioapi",
        "sessioninfo",
        "styler",
        "usethis",
        "gert"
    )
)
remotes::install_cran("BiocManager")
BiocManager::install(c("BiocStyle", "BiocCheck", "biocthis"))

Now the time has come to create your very first package! To keep a coherent cohorte, we’ll create a package named <YOURNAME>TestPackage. But first, you need to make sure that this package name is available.

Question

Verify that your package name is available, with the available package.

HintHint

The available package has a function named… available(). It checks a name against CRAN/Bioconductor, and also flags unintended meanings.

available::available("______")
available::available("JacquesTestPackage")

If this name is available, let’s create your package.

Question

Which function from devtools can be used to create a package skeleton? Use it to create your package.

HintHint

The function is create_package(). It takes the path of the package to create (so mind the parent directory — e.g. your home ~).

library(devtools)
create_package("______")
library(devtools)
setwd("~")
create_package("JacquesTestPackage")

When creating your package, devtools+RStudio combo automatically opens a new RStudio window, with the package’s project automatically activated. Navigate to your package RStudio project window and re-open this instruction sheet.

Question

Make sure you’re in the right project using the appropriate function from rstudioapi package.

HintHint

rstudioapi::getActiveProject() returns the path of the currently active RStudio project. If it is not the right one, usethis::proj_activate() can switch to it.

usethis::proj_activate("~/JacquesTestPackage/")
rstudioapi::getActiveProject()
Question

Explore the newly created folder architecture. Do you recognize all the files and directories?

The DESCRIPTION file automatically created does not exactly match Bioconductor requirements. You should replace it by a better-fitted DESCRIPTION file.

Question

Find the appropriate function from biocthis to replace the current DESCRIPTION file by one fitting BIOCONDUCTOR’s requirements.

HintHint

biocthis functions all start with use_bioc_*(). Look for the one that ends in _description.

biocthis::use_bioc_description()
Question

Manually edit the important fields from your new DESCRIPTION file: Title & Author. The remaining fields will be filled out later on.

Question

Now add a LICENSE file to specify under which license you wish to publish your package.

HintHint

usethis provides one function per common license, all named use_*_license() (e.g. use_mit_license(), use_gpl3_license(), …).

usethis::use_mit_license()

A nice touch is to systematically provide a README file. This provides a good overview of your package when browsed e.g. in GitHub, and for non-R specialists, who don’t necessarily know about DESCRIPTION and vignettes. And since you are developing an R package, let’s stick with the Rmd format for your README.

Question

What are the benefits of having a README.Rmd file?

Which function from biocthis can create a .Rmd README file?

HintHint

Again a biocthis::use_bioc_*() function — this time one that mentions readme and rmd. A README.Rmd lets you embed runnable R code (and its output) directly in the README.

biocthis::use_bioc_readme_rmd()

That being said, package submission requires that a REAMDE.md file only is present in the repository. A way to provide this is to 1) devtools::build_readme() to parse README.Rmd into README.md (with R chunks processed!), and 2) add README.Rmd to .gitignore.

Question

Parse your README.Rmd into README.md and check the differences.

HintHint

devtools::build_readme() knits README.Rmd into README.md. Load your package first with devtools::load_all('.') so that any package code used in the README is available.

devtools::load_all('.')
devtools::build_readme()
rstudioapi::documentOpen("README.Rmd")
rstudioapi::documentOpen("README.md")

A bunch of extra useful commands from bioc/usethis can be performed at this stage, e.g. to add a NEWS file, a Code of Conduct, a citation file, etc… Beware, each command creates/modify one/several files. Pay attention to the comments printed in the R console!

Question

Run each of the following commands one by one and make sure to fulfill each required action highlighted in the R console.

biocthis::use_bioc_news_md()  ## To add a NEWS.md file
biocthis::use_bioc_coc()  ## To add a .github/CODE_OF_CONDUCT.md file
biocthis::use_bioc_support()  ## To add a .github/SUPPORT.md file
biocthis::use_bioc_issue_template()  ## To add a .github/ISSUE_TEMPLATE/issue_template.md file
biocthis::use_bioc_citation()  ## To add a inst/CITATION file
usethis::use_lifecycle_badge("Experimental")  ## To add a "Experimental" badge to your README.Rmd
devtools::build_readme()  ## To rebuild README.md

2.3 Write functions to populate your package

To write your first package function, you can either create an R/<name>.R file manually, or run usethis::use_r(name).

TipReusing Day 1

In Day 1, you already prototyped three of these functions (importFiles(), filterGRanges(), computeCoverage()) in a plain script. Your job today is to move them into the package (one R/<name>.R file each), write the missing fourth function (plotCoverage()), and document them all. Prototyping in a script, then “packaging”, is exactly how real development goes!

Now is time to write! Refer to the functionalities (described in Section 0) that we are aiming to provide in this package, and start writing 4 functions which, together, can fulfill these functionalities. Briefly, the four functions are:

  1. importFiles(): import a coverage track and a set of genomic features (scaled to a fixed width) together in a list;
  2. filterGRanges(): filter the imported features to only retain those fully overlapping with covered genome segments (from the coverage track);
  3. computeCoverage(): compute the mean coverage and its confidence interval, for each position within the features’ width;
  4. plotCoverage(): plot the results with ggplot2.
Tip

We suggest these exact function names because the exercises for the following days (Day 3, 4 and 5) reuse them. Using the same names will let your package grow smoothly across the week.

Tip

These functions should work in a chain:

importFiles(bw_path, features_path, width) |>
    filterGRanges() |>
    computeCoverage() |>
    plotCoverage()

The only genuinely new function today is plotCoverage(), which draws the aggregated profile you sketched at the end of Day 1.

Question

Write a plotCoverage(df) function that takes the data.frame returned by computeCoverage() and returns a ggplot.

HintHint

Map distance to x and mean to y. Add a geom_ribbon() for the confidence interval (ymin = ci_low, ymax = ci_high) and a geom_line() for the mean.

plotCoverage <- function(df) {
    ggplot2::ggplot(df, ggplot2::aes(x = distance, y = mean)) +
        # TODO: geom_ribbon() for the confidence interval
        # TODO: geom_line() for the mean
        ggplot2::labs(x = "Distance to center (bp)", y = "Mean coverage")
}
plotCoverage <- function(df) {
    ggplot2::ggplot(df, ggplot2::aes(x = distance, y = mean)) +
        ggplot2::geom_ribbon(
            ggplot2::aes(ymin = ci_low, ymax = ci_high),
            fill = "steelblue", alpha = 0.2
        ) +
        ggplot2::geom_line(color = "steelblue") +
        ggplot2::labs(x = "Distance to center (bp)", y = "Mean coverage") +
        ggplot2::theme_bw()
}
Tip

The complete reference implementation of the four function files is available in Share/functions/. These files provide one possible implementation fulfilling our package’s requirements.

2.4 Add function documentation

Question

Write roxygen tags (@title, @description, @param, @return, …) for each function.

HintHint

roxygen comments start with #' and sit directly above the function. The first line is the title, a blank #' line separates blocks, and each @param documents one argument. Do not forget @export (so the function is usable) and @importFrom pkg fun (to declare the functions you borrow from other packages).

#' Import a coverage track and a set of genomic features
#'
#' Longer description of what the function does.
#'
#' @param bw_file Path to a coverage track file.
#' @param features_file Path to a features file.
#' @param width Integer. Width to resize each feature to.
#'
#' @return A named list with `coverage` and `features`.
#'
#' @importFrom rtracklayer import
#' @importFrom GenomicRanges resize
#' @export
importFiles <- function(bw_file, features_file, width) {
    # ...
}

Once you have documented each function, you can regenerate the documentation with document().

document()