How to deal with different versions of R?
grepinsight opened this issue · comments
Thank you for writing renv
! I am excited to use it to make my code more future-proof and reproducible.
renv
solves package management; my question is what is the best practice for handling different R versions?
For example, on my local computer, I have 3.5.2
, but on a remote computer, I have 3.2.3
, and I get the following message when trying to restore a project built from the local machine.
Project requested R version '3.5.2' but '3.2.3' is currently being used
In python, there are pyenv
or conda
which allows one to use multiple python versions in an isolated environment.
Is there an equivalent system that allows multiple versions of R?
If not, what's the current recommendation?
Currently, there isn't any explicit provisioning for different R versions in renv
.
Note that currently, different R versions will indeed get their own separate R libraries by default, but they will all ultimately write to the same renv.lock
lockfile on snapshot.
Ultimately though, it's just a warning -- renv
will continue to function as normal, but it's possible that the packages recorded in your lockfile will not work with an older version of R.
Can you elaborate a bit more on why you want to support different versions of R -- e.g. why are you using R 3.2.3 on your remote machine, as opposed to something newer?
Thank you for your answer @kevinushey
Can you elaborate a bit more on why you want to support different versions of R -- e.g. why are you using R 3.2.3 on your remote machine, as opposed to something newer?
This is just mainly due to my laziness. Installing R for mac is super easy so I tend to try a newer version of R (granted, I am behind now that there is 3.6.2), whereas the remote machine is Linux and I usually install R via source often because I don't have root access.
Even if I have root access, installing a particular version of R on, say, Ubuntu is not trivial. I wish there is an easier mechanism for installing different versions of R and switching back and forth among those different versions.
My ultimate use case is to 1) enable fast/efficient development and 2) deploy the R code in a production setting.
Would you say rocker
+ renv
would be the best solution for such use cases?
Would you say rocker + renv would be the best solution for such use cases?
I think that's the cleanest way forward, and it also protects you from issues that might arise if, say, system libraries were updated in a way that was incompatible with one or more R packages you use in your project. (Rare, but it does happen!)
The renv
Docker vignette may be useful: https://rstudio.github.io/renv/articles/docker.html
Another alternative if you want something lighter weight than Rocker (which assumes the whole compute stack will be on Docker) is to use:
https://github.com/rstudio/r-builds
These R binaries:
- Install really fast on Ubuntu (from a .deb file) so you don't have to compile from source
- Are designed to support multiple versions of R being installed side-by-side, so you can switch seamlessly between R versions
Another alternative if you want something lighter weight than Rocker (which assumes the whole >compute stack will be on Docker) is to use:
https://github.com/rstudio/r-builds
@slopp Oh wow! thank you so much. This solves one of the major pain points I've had for years...
Now for a colleague to reproduce my analysis, I can just give R_VERSION
and a renv
cache tarball (and a bit of instruction) for him/her to quickly replicate my R environment!! this is super.
The renv Docker vignette may be useful: https://rstudio.github.io/renv/articles/docker.html
@kevinushey thank you for the recommendation. I think docker is a way to go if more rigor/reproducibility is required.
Related #253
Closing this since the issue raised can be solved by @slopp and @kevinushey 's recommendations. Thank you all!
Hi @kevinushey and @grepinsight, please re-open this issue.
I can give you a perfect example of why you might want to support multiple versions of R. About a year ago I upgraded R from 3.4.4 to 3.5 (finally). And immediately, my projects started breaking. After digging in, I discovered that one of my package was no longer supported in the new version of R (and almost all of my research depended on it!).
I think a tool like pyenv
along with renv
would be a fantastic way to manage this sort of situation.
And as it turns out, there is another Renv project on github that does exactly what pyenv
does... but for R! This project not been updated in 8 years. So there might be an opportunity to fork the code (or take the project over entirely) and merge its capabilities with renv
.
There's a lot that goes into a feature like this, though:
- Does
renv
need to get into the business of managing R installations to make this seamless? - How do you tell
renv
where a user's set of R installations live? - How does
renv
store the version of R to use? Do we also store the path to R? What about issues with portability (for collaborating withrenv
)? - How does a user "activate" a particular version of R? How does this integrate with various front-ends (RStudio, ESS, and so on)?
Getting this right requires a lot of work and hence is why we haven't taken it on yet.
I find pyenv
+ pipenv
an extremely useful combination when working with Python projects.
I guess renv
would be the R alternative of pipenv
, however, I could not find any alternative to pyenv
for managing different versions of R.
So far, the best alternative I found was installing several pre-compiled R versions, but, this requires being root user.
So, I forked the pyenv
project and did the necessary tweaks to allow it to work with R versions. I didn't have idea that someone tried this same approach some years ago, as @viking did 😂.
This work leads to the renv-installer
project, to install it, simply clone the repo, and it will allow installing in one command, a selection of 33 different R versions. Switching between R versions results extremely easy.
A simple example, in Ubuntu 20.04 shell would be:
# Install it
$ git clone https://github.com/jcrodriguez1989/renv-installer.git ~/.renv
$ echo 'export RENV_ROOT="$HOME/.renv"' >> ~/.bashrc
$ echo 'export PATH="$RENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo -e 'if command -v renv 1>/dev/null 2>&1; then\n eval "$(renv init -)"\nfi' >> ~/.bashrc
$ exec "$SHELL"
# Install user-locally R 3.0.0
$ renv install 3.0.0
# Set it to use it when R is called in this folder
$ renv local 3.0.0
$ R
I guess that renv-installer
would be great in combination with renv
, so I developed the bash renv sync
command. When renv sync
is executed in a folder that contains an renv.lock
file, it will check if the required R version is installed by renv-installer
, if so, it will set it as the current R to run in this folder, install renv
package, and do renv::restore()
.
Please let me know if you have any doubts, comments, or suggestions about renv-installer
.
Hi Kevin/Juan - thank you for your replies. I went ahead and installed Renv
and it turns out that it works fine and has no issue working hand-in-hand with renv
for package management. My setup is as follows:
First, install Renv
(I am on a mac)
# Install Renv to home folder
cd ~/
git clone git://github.com/viking/Renv.git .Renv
# Add ~/.Renv/bin to $PATH
echo 'export PATH="$HOME/.Renv/bin:$PATH"' >> ~/.bash_profile
# Add Renv init to your shell to enable shims and autocompletion
echo 'eval "$(Renv init -)"' >> ~/.bash_profile
# Restart shell
exec $SHELL
Install R 3.6.3_1 into Renv
using homebrew
(mac only)
# Install r-latest
brew install r
brew unlink r
# Install r-3.4.4 from homebrew
# brew install https://raw.githubusercontent.com/jeroen/autobrew-core/94655a45637f90a408770c6374066fa5a76fb5ac/Formula/r.rb
brew unlink r
# Soft link R versions into Renv
ln -s /usr/local/Cellar/r/3.6.3_1 ~/.Renv/versions/3.6.3_1
# Rebuild shim binaries (run this from Terminal)
Renv rehash
Repeat the above procedure for each version of R
that you want to install. Then set your global (or local) version of R
and install renv
for that version:
# Set the global R version
Renv global 3.6.3_1
Renv version
# Install renv
if (!requireNamespace("remotes")) {
install.packages("remotes")
}
Select the local mirror to install from. Then continue:
remotes::install_github("rstudio/renv")
Tell renv
where to find your packages cache:
touch ~/.Renviron
mkdir ~/Code/R_Packages
echo 'RENV_PATHS_CACHE = ~/Code/R_Packages/renv' >> ~/.Renviron
Tell R
where to find its system packages. You only need to do this once and the %v
token will automatically set the right folder for each version of R
echo "R_LIBS_USER='~/Code/R_Packages/system/%v'" >> ~/.Renviron
Now load up R
and create your first project:
setwd("/path/to/project")
renv::init()
And if you want to auto-load your project whenever you are in the project's working directory, run the following shell command:
echo 'renv::activate("/path/to/project")' >> /path/to/project/.Rprofile
So all the pieces are there. The only thing I would change is to wrap the above shell commands into a script that could auto-install different versions of R
into Renv
and I would do the same way pyenv
does, by adding the command:
Renv install 3.6.3_1 # or whichever version you want
For anyone else looking at this issue, especially on Mac, you might want to give https://asdf-vm.com/ a go. I've been using it to manage several separate R versions side-by-side, and you can switch from one to the other within a single terminal (as compared to something like https://github.com/hrbrmstr/RSwitch which switches the whole system across)
I want to bring up another use-case for not directly setting the "Version" attribute in the renv.lock file.
We use different R minor versions at work (some people are using R 4.0.2 from MRAN for the Intel MKL library, some people prefer to stay at the most up to date 4.0.3). Now, even if people use different minor versions of R, they will update the renv.lock file constantly when they use renv to install packages. This breaks any workflow with git since people keep overwriting their lock files.
I think the point that different R versions use different libraries and therefore need other renv libraries, is perfectly fine. But this is not the case for minor versions - and it would help us a lot if there was at least an option to set "Version": "4.0.x"
.
Thanks -- I've added the r.version
project setting in 7267c20, to allow projects to set a specific version of R to be encoded in the lockfile (regardless of the one currently in use).
Incredible. That was fast. Thank you so much!
Since this discussion led me here ... here's my attempted hack at a macOS centric solution on the CLI. Involves creating a .Rversion
file, kinda like using nvm and creating a .nvmrc
file, and adding the x.y
version to it, e.g. echo "4.0" > .Rversion
. Ran via R
like normal & rstudio
in Bash/Zsh. Rscript function can be made for Rscript
using the R function, by replacing with /usr/local/bin/Rscript
.
Another Edit:
Just go here if you're interested. No switching of the system's global version of R for R
& Rscript
calls. Just the good ol' R_HOME
variable.
Edit: made some edits since yesterday, compatible with both Zsh & Bash now.
For R:
function R() {
rpath=/Library/Frameworks/R.framework/Versions
old=$(readlink "$rpath"/Current);
if [ -f .Rversion ]; then
new=$(cat .Rversion)
if [[ $(/bin/ls "$rpath" | grep "$new") == "" ]]; then
echo "R version" "$new" "not installed" && return 1; else
ln -sfh "$new" "$rpath"/Current; fi; fi
if [ -n "$ZSH_VERSION" ]; then setopt local_options no_monitor; fi
if [ -n "$BASH_VERSION" ]; then set +m; fi;
({ sleep 1; ln -sfh "$old" "$rpath"/Current; if [ -n "$BASH_VERSION" ]; then set -m; fi } &)
/usr/local/bin/R "$@"
}
For RStudio:
function rstudio() {
rpath=/Library/Frameworks/R.framework/Versions
old=$(readlink "$rpath"/Current);
if [ -f .Rversion ]; then
new=$(cat .Rversion)
if [[ $(/bin/ls "$rpath" | grep "$new") == "" ]]; then
echo "R version" "$new" "not installed" && return 1; else
ln -sfh "$new" "$rpath"/Current; fi; fi
if [ -n "$ZSH_VERSION" ]; then unsetopt local_options nomatch; fi
if [ -f *.Rproj ]; then
open -na Rstudio *.Rproj; else
("/Applications/RStudio.app/Contents/MacOS/RStudio" &)
fi; ({ sleep 5; ln -sfh "$old" "$rpath"/Current;} &)
}