An introduction to renv

Create reproducible environments for R projects

December 2024

Nicolas Casajus

Senior data scientist
@FRB-CESAB    

Context

  • Research compendium for file organization
  • Writing functions for code optimization
  • Quarto | RMarkdown for literate programming
  • git for version control
  • GitHub for sharing & collaboration


All these tools aim to make your code and data more reproducible.


 What about the computational environment?

  •  packages (and versions)
  •  version
  • Software and system libraries
  • Operating system

Context

  • Research compendium for file organization
  • Writing functions for code optimization
  • Quarto | RMarkdown for literate programming
  • git for version control
  • GitHub for sharing & collaboration


All these tools aim to make your code and data more reproducible.


 What about the computational environment?

  •  packages (and versions)
  •  version
  • Software and system libraries
  • Operating system

What is the issue with packages?

  • Packages rapidly change over time (versions)
  • Code breaks between major versions
  • Accessible from different repositories (CRAN, GitHub, R Universe, etc.)
  • Only one version of a package can be installed
  • A package can have many dependencies


Questions

  • Will my project work on another laptop?
  • Will my project work in the future?
  • How to deal with projects requiring different versions of a same package?


 We have to specify, the required packages, their versions, and the repositories in which they are accessible!

The renv package


# Install 'renv' package ----
install.packages("renv")


Table: Main functions of renv
Function Description
init() Initialize renv in a project
status() Check consistencies between lockfile & project library
snapshot() Record current state of the project library in the lockfile
restore() Restore project library from a lockfile
install() Install packages in the project library
remove() Remove packages from the project library
deactivate() Temporary deactivate renv for the project
activate() (Re)activate renv in the project

Library vs. package

PACKAGE

A well-structured collection of functions, documentation, data, and tests.


LIBRARY

A directory in which packages will be installed.


## Available libraries ----

.libPaths()

## [1] "/home/nicolas/.local/bin/R/x86_64-pc-linux-gnu-library/4.4"
## [2] "/usr/lib/R/library"


[1] is the User library
[2] is the System library

Library vs. package

PACKAGE

A well-structured collection of functions, documentation, data, and tests.


LIBRARY

A directory in which packages will be installed.


## Available libraries ----

.libPaths()

## [1] "/home/nicolas/.local/bin/R/x86_64-pc-linux-gnu-library/4.4"
## [2] "/usr/lib/R/library"


[1] is the User library
[2] is the System library

## Locate core package ----

find.package("utils")

## [1] "/usr/lib/R/library/"

Library vs. package

PACKAGE

A well-structured collection of functions, documentation, data, and tests.


LIBRARY

A directory in which packages will be installed.


## Available libraries ----

.libPaths()

## [1] "/home/nicolas/.local/bin/R/x86_64-pc-linux-gnu-library/4.4"
## [2] "/usr/lib/R/library"


[1] is the User library
[2] is the System library

## Locate core package ----

find.package("utils")

## [1] "/usr/lib/R/library/"


## Locate other package ----

find.package("rmarkdown")

## [1] "/home/nicolas/.local/bin/R/x86_64-pc-linux-gnu-library/4.4/"

Library system

Default  behaviour

Library system

Default  behaviour

Library system with renv

Library system

Default  behaviour

Library system with renv

Initialize renv in a project

## Initialize renv in a project ----

renv::init()


R version 4.4.2 (2024-10-31) -- "Pile of Leaves"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu

- Project '~/Documents/myproject' loaded. [renv 1.0.11]
>


Alternatively, for a new project:

What happened?

.
├── .Rprofile
│
├── renv/
│   ├── .gitignore
│   ├── activate.R
│   ├── library/
│   └── settings.dcf
│
└── renv.lock

What happened?

.
├── .Rprofile          # Activate renv on project opening
│
├── renv/
│   ├── .gitignore
│   ├── activate.R
│   ├── library/
│   └── settings.dcf
│
└── renv.lock

What happened?

.
├── .Rprofile          # Activate renv on project opening
│
├── renv/
│   ├── .gitignore     # Ignore large renv files (e.g. packages)
│   ├── activate.R     # R script to launch renv
│   ├── library/       # R packages
│   └── settings.dcf   # renv settings
│
└── renv.lock

What happened?

.
├── .Rprofile          # Activate renv on project opening
│
├── renv/
│   ├── .gitignore     # Ignore large renv files (e.g. packages)
│   ├── activate.R     # R script to launch renv
│   ├── library/       # R packages
│   └── settings.dcf   # renv settings
│
└── renv.lock          # Packages metadata, a.k.a the Lockfile

The lockfile

It’s a simple JSON (JavaScript Object Notation) file that specifies package metadata:

  • package name
  • package version
  • package repository URL


Preview of a lockfile (renv.lock)

{
  "R": {
  
    "Version": "4.4.2",
    "Repositories": [
      {
        "Name": "CRAN",
        "URL": "https://cloud.r-project.org"
      }
    ]
  },
  
  "Packages": {
  
    "renv": {
      "Package": "renv",
      "Version": "1.0.11",
      "Source": "Repository",
      "Repository": "CRAN",
      "Requirements": [
         "utils"
      ],
      "Hash": "47623f66b4e80b3b0587bc5d7b309888"
    }
  }
}

The lockfile

It’s a simple JSON (JavaScript Object Notation) file that specifies package metadata:

  • package name
  • package version
  • package repository URL


Preview of a lockfile (renv.lock)





  To collaborate you only need to share the renv.lock

{
  "R": {
  
    "Version": "4.4.2",
    "Repositories": [
      {
        "Name": "CRAN",
        "URL": "https://cloud.r-project.org"
      }
    ]
  },
  
  "Packages": {
  
    "renv": {
      "Package": "renv",
      "Version": "1.0.11",
      "Source": "Repository",
      "Repository": "CRAN",
      "Requirements": [
         "utils"
      ],
      "Hash": "47623f66b4e80b3b0587bc5d7b309888"
    }
  }
}

Working w/ renv

  • renv::status() is your friend:

Use this function at any time to check inconsistencies between lockfile, library, and dependencies


  Package used but not installed in the project library

renv::status()

## The following package(s) are used in this project, but are 
## not installed:
## - fs

Working w/ renv

  • renv::status() is your friend

Use this function at any time to check inconsistencies between lockfile, library, and dependencies


  Package used but not installed in the project library

renv::status()

## The following package(s) are used in this project, but are 
## not installed:
## - fs

  Package used and installed but not recorded in the lockfile

renv::status()

## The following package(s) are in an inconsistent state:
## 
## package installed recorded used
## fs      y         n        y 

Working w/ renv

  • renv::status() is your friend

Use this function at any time to check inconsistencies between lockfile, library, and dependencies


  Package used but not installed in the project library

renv::status()

## The following package(s) are used in this project, but are 
## not installed:
## - fs

  Package used and installed but not recorded in the lockfile

renv::status()

## The following package(s) are in an inconsistent state:
## 
## package installed recorded used
## fs      y         n        y 


  Package used, installed and recorded in the lockfile

renv::status()

## No issues found -- the project is in a consistent state. 

Working w/ renv

  • renv::status() is your friend

Use this function at any time to check inconsistencies between lockfile, library, and dependencies


  Package used but not installed in the project library

renv::status()

## The following package(s) are used in this project, but are 
## not installed:
## - fs

  Package used and installed but not recorded in the lockfile

renv::status()

## The following package(s) are in an inconsistent state:
## 
## package installed recorded used
## fs      y         n        y 


  Package used, installed and recorded in the lockfile

renv::status()

## No issues found -- the project is in a consistent state. 

  Package not used but recorded in the lockfile

renv::status()

## The following package(s) are in an inconsistent state:
## 
## package installed recorded used
## fs      y         y        n  

Installing packages

  • Use the function renv::install() to install packages in your project library

You can install the latest version of a package, a specific version of a package, a package from GitHub, GitLab, etc.


## Install the latest version from CRAN ----
renv::install("devtools")

## Install a specific version ----
renv::install("devtools@2.3.0")

## Install the development version from GitHub ----
renv::install("r-lib/devtools")


  If you have a DESCRIPTION file w/ a list of packages, you can install all required packages as follow:


## Install (latest versions) of packages listed in DESCRIPTION ----
renv::install()

Updating the lockfile

  • Use the function renv::snapshot() to update the lockfile (renv.lock)

This function will add and/or remove package metadata in the lockfile


## Update the lockfile ----
renv::snapshot()

## The following package(s) will be updated in the lockfile:
## 
## # CRAN ----------------------------------------------------
## - fs    [1.6.5 -> *]
## - cli   [* -> 3.6.3]


  • We remove the fs package metadata from the lockfile
  • We add the cli package metadata to the lockfile

Collaborating w/ renv

  • Just share the renv.lock file


Then your colleague will have to initialize renv in the project and follow instructions:


## Initialize renv & restore environment ----
renv::init()

## This project already has a lockfile. What would you like to do? 
## 
## 1: Restore the project from the lockfile.
## 2: Discard the lockfile and re-initialize the project.
## 3: Activate the project without snapshotting or installing any packages.
## 4: Abort project initialization.
## 
## Selection: 1
## The following package(s) will be updated:
## 
## # CRAN -----------------------------------------------------------------
## - fs     [* -> 1.6.5]
## - renv   [* -> 1.0.11]
## 
## # Downloading packages -------------------------------------------------
## 
## ...
## 
## # Installing packages --------------------------------------------------
## - Installing fs ...            OK [linked from cache]
## - Installing renv ...          OK [built from source and cached in 8.6s]
## 
## - Project '~/Documents/myproject' loaded. [renv 1.0.11]




  All packages (w/ the good version) listed in the lockfile will be automatically installed in the project library

Recommendations

  Use renv at the end of the project to freeze your packages environment and share the renv.lock


## Initialize renv (at the end of the project) ----
renv::init()

## Update the lockfile (and install missing packages) ----
renv::snapshot()

## Check lockfile status ----
renv::status()


  Do not forget to add renv/ and .Rprofile to the .gitignore

Thanks!