Reproducible

From 太極
Jump to navigation Jump to search

Common Workflow Language (CWL)

R

  • A Reproducible Data Analysis Workflow with R Markdown, Git, Make, and Docker, Slides, Talks & Video. The whole idea is written in an R package repro package. The package create an R project Template where we can use it by RStudio -> New Project -> Create Example Repro Template. Note that the Makefile and Dockerfile can be inferred from the markdown.Rmd file. Note this approach does not make use the renv package. Also it cannot handle Bioconductor packages. Four elements
    • Git folder of source code for version control (R project)
    • Makefile. Make is a “recipe” language that describes how files depend on each other and how to resolve these dependencies.
    • Docker software environment (Containerization)
    • RMarkdown (dynamic document generation)
    automake() # Create '.repro/Dockerfile_packages', 
               #        '.repro/Makefile_Rmds' & 'Dockerfile'
               # and open <Makefile>
    
    # Modify <Makefile> by following the console output
    
    rerun() # will inspects the files of a project and suggest a way to 
            # reproduce the project. So just follow the console output
            # by opening a terminal and typing
    make docker && make -B DOCKER=TRUE
    
    # The above will generate the output html file in your browser
    

    In the end, it calls the following command according to the console output where 'reproproject' in this example is the Docker image name (same as my project name except it automatically converts the name to lower cases).

    docker run --rm --user 368262265 \
      -v "/Full_Path_To_Project":"/home/rstudio/" \
      reproproject Rscript \
      -e 'rmarkdown::render("/home/rstudio//markdown.Rmd", "all")' 
    
  • Advanced Reproducibility in Cancer Informatics
  • Teaching reproducibility and responsible workflow (2023 JSM)
  • An overview of what’s out there for reproducibility with R 2023/10/5
  • Building Reproducible Analytical Pipelines with R by Dr. Bruno André Rodrigues Coelho | Tunis R User 2023/12/9.

Rmarkdown

Rmarkdown package

packrat

Create a snapshot

  • Do we really need to call packrat::snapshot()? The walk through page says it is not needed but the lock file is not updated from my testing.
  • I got an error when it is trying to fetch the source code from bioconductor and local repositories: packrat is trying to fetch the source from CRAN in these two packages.
    • On normal case, the packrat/packrat.lock file contains two entries in 'Repos' field (line 4).
    • The cause of the error is I ran snapshot() after I quitted R and entered again. So the solution is to add bioc and local repositories to options(repos).
    • So what is important of running snapshot()?
    • Check out the forum.
> dir.create("~/projects/babynames", recu=T)
> packrat::init("~/projects/babynames")
Initializing packrat project in directory:
- "~/projects/babynames"

Adding these packages to packrat:
            _
    packrat   0.4.9-3

Fetching sources for packrat (0.4.9-3) ... OK (CRAN current)
Snapshot written to '/home/brb/projects/babynames/packrat/packrat.lock'
Installing packrat (0.4.9-3) ...
	OK (built source)
Initialization complete!
Unloading packages in user library:
- packrat
Packrat mode on. Using library in directory:
- "~/projects/babynames/packrat/lib"

> install.packages("reshape2")
> packrat::snapshot()

> system("tree -L 2 ~/projects/babynames/packrat/")
/home/brb/projects/babynames/packrat/
├── init.R
├── lib
│   └── x86_64-pc-linux-gnu
├── lib-ext
│   └── x86_64-pc-linux-gnu
├── lib-R            # base packages
│   └── x86_64-pc-linux-gnu
├── packrat.lock
├── packrat.opts
└── src
    ├── bitops
    ├── glue
    ├── magrittr
    ├── packrat
    ├── plyr
    ├── Rcpp
    ├── reshape2
    ├── stringi
    └── stringr

Restoring snapshots

Suppose a packrat project was created on Ubuntu 16.04 and we now want to repeat the analysis on Ubuntu 18.04. We first copy the whole project directory ('babynames') to Ubuntu 18.04. Then we should delete the library subdirectory ('packrat/lib') which contains binary files (*.so) that do not work on the new OS. After we delete the library subdirectory, start R from the project directory. Now if we run packrat::restore() command, it will re-install all missing libraries. Bingo! NOTE: Maybe I should use packrat::bundle() instead of manually copy the whole project folder.

Note: some OS level libraries (e.g. libXXX-dev) need to be installed manually beforehand in order for the magic to work.

$ rm -rf ~/projects/babynames/packrat/lib
$ cd ~/projects/babynames/
$ R
> 
> packrat::status()
> remove.packages("plyr")
> packrat::status()
> packrat::restore()

Workflow

setwd("ProjectDir")

packrat::init()

packrat::on()  # packrat::search_path()

install.packages()
# For personal packages stored locally
packrat::set_opts(local.repos = "~/git/R")
packrat::install_local("digest") # dir name of the package
library(YourPackageName) 
  # double check all dependent ones have been installed

packrat::snapshot()

packrat::bundle()

A bundle file (*.tar.gz) will be created under ProjectDir/packrat/src directory. Note this tar.gz file includes the whole project folder.

To unbundle the project in a new R environment/directory:

setwd("NewDirectory") # optional
packrat::unbundle(FullPathofBundleTarBall, ".") 
  # this will create 'ProjectDir'
  # CPU is more important than disk speed
  # At the end, it will show the project has been unbundled and restored at ...

setwd("ProjectDir")
packrat::packrat_mode()  # on
.libPaths()   # verify 
library()  # Expect to see packages in our bundle
# packrat::on()

Example 1: The above method works for packages from Bioconductor; e.g. S4Vectors which depends on BiocGenerics & BiocVersion only. However, Bioconductor project des not have a snapshot repository like MRAN. So it is difficult to reproduce the environment for an earlier release of Bioconductor.

Example 2: bundle our in-house R package for future reproducibility.

Set Up a Custom CRAN-like Repository

See https://rstudio.github.io/packrat/custom-repos.html. Note the personal repository name ('sushi' in this example) used in "Repository" field of the personal package will be used in <packrat/packrat.lock> file. So as long as we work on the same computer, it is easy to restore a packrat project containing packages coming from personal repository.

Common functions:

  • packrat::init()
  • packrat::snapshot(), packrat::restore()
  • packrat::clean()
  • packrat::status()
  • packrat::install_local() # http://rstudio.github.io/packrat/limitations.html
  • packrat::bundle() # see @28:44 of the video, packrat::unbundle() # see @29:17 of the same video. This will rebuild all packages
  • packrat::on(), packrat::off()
  • packrat::get_opts()
  • packrat::set_opts() # http://rstudio.github.io/packrat/limitations.html
  • packrat::opts$local.repos("~/local-cran")
  • packrat::opts$external.packages(c("devtools")) # break the isolation
  • packrat::extlib()
  • packrat::with_extlib()
  • packrat::project_dir(), .libPaths()

Warning

  • If we download and modify some function definition from a package in CRAN without changing DESCRIPTION file or the package name, the snapshot created using packrat::snapshot() will contain the package source from CRAN instead of local repository. This is because (I guess) the DESCRIPTION file contains a field 'Repository' with the value 'CRAN'.

Docker

Docker and Packrat.

  • This is a minimal example that installs a single package each from CRAN, bioconductor, and github to a Docker image using packrat.
  • All operations are done in the container. So the host OS does not need to have R installed.
  • The R script will install packrat in the container. It will also initialize packrat in the working directory and install R packages there. But in the packrat::snapshot() it chooses snapshot.sources = FALSE. The goal is to generate packrat.lock file.
  • The first part of generating packrat.lock is not quite right since the file was generated in the container only. We should use -v in the docker run command. The github repository at https://github.com/joelnitta/docker-packrat-example has fixed the problem.
$ git clone https://github.com/joelnitta/docker-packrat-example.git
$ cd docker-packrat-example

# Step 1: create the 'packrat.lock' file
$ nano install_packages.R     # note: nano is not available in the rstudio container
                              # need to install additional OS level packages like libcurl
                              # in rocker/rstudio. Probably rocker/tidyverse is better than rstudio
                              # 
$ docker run -it -e DISABLE_AUTH=true -v $(pwd):/home/rstudio/project rocker/tidyverse:3.6.0 bash
# Inside the container now
$ cd home/rstudio/project
$ time Rscript install_packages.R  # generate 'packrat/packrat.lock'
$ exit                        # It took 43 minutes.
                              # Question: is there an easier way to generate packrat.lock without
                              # wasting time to install lots of packages?
# Step 2: build the image
# Open another terminal/tab
$ nano Dockerfile             # change rocker image and R version. Make sure these two are the same as 
                              # we have used when we created the 'packrat.lock' file
$ time docker build . -t mycontainer # It took 45 minutes.
$ docker run -it mycontainer R

# Step 3: check the packages defined in 'install_packages.R' are installed
packageVersion("minimal")
packageVersion("biospear")

Questions:

  • After running the statement packrat::init(), it will leave a footprint of a hidden file .Rprofile in the current directory. PS: The purpose of .Rprofile file is to direct R to use the private package library (when it is started from the project directory).
    #### -- Packrat Autoloader (version 0.5.0) -- ####
    source("packrat/init.R")
    #### -- End Packrat Autoloader -- ####
If the 'packrat' directory was accidentally deleted, next time when you launch R it will show an error message because it cannot find the file.
  • The ownership of the 'packrat' directory will be root now. See this Package Management for Reproducible R Code.
  • This sophisticated approach does not save the package source code. If a package has been updated and the version we used has been moved to archive in CRAN, what will happen when we try to restore it? So it is probably better to use snapshot.sources = TRUE and run packrat::bundle().

renv: successor to the packrat package

Compare to packrat:

  • Many packages are difficult to build from sources. Your system will need to have a compatible compiler toolchain available. In some cases, R packages may depend on C/C++ features that aren't available in an older system toolchain, especially in some older Linux enterprise environments.
  • renv no longer attempts to explicitly download and track R package source tarballs within your project. For packages from local sources, refer this article.
  • renv has its discovery machinery to analyze your R code to determine which R packages will be included in the lock file. We can however instead prefer to capture all packages installed into your project library by using renv::settings$snapshot.type("all")

renv package does not have bundle() nor unbundle() function.

# mkdir renvdeseq2
setwd("renvdeseq2")
renv::init(bioconductor = TRUE) 
             # attempts to copy and reuse packages
             # already installed in your R libraries
             # We'll be asked to restart the R session if we
             # are not doing this in RStudio.
renv::install("BiocManager")

# method 1: this will only install packages under the curDir/renv/... folder
BiocManager::install("DESeq2")

# method 2: this will install packages in ~/.cache/R/renv/renv/... folder
#           therefore, the library can be reused by other needs.
options(repos = BiocManager::repositories())
renv::install("DESeq2") 

renv::snapshot() # create renv.lock
  # it seems the lock file "renvdeseq2/renv.lock" does not
  # save any package info I just installed from Bioconductor
  # except the renv package.
  # Read https://rstudio.github.io/renv/articles/faq.html

Find R package dependencies in a project

renv::dependencies()

The following line will make snapshot() to write all packages in renv .cache directory (e.g., ~/.cache/R/renv/cache/v5/R-4.2/x86_64-pc-linux-gnu/) to renv.lock file. Note that the setting is persistent even we restart R!

renv::settings$snapshot.type("all") # default is "implicit"
renv::snapshot() 

Pass renv.lock to other people and/or clone the project repository

# Make sure the 'renv' package has been installed on the remote computer
install.packages("renv")
renv::init()  # install the packages declared in renv.lock

Use renv::migrate() to port a Packrat project to renv.

renv::install()

Using renv to track the version of your packages in R (CC229). After renv::init(), it will identify some packages we don't have or have older versions... In the end we are informed some packages are not installed. Consider reinstalling these packages before snapshotting the lockfile. So go ahead and run renv::snapshot().

Other sources

For example, for DeMixT from github,

renv::init()
renv::install("wwylab/DeMixT")
# Error: package 'SummarizedExperiment' is not available
renv::install("bioc::SummarizedExperiment")
renv::install("wwylab/DeMixT")
renv::snapshot()

install.packages()

It seems install.packages() also install the packages in the project directory. So it's not clear what's the difference of install.packages() and renv::install() for simple case. But renv::install() is more flexible than install.packages().

Note that the installed packages won't go into the lock file unless the project is using it. For example, we can create a simple R file that calls "library(PACKAGENAME)" and in the R console we can run "source(MySimple.R)". Now when we run renv::snapshot(), the PACKAGENAME will be recorded.

If I open a project that loaded an renv environment, then calling "install.packages()" will install new packages into the renv's cache folder (e.g., ~/.cache/R/renv/cache/v5/R-4.2/x86_64-pc-linux-gnu/ in Linux). Note that the version number will be recorded too (e.g., ~/.cache/R/renv/cache/v5/R-4.2/x86_64-pc-linux-gnu/pkgndep/1.2.1 ).

Reference

See Reference.

Bioconductor

Using renv with Bioconductor

Create an Rmd file and include an R chunk "library(DESeq2)". Then run the following line

renv::init(bioconductor = TRUE)

and it will generate "renv.lock", ".Rprofile" files and "renv" directory.

PS.

  • When we install a fresh R in Ubuntu, we should run "sudo apt install r-base-dev curl libcurl4-openssl-dev libssl-dev libxml2-dev " system packages before we can successfully run "BiocManager::install('DESeq2')".
  • It is perfectly fine to run renv::init(bioconductor = TRUE) even if you have previously run renv::init() without the bioconductor argument. The bioconductor argument simply ensures that Bioconductor repositories are activated within your renv project.

renv::dependencies()

?dependencies. Find R packages used within a project. dependencies() will crawl files within your project, looking for R files and the packages used within those R files.

df <- renv::dependencies("Some_Dir")

It also search Rmd files from my testing.

renv::record()

You can use the record() function from the renv package to record a new entry within an existing renv.lock file.

renv::record("[email protected]")

However, the package is still not installed in the local directory. In other words, renv::record() seems to be an opposite function to renv::install() where renv::install() will install a package in the local directory even the package was not used anywhere.

renv::load()

?renv::load. It is especially useful in Windows OS.

Note that it does not change the working directory though.

renv::restore()

  • For renv-based project, we just need to share a text file renv.lock to our colleague. But for packrat-based project, we need to run bundle() command and pass a tar.gz file to our colleague.
  • See the output message on here. This is based on renv 0.16.0 (2022-09-29).

renv::update()

https://rstudio.github.io/renv/reference/update.html

renv::update() # including Bioconductor, Github, Gitlab, Git, Bitbucket, ...

renv::update(packages = c("dplyr", "ggplot2", "tidyr")) # update specific CRAN 

renv::install("bioc::Biobase") # install/update specific Bioconductor package

renv::update(packages = "mygithubpackage")

A case with issues using renv::snapshot() & renv::restore()

  • BiocGenerics in Bioconductor 3.17 is now 0.46.0 but I have 0.45.3. Also the current Bioconductor 3.18 shows BiocGenerics version 0.48.1.
    ...
    * Project '~/Project' loaded. [renv 0.17.3]
    * The project is currently out-of-sync.
    * Use `renv::status()` for more details.
    
    > renv::snapshot()
    The following Bioconductor packages appear to be from a separate Bioconductor release:
    
    	BiocGenerics [installed 0.45.3 != latest 0.46.0]
    
    renv may be unable to restore these packages.
    Bioconductor version: 3.17
    
    The following package(s) have unsatisfied dependencies:
    
    	MatrixModels requires Matrix (>= 1.6-0), but version 1.5-4 is installed
    
    Consider updating the required dependencies as appropriate.
    
    Do you want to proceed? [y/N]: N
    
    > packageVersion("BiocGenerics")
    [1] ‘0.45.3’
    > packageVersion("Matrix")
    [1] ‘1.5.4’
    > packageVersion("MatrixModels")
    [1] ‘0.5.3’
    
    > packageVersion("renv")
    [1] ‘0.17.3’

    Q: MatrixModel was not recorded in renv.lock. Why renv::snapshot() shows unsatisfied dependencies for the 'MatrixModels' package. Open a terminal and list the files in directory "./renv/library/R-4.3/aarch64-apple-darwin20" by dates. Decide to delete the package. In the end, I run remove.packages("MatrixModels") and BiocManager::install("BiocGenerics") to update the package to the latest version in Bioconductor 3.17 (old) release.

  • (Cont.) When I run renv::restore() on another machine, I got an error related to BiocGenerics.
    > renv::restore()
    It looks like you've called renv::restore() in a project that hasn't been activated yet.
    How would you like to proceed?
    
    1: Activate the project and use the project library.
    2: Do not activate the project and use the current library paths.
    3: Cancel and resolve the situation another way.
    
    Selection: 1
    - renv activated -- please restart the R session.
    The following package(s) will be updated:
    
    # Bioconductor ---------------------------------------------------------------
    - BiocGenerics        [0.46.0 -> 0.45.3]
    - IRanges             [2.34.1 -> 2.34.0]
    - S4Vectors           [0.38.2 -> 0.38.1]
    
    # CRAN -----------------------------------------------------------------------
    - BiocManager         [1.30.22 -> 1.30.20]
    ...
    - Downloading S4Vectors from Bioconductor ...   OK [819.2 Kb in 0.63s]
    - Downloading BiocGenerics from Bioconductor ... 	ERROR [error code 22]
    - Downloading BiocGenerics from Bioconductor ... 	ERROR [error code 22]
    - Downloading S4Vectors from Bioconductor ...   	ERROR [error code 22]
    Warning: failed to find source for 'S4Vectors 0.38.1' in package repositories
    Warning: failed to find source for 'BiocGenerics 0.45.3' in package repositories
    Warning: error downloading 'https://bioconductor.org/packages/3.17/bioc/src/contrib/Archive/BiocGenerics/BiocGenerics_0.45.3.tar.gz' [error code 22]
    Warning: error downloading 'https://cran.rstudio.com/src/contrib/Archive/BiocGenerics/BiocGenerics_0.45.3.tar.gz' [error code 22]
    Warning: error downloading 'https://cran.rstudio.com/src/contrib/Archive/S4Vectors/S4Vectors_0.38.1.tar.gz' [error code 22]
    Error: failed to retrieve package '[email protected]'
    Traceback (most recent calls last):
    9: renv::restore()
    8: renv_restore_run_actions(project, diff, current, lockfile, rebuild)
    7: retrieve(packages)
    6: handler(package, renv_retrieve_impl(package))
    5: renv_retrieve_impl(package)
    4: renv_retrieve_bioconductor(record)
    3: renv_retrieve_repos(record)
    2: stopf("failed to retrieve package '%s'", renv_record_format_remote(record))
    1: stop(sprintf(fmt, ...), call. = call.)
  • I go back to the original project. Run 'BiocManager::install("BiocGenerics")' and remove.packages("MatrixModels")
    > renv::snapshot()
    The following package(s) will be updated in the lockfile:
    
    # Bioconductor =======================
    - BiocGenerics        [0.45.3 -> 0.46.0]
    
    # CRAN ===============================
    - Matrix              [1.5-4 -> 1.6-5]
    ...
    The version of R recorded in the lockfile will be updated:
    - R                   [4.3.1 -> 4.3.2]
    
    Do you want to proceed? [y/N]: y

    Now I copy renv.lock to another machine/place. Call renv::restore() to test again.

  • (Cont.) renv::restore() did show errors in the processing, but failed to give a warning at the end.
    > renv::restore()
    It looks like you've called renv::restore() in a project that hasn't been activated yet.
    How would you like to proceed?
    
    1: Activate the project and use the project library.
    2: Do not activate the project and use the current library paths.
    3: Cancel and resolve the situation another way.
    
    Selection: 1
    - renv activated -- please restart the R session.
    The following package(s) will be updated:
    ...
    Do you want to proceed? [Y/n]:
    # Downloading packages -------------------------------------------------------
    - Downloading vctrs from CRAN ...               OK [file is up to date]
    - Downloading tinytex from CRAN ...             OK [file is up to date]
    ...
    - Downloading S4Vectors from Bioconductor ...   OK [file is up to date]
    - Downloading mgcv from CRAN ...                	ERROR [error code 22]
    - Downloading mgcv from CRAN ...                OK [file is up to date]
    - Downloading nlme from CRAN ...                	ERROR [error code 22]
    - Downloading nlme from CRAN ...                OK [file is up to date]
    ...
    Successfully downloaded 60 packages in 460 seconds.
    
    # Installing packages --------------------------------------------------------
    - Installing clue ...                           OK [copied from cache]
    - Installing lattice ...                        OK [copied from cache]
    ...
    The following loaded package(s) have been updated:
    - BiocManager
    - renv           <------------  Something is wrong. Just 2 packages got installed.
    Restart your R session to use the new versions.
    > q()
    Save workspace image? [y/n/c]: n
    
    $ R
    - Project '~/Project' loaded. [renv 1.0.4]
    - One or more packages recorded in the lockfile are not installed.
    - Use `renv::status()` for more details.
    Warning message:
    renv 1.0.4 was loaded from project library, but this project is configured to use renv ${VERSION}.
    Use `renv::record("[email protected]")` to record renv 1.0.4 in the lockfile.
    Use `renv::restore(packages = "renv")` to install renv ${VERSION} into the project library.
    
    > packageVersion("renv")
    [1] ‘1.0.4’
    > library()    <-------------- Just show 2 packages in the renv directory.
  • (Cont.) I repeat the step of calling renv::restore() again. Now library() shows a complete list.
    installed.packages(lib="./renv/library/R-4.3/x86_64-pc-linux-gnu") |> dim()
    [1] 227  16

    Testing loading packages on the new machine and everything looks well.

  • It seems to be OK the renv versions are different on the old (0.17.3) and new systems (1.0.3). But a problem with using the old renv is BiocVersion recorded in lockfile but not used in this project. So I decided to upgrade the renv package. After upgrading the version, the warning is gone.

A case with only one CRAN package and the first time use

  • I put glmnet in an R file.
  • renv::init() returned a warning message.
    > install.packages("renv") # 1.0.4 in R 4.3.2
    > renv::init()
    
    renv: Project Environments for R
    
    Welcome to renv! It looks like this is your first time using renv.
    This is a one-time message, briefly describing some of renv's functionality.
    
    renv will write to files within the active project folder, including:
    
      - A folder 'renv' in the project directory, and
      - A lockfile called 'renv.lock' in the project directory.
    
    In particular, projects using renv will normally use a private, per-project
    R library, in which new packages will be installed. This project library is
    isolated from other R libraries on your system.
    
    In addition, renv will update files within your project directory, including:
    
      - .gitignore
      - .Rbuildignore
      - .Rprofile
    
    Finally, renv maintains a local cache of data on the filesystem, located at:
    
      - "~/.cache/R/renv"
    
    This path can be customized: please see the documentation in `?renv::paths`.
    
    Please read the introduction vignette with `vignette("renv")` for more information.
    You can browse the package documentation online at https://rstudio.github.io/renv/.
    Do you want to proceed? [y/N]: y
    
    - "~/.cache/R/renv" has been created.
    - Resolving missing dependencies ...
    # Downloading packages -------------------------------------------------------
    - Downloading glmnet from CRAN ...              OK [2.3 Mb in 0.17s]
    - Downloading foreach from CRAN ...             OK [87.7 Kb]
    - Downloading iterators from CRAN ...           OK [293.2 Kb in 0.11s]
    - Downloading shape from CRAN ...               OK [631.3 Kb in 0.15s]
    - Downloading Rcpp from CRAN ...                OK [3.3 Mb in 0.21s]
    - Downloading RcppEigen from CRAN ...           OK [1.4 Mb in 0.15s]
    Successfully downloaded 6 packages in 2.6 seconds.
    
    # Installing packages --------------------------------------------------------
    - Installing iterators ...                      OK [built from source and cached in 1.3s]
    - Installing foreach ...                        OK [built from source and cached in 1.4s]
    - Installing shape ...                          OK [built from source and cached in 1.5s]
    - Installing Rcpp ...                           OK [built from source and cached in 30s]
    - Installing RcppEigen ...                      OK [built from source and cached in 41s]
    - Installing glmnet ...                         OK [built from source and cached in 1.2m]
    The following required packages are not installed:
    - codetools  [required by foreach]
    - Matrix     [required by glmnet]
    - survival   [required by glmnet]
    Consider reinstalling these packages before snapshotting the lockfile.
    
    The following package(s) will be updated in the lockfile:
    
    # CRAN -----------------------------------------------------------------------
    - foreach     [* -> 1.5.2]
    - glmnet      [* -> 4.1-8]
    - iterators   [* -> 1.0.14]
    - Rcpp        [* -> 1.0.12]
    - RcppEigen   [* -> 0.3.3.9.4]
    - renv        [* -> 1.0.4]
    - shape       [* -> 1.4.6.1]
    
    The version of R recorded in the lockfile will be updated:
    - R           [* -> 4.3.2]
    
    - Lockfile written to "/tmp/test/renv.lock".
    - renv activated -- please restart the R session.
    > q()
    

    I copy renv.lock to renv-old.lock for comparison purpose later. Note that 'R' repository is "https://cloud.r-project.org".

  • Quit R and re-run renv::snapshot() helps.
    - Project '/tmp/test' loaded. [renv 1.0.4]
    - The project is out-of-sync -- use `renv::status()` for details.
    > renv::status()
    The following package(s) are in an inconsistent state:
    
     package   installed recorded used
     codetools y         n        y
     lattice   y         n        y
     Matrix    y         n        y
     survival  y         n        y
    
    See ?renv::status() for advice on resolving these issues.
    > packageVersion("renv")
    [1] ‘1.0.4’
    > renv::snapshot()
    The following package(s) will be updated in the lockfile:
    
    # CRAN -----------------------------------------------------------------------
    - codetools   [* -> 0.2-19]
    - lattice     [* -> 0.22-5]
    - Matrix      [* -> 1.6-1.1]
    - survival    [* -> 3.5-7]
    
    Do you want to proceed? [Y/n]:
    - Lockfile written to "/tmp/test/renv.lock".
    > q()
    

    Close and open R again. No complain.

    - Project '/tmp/test' loaded. [renv 1.0.4]
    > renv::status()
    No issues found -- the project is in a consistent state.
    
  • Compare the renv-old.lock and current renv.lock files. Matrix, codetools, lattice and survival packages are added.

rig system make-orthogonal

The command rig system make-orthogonal is used to make installed versions of R orthogonal. This means that it ensures that different versions of R installed on the same system do not interfere with each other

$ cd ~/Project1          # This does not matter as RStudio does not care about this

$ rig rstudio 4.3-arm64  # Good
[INFO] Running open -n -a RStudio --env RSTUDIO_WHICH_R=/Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/R

$ rig rstudio 4.2-arm64  # Error 
[ERROR] R 4.2-arm64 is not orthogonal, it cannot run as a non-default. Run `rig system make-orthogonal`.

$ rig system make-orthogonal  # Fix the error
[INFO] Running `sudo` for updating the R installations. This might need your password.
Password:
[INFO] Making all R versions orthogonal

$ rig rstudio 4.2-arm64 # No more error even RStudio still opens the last project
                        # no based on the current working directory
[INFO] Running open -n -a RStudio --env RSTUDIO_WHICH_R=/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/R

Summary so far

I have two ways for associate an R project with an R version (at least on my mac). Install rig and use rig add to install multiple versions of R.

  1. Use "renv" to create an renv environment project. At the end, I run mv .Rprofile Rprofile. This will prevent loading renv environment automatically (so the current default R version does not matter) and I have a backup of the current renv environment. If I need, I can still rename Rprofile back to .Rprofile and launch R/RStudio.
  2. Use "renv" to create an renv environment project. Use rig rstudio 4.2-arm64 to launch RStudio and manually change the project to the desired project (from the last open project).

To use with RStudio IDE, see

  • How to launch a specific version of R from a specific directory from the rig page. It works well when the project directory is an renv directory.
  • My current solution; see Install R (not specifically related to renv).
open -n -a RStudio ~/proj/proj.Rproj \
  --env RSTUDIO_WHICH_R=/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/R

Tips

  • renv::init() will check any syntax errors
    > renv::init()
    WARNING: One or more problems were discovered while enumerating dependencies.
    
    /tmp/Project1/RRR.R
    
    ERROR 1: /tmp/Project1/RRR.R:2:1: unexpected '>' 1: # This is a test for renv 2: > ^ Please see `?renv::dependencies` for more information. Do you want to proceed? [y/N]: y

    At the end, it will not record any packages from the R file in the renv.lock file. When we start R next time, we will see error/warning messages again

    * Project '/tmp/Project1' loaded. [renv 0.17.0]
    WARNING: One or more problems were discovered while enumerating dependencies.
    
    /tmp/Project1/RRR.R
    -------------------
    
    ERROR 1: /tmp/Project1/RRR.R:2:1: unexpected '>'
    1: # This is a test for renv
    2: >
       ^
    
    Please see `?renv::dependencies` for more information.
    Error: snapshot aborted
    Traceback (most recent calls last):
    43: source("renv/activate.R")
    42: withVisible(eval(ei, envir))
    ...
    1: stop(condition)
    [Previously saved workspace restored]
    
  • Even for a very simple R file/case, I find "rm -rf renv" will fail if I decide to "clean" the directory.
    rm: cannot remove 'renv/sandbox/R-4.3/x86_64-pc-linux-gnu/9a444a72/compiler': Permission denied
    ...
    
  • For code chunks that you’d explicitly like renv to ignore, you can include renv.ignore=TRUE in the chunk header
  • Ignoring Files: .gitignore and .renvignore
  • Errors: Use something like renv::settings$snapshot.type("explicit") Check out the github issues page

Hash

renv - manually overwrite package version in lock file. The hash is used for caching; it allows renv::restore() to restore a package from the global renv cache if available, thereby avoiding a retrieve + build + install of the package.

If it is not set, then renv will not use the cache and instead always try to retrieve the package from the declared source.

Cache and path customization

On Linux all R packages under "renv/library/R-4.3/x86_64-pc-linux-gnu/" folder are just soft links to folders in the renv cache directory. So the project specific renv directory does not take much space.

On my macOS, the cache directory is

> renv::paths$cache()
[1] "/Users/USERNAME/Library/Caches/org.R-project.R/R/renv/cache/v5/R-4.3/aarch64-apple-darwin20"

On my Linux system, the cache directory is

> renv::paths$cache()
[1] "/home/USERNAME/.cache/R/renv/cache/v5/R-4.3/x86_64-pc-linux-gnu"

isolate()

  • How can I copy and entire renv based project to a new PC (which does not have internet access)?
  • ?isolate - Copy packages from the renv cache directly into the project library, so that the project can continue to function independently of the renv cache. Remember: normally the R packages under renv/ directory are soft link to renv cache directory. If we use isolate(), the R packages will be "copied" instead of "linked" to the project/renv folder.
  • If you want to undo the isolation and revert back to using the renv cache, you can delete the packages in your project library and then call renv::restore(). This will reinstall the packages from the renv cache and create symlinks in your project library.

Set the default repository

  • According to the NEWS, renv 1.0.0 now uses Posit Public Package Manager by default, for new projects where the repositories have not already been configured externally.
  • It seems this is not true. To manually set the repository to PPM,
    options(renv.config.ppm.enabled = TRUE)
    

    After I've done this once, the next time when I use renv::init(), it will use "https://packagemanager.posit.co/cran/latest" as the CRAN repository.

  • Setting CRAN repository options. Search for PPM (Posit Package Manager).

Private R packages

The Package Cellar

Local R packages

Deprecated?

  • https://rstudio.github.io/renv/articles/local-sources.html
  • Since local R packages (no matter it is source or binary) are not part of renv.lock, the original location of these packages are not important when we first install these packages.
  • When we try to restore local R packages, we can put these packages' source files into renv/local directory.
# mkdir renvbiotrip
setwd("renvbiotrip")
renv::init() # we shall restart R according to the instruction
# * Initializing project ...
# * Discovering package dependencies ... Done!
# * Copying packages into the cache ... Done!
# The following package(s) will be updated in the lockfile:
# CRAN ===============================
# - renv   [* -> 0.10.0]
# * Lockfile written to '/tmp/renvbiotrip/renv.lock'.
# * Project '/tmp/renvbiotrip' loaded. [renv 0.10.0]
# * renv activated -- please restart the R session.

renv::install("~/Downloads/MyPackage_0.1.1.tar.gz")
# 1. The above command will take care of the dependence. Cool !
#    That is, we don't need to use the remotes package.
# 2. The output will show if packages are installed from 
#    'linked cache' or from source
renv::settings$snapshot.type("all") 
renv::snapshot()
# It will give a message some package(s) were installed from an unknown source
# renv may be unable to restore these packages in the future.

Since the dependence package versions change from time to time, if we compare the renv.lock file created yesterday it will likely be different from what we created today (package version and hash tag).

Now we are ready to test the restoration.

  • Pass renv.lock and MyPackage_0.1.1.tar.gz to other people (different instruction if we pass the project repository?). Suppose we have copied renv.lock to renvbiotrip/ directory on a new computer.
    # mkdir renvbiotrip
    ## Copy renv.lock to renvbiotrip/
    # mkdir renvbiotrip/renv/local
    ## Copy MyPackage_0.1.1.tar.gz (private packages) to renvbiotrip/renv/local
    install.packages("renv")
    renv::restore()  # install the packages declared in renv.lock
                 
    # The output will show if packages are installed from 
    # 'linked cache' or from source       
    library(MyPackage) # verify  
    MyPackage::foo()   # test    
    
  • We can test renv.lock in a Docker container from another directory to mimic the way of passing the file to other people. For example,
    docker run --rm -it -v $(pwd):/home/docker -w /home/docker r-base:4.0.0
    
  • We can create a docker image based on the renv.lock and MyPackage.tar.gz files. See the renvbiotrip repository.

Note that

  • If we issue renv::restore() instead of renv::init() on the destination machine, the packages will be installed into the global environment.
  • It seems renv::init() is equivalent to renv::activate() AND renv::restore() on the destination machine.

The project library is out of sync with the lockfile

We'll get this message if we start R with a version different from what is in the "renv.lock" file. See install a package on an old version of R.

graph

> graph(root = "devtools", leaf = "rlang")
Error in inherits(edges, "formula") : 
  argument "edges" is missing, with no default

Docker

  • Using renv with Docker. Note that there are two ways for the Docker approach. One way is to include package installation in the Docker file which embeds the packages into the image. A second approach is to add appropriate R packages when the container is run.
    1. Creating Docker Images with renv (see here for 3 example Registries: Rocker Project/R-Hub/RStudio). Note: r-base:X.X.X image does not include several important libraries like "curl". If we use r-base.X.X.X as the base image, we will run into errors when we call renv::restore(). Docker images from Bioconductor (which is based on rocker/rstudio) has included utilities.
      RUN R -e "install.packages('renv', repos = c(CRAN = 'https://cloud.r-project.org'))"
      WORKDIR /home/docker
      COPY renv.lock renv.lock
      ENV RENV_PATHS_LIBRARY renv/library
      RUN R -e 'renv::restore()'
      CMD ["R"]
      
    2. Running Docker Containers with renv. Note that repository name must be lowercase.
      docker build -t projectname .
      docker run --rm -it projectname # OR
      docker run --rm -it -v $(pwd):/home/docker projectname
      

      Question: how to update a package within a container? 1. start the container with root and update packages in the container 2. system("su docker") to switch to the user 'docker'. 3. when we run system("su docker"), it will exit R and go to the shell. Run "whoami" to double check the current user and type "R" to enter R again.

      Another simple but inferior way to test the docker method is the following: assuming <renv.lock> is saved in the ProjectDir directory and the ProjectDir directory does not have renv nor .Rprofile. The big drawback of this approach is the created renv directory and <.Rprofile> belongs to the user root.

      docker run --rm -it -v ProjectDir:/home r-base:4.0.0
      install.packages("renv")
      setwd("/home")
      renv::init()
      
    3. Back up images. How to copy Docker images from one host to another without using a repository by using the docker save command.

pracpac package

pracpac - Practical 'R' Packaging in 'Docker'

Github actions

Chapter 5 Testing with a reproducible environment

checkpoint

R → Reproducible Research

dockr package

'dockr': easy containerization for R

Docker & Singularity

Docker

targets package

  • targets: Democratizing Reproducible Analysis "Pipelines" Will Landau.
    • It is similar to the Linux make command.
    • It’s designed to help with computationally demanding analysis projects. The package skips costly runtime for tasks that are already up to date.
    • An example. This pipeline reads in a CSV file, performs a transformation, and then generates a summary and a plot.
    # Load the necessary library
    library(targets)
    
    # Define the pipeline
    tar_plan(
      tar_target(
        raw_data,
        read.csv("data.csv")  # Assume you have a CSV file named "data.csv"
      ),
      tar_target(
        transformed_data,
        raw_data %>% transform()  # Perform your transformation here
      ),
      tar_target(
        summary,
        transformed_data %>% summary()
      ),
      tar_target(
        plot,
        ggplot(transformed_data, aes(x = x, y = y)) +
          geom_point() +
          theme_minimal()
      )
    )
    
    # Run the pipeline
    tar_make()
    If the data.csv file doesn’t change, and the transformation function remains the same, the targets package won’t re-run those steps. It will directly use the results from the previous run, saving computational resources. This is the power of the targets package: it intelligently determines which parts of your analysis need to be updated and which parts can be skipped.
    Please replace "data.csv" and transform() with your actual data file and transformation function. Also, replace aes(x = x, y = y) with the actual variables you want to plot.
  • Why you should consider working on a dockerized development environment
  • Building reproducible analytical pipelines with R at ReproTea (2023-07-19). renv, targets, docker, Dockerfile (packages from posit) and alternatives (Podman, Nix). Nice talk.

rix package

Building reproducible analytical pipelines with R

Nix

  • https://nixos.org/download.html
  • Current (2024/2/27) version 2.20.3.
    $ sh <(curl -L https://nixos.org/nix/install) --daemon
    
    $ nix-shell -p R rPackages.ggplot2
    
    # to install a package
    $ nix-env -iA nixos.librewolf
    $ sudo nix-env -iA nixos.librewolf
    
    # to remove an installed package,
    $ nix-env -e [package_name]
  • Plots can be shown when we call a plot function in a nix interactive shell.
  • For some reason, the Bioconductor packages will need to compile when I run nix-build.
  • What is the difference of using nix-env and nix-shell?
    • nix-env is a global installation. It is similar to traditional package managers like apt, yum, or brew. It is not ideal for reproducibility.
    • nix-shell is a local installation. These packages are not installed globally. The environment is temporary and isolated. A nix-shell will temporarily modify your $PATH environment variable. This can be used to try a piece of software before deciding to permanently install it. By specifying packages in a shell.nix or default.nix file, you can ensure consistent development environments across different machines or projects.
    $ nix-env -iA nixpkgs.rPackages.dplyr
    
    $ nix-shell -p rPackages.dplyr
    
  • Getting Started With Nix Package Manager: A Beginner’s Guide 2024
  • How To Install openSSH on NixOS
  • NixOS

Dev Containers

Easy R Tutorials with Dev Containers

conda, mamba

How to create a conda or mamba environment for R programming to enhance reproducibility (CC230) by Riffomonas Project

Snakemake

Papers

High-throughput analysis suggests differences in journal false discovery rate by subject area and impact factor but not open access status

Share your code and data

Misc

  • 4 great free tools that can make your R work more efficient, reproducible and robust
  • digest: Create Compact Hash Digests of R Objects
  • memoise: Memoisation of Functions. Great for shiny applications. Need to understand how it works in order to take advantage. I modify the example from Efficient R by moving the data out of the function. The cache works in the 2nd call. I don't use benchmark() function since it performs the same operation each time (so favor memoise and mask some detail).
    library(ggplot2) # mpg 
    library(memoise) 
    plot_mpg2 <- function(mpgdf, row_to_remove) {
      mpgdf = mpgdf[-row_to_remove,]
      plot(mpgdf$cty, mpgdf$hwy)
      lines(lowess(mpgdf$cty, mpgdf$hwy), col=2)
    }
    m_plot_mpg2 = memoise(plot_mpg2)
    system.time(m_plot_mpg2(mpg, 12))
    #   user  system elapsed
    #  0.019   0.003   0.025
    system.time(plot_mpg2(mpg, 12))
    #   user  system elapsed
    #  0.018   0.003   0.024
    system.time(m_plot_mpg2(mpg, 12))
    #   user  system elapsed
    #  0.000   0.000   0.001
    system.time(plot_mpg2(mpg, 12))
    #   user  system elapsed
    #  0.032   0.008   0.047
And be careful when it is used in simulation.
f <- function(n=1e5) { 
  a <- rnorm(n)
  a
} 
system.time(f1 <- f())
mf <- memoise::memoise(f)
system.time(f2 <- mf())
system.time(f3 <- mf())
all.equal(f2, f3) # TRUE