.First function in R - r

I don't understand the point of .First function in R. My reason is any code in .Rprofile will be sourced and executed when R starts up anyway.
this
.First<-function(){
library('devtools')
}
and this
library('devtools')
in .Rprofile have exactly the same effect.
However, here is an example that shows .First can make a difference:
example 1, you can see X11.options()$type correctly becomes Xlib as set in .Rprofile
>> cat .Rprofile
.First <- function() {
library(devtools)
}
setHook(
packageEvent("grDevices", "onLoad"),
function(...) grDevices::X11.options(type="Xlib")
)
>> Rscript -e 'X11.options()$type'
[1] "Xlib"
example 2, you can see X11.options()$type is still cairo, the setHook in .Rprofile didn't take effect
>> cat .Rprofile
library(devtools)
setHook(
packageEvent("grDevices", "onLoad"),
function(...) grDevices::X11.options(type="Xlib")
)
>> Rscript -e 'X11.options()$type'
[1] "cairo"
in what case do I absolutely have to use .First function?
why .First made a difference in the example above?
Thanks!

It may be unnecessary but it does provide yet another place to modify the startup. It certainly doesn't hurt having it.
I generally run R in different directories to keep things separated; link to a common .Rprofile; and use .First to tailor the current R run environment to the specific problem I'm working on. If .First action wasn't available I'd have to create one.

One benefit of putting startup code in .First() instead of .RProfile is that you can use local variables which won't stay in your Global environment after .First() completes.
For example, my .First() displays the list of .R files on the project directory in as many columns as will fit:
localFiles <- list.files(pattern = "\\.R$", ignore.case = TRUE)
maxChars <- max(nchar(localFiles))
numCols <- as.integer((options("width")$width-2) / (1 + maxChars)) # how many columns will fit?
fmt <- sprint(" %%-%d", maxChars) # left justified in each column
for (nn in localFiles) {
if ((match(nn, localFiles) %% numCols) == 1) cat(" ") # indent each row
cat(sprint(fmt, nn))
if ((match(nn, localFiles) %% numCols) == 0) cat("\n") # end of row
}
if (length(localFiles) %% numCols != 0) cat("\n") # end last row if not complete
Since this is in .First(), all of the temporary variables get cleaned up when the function returns and the Global environment remains clean.

R sources Rprofile.site then either project-level or user-level .Rprofile and then evaluates .First (Rstudio Support).
.First is useful to include in Rprofile.site if you want to trigger an action (site-wide) that is conditional on user or project .Rprofile.
Example: renv is activated by project-level .Rprofile.
This code will print a message indicating the location of the renv cache or will alert the user that renv hasn't been activated.
if (interactive()) {
.First <- function() {
if ("RENV_PROJECT" %in% names(Sys.getenv())) {
cat("\nLinked to renv cache:\n")
cat("-", renv::paths$cache(), "\n\n")
} else {
cat("\n*** Warning: you are not in an active renv project! ***\n")
cat("- Some functions may be unavailable.\n\n")
}
}
}

Related

Use Rcpp function in foreach %dopar%

I have created an Rcpp test package called "test" using the Rcpp package skeleton to try to run c++ code in parallel but keep running into errors. I'm running R 4.1.2 on Mac OS and have updated all parallel computing packages. I added to the package skeleton an R script containing
# wrap c++ function in R function
test_func <- function()
{
return(rcpp_hello_world())
}
# attempt to parallelize
parallelize <- function()
{
# create cluster
cl <- parallel::makeCluster(parallel::detectCores() - 1)
parallel::clusterExport(cl,varlist = c("test_func","rcpp_hello_world"),envir = environment())
doParallel::registerDoParallel(cl)
# call test_func in parallel
res <- foreach::`%dopar%`(foreach::foreach(i = 1:5,.combine = c),ex = {test_func()})
# clean up
parallel::stopCluster(cl)
return(res)
}
I loaded my package using devtools::load_all(), but typing parallelize() in my console I get the error "Error in { : task 1 failed - "object '_test_rcpp_hello_world' not found" ". When I add "_test_rcpp_hello_world" to the clusterExport call I get the error "Error in { : task 1 failed - "NULL value passed as symbol address" ".
Everything works fine when I switch %dopar% to %do%, but I'm hoping to be able to still parallelize.
I know that similar questions have been asked here, but I can't use a solution which calls sourceCpp on each worker (the c++ code in my actual R package is huge and this operation would defeat the purpose of parallelizing).
Any help would be greatly appreciated!!
(Continuing from the comments)
The key is that to execute 'local' code on a node, you cannot send a (compiled) function to the node. The node needs to have it, and the best way it to have the node(s) have access to the same package(s), load them and thus be ready to run code using them. I just glanced at some old slide decks from presentations I gave and I didn't find an perfect example -- but a pointer to a (thirteen-plus (!!) year old) directory of example scripts including this for running (cpu-wise expensive) DieHarder tests on nodes via Rmpi:
#!/usr/bin/env r
suppressMessages(library(Rmpi))
suppressMessages(library(snow))
cl <- NULL
mpirank <- mpi.comm.rank(0)
if (mpirank == 0) {
cl <- makeMPIcluster()
} else { # or are we a slave?
sink(file="/dev/null")
slaveLoop(makeMPImaster())
mpi.finalize()
q()
}
clusterEvalQ(cl, library(RDieHarder))
res <- parLapply(cl, c("mt19937","mt19937_1999",
"mt19937_1998", "R_mersenne_twister"),
function(x) {
dieharder(rng=x, test="operm5",
psamples=100, seed=12345)
})
stopCluster(cl)
print( do.call(rbind, lapply(res, function(x) { x[[1]] } )))
mpi.quit()
The key is in the middle: clusterEvalQ(cl, library(RDieHarder)) All worker nodes are asked to load the RDieHarder package. Conceptually, you want to do the same here, and the foreach family lets you do it too.

Can R packages add code snippets to users' snippet files?

There are several code snippets that are invaluable to my workflow and play nicely with functions in my custom R package. Can I include these code snippets in my R package so that they are added to users' code snippets (with permissions of course) when they install my package?
Rmd snippet example that creates a sql chunk:
snippet sql
```{sql, connection = conn, output.var = "${1:df}"}
${2}
```
Short answer: Yes
One way to achieve what you want (that works for my package) is:
Store the packages snippet definitions in two text files somewhere in the packages inst/ directory. It's important that the snippets follow exactly the formatting rules (e.g. tabs at the start of the lines, not spaces). I have one file for R code snippets and one for markdown.
Define a function that reads these files and copies their content into RStudios user snippets files. These files are generated at the first attempt to edit the snippets (Tools -> Global Options -> Code -> Edit Snippets) (I think RStudio uses an other, not user exposed file before one tries to edit, not sure though). On ubuntu the RStudio files are called 'r.snippets' and 'markdown.snippets' and are in '~/.R/snippets/'. I also check if the snipped definition already exists, and double check the tabs at the start of the lines before using cat(..., append=TRUE) to add the packages snippet definitions.
I first used an elaborate .onLoad function with configs and all but now I just export a addPackageSnippets function ;)
Edit
Some code:
Part that checks for already existing snippet definitons:
I just read the rstudio file and extract the lines starting with 'snippet'. I do the same for the packages snipptes definition file and use setdiff (one might want to also use trimws on the lists, just in case there is some trailing white-space)
# load package snippets definitions
#
pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)
# Extract names of package snippets
#
pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]
# Extract 'names' of already existing snitppets
#
rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]
# find definitions appearing in packageSnippets but not in rstudioSnippets
# if no snippets are missing go to next file
#
snippetsToCopy <- setdiff(pckgSnippetsFileDefinitions, rstudioSnippetDefinitions)
For context here is the whole 'addPackageSnippets' function. The function is using only the base package, except getOS which returns one of 'linux', 'windows' or 'mac' (i.e. a wrapper around Sys.info()
#' #title Export snippets
#'
#' #description \code{addPackageSnippets} copies all (missing) snippet definitions
#' in 'inst/rstudio/Rsnippets.txt' and 'Rmdsnippets.txt' to the RStudios user snippet location.
#'
#' #return boolean invisible(FALSE) if nothing was added, invisible(TRUE) if snipped definitions were added
#' #export
#'
#' #examples \dontrun{addPackageSnippets()}
addPackageSnippets <- function() {
added <- FALSE
# if not on RStudio or RStudioServer exit
#
if (!nzchar(Sys.getenv("RSTUDIO_USER_IDENTITY"))) {
return(NULL)
}
# Name of files containing snippet code to copy
#
pckgSnippetsFiles <- c("Rsnippets.txt", "Rmdsnippets.txt")
# Name of files to copy into. Order has to be the same
# as in 'pckgSnippetsFiles'
#
rstudioSnippetsFiles <- c("r.snippets", "markdown.snippets")
# Path to directory for RStudios user files depends on OS
#
if (getOS() == "linux") {
rstudioSnippetsPathBase <- "~/.R/snippets"
} else if (getOS() == "windows") {
rstudioSnippetsPathBase <- file.path(path.expand('~'), ".R", "snippets")
} else {
warning(paste0("goSnippets() is only implemented on linux and windows"))
return(NULL)
}
# Read each file in pckgSnippetsFiles and add its contents
#
for (i in seq_along(pckgSnippetsFiles)) {
# Try to get template, if template is not found skip it
#
pckgSnippetsFilesPath <- system.file("rstudio", pckgSnippetsFiles[i], package = "myFunc")
if (pckgSnippetsFilesPath == "") {
next()
}
# load package snippets definitions
#
pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)
# Extract names of package snippets
#
pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]
# Construct path for destination file
#
rstudioSnippetsFilePath <- file.path(rstudioSnippetsPathBase, rstudioSnippetsFiles[i])
# If targeted RStudios user file does not exist, raise error (otherwise we would 'remove')
# the default snippets from the 'user file'
#
if (!file.exists(rstudioSnippetsFilePath)) {
stop(paste0( "'", rstudioSnippetsFilePath, "' does not exist yet\n.",
"Use RStudio -> Tools -> Global Options -> Code -> Edit Snippets\n",
"To initalize user defined snippets file by adding dummy snippet\n"))
}
# Extract 'names' of already existing snitppets
#
rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]
# replace two spaces with tab, ONLY at beginning of string
#
pckgSnippetsFileContentSanitized <- gsub("(?:^ {2})|\\G {2}|\\G\t", "\t", pckgSnippetsFileContent, perl = TRUE)
# find defintions appearing in packageSnippets but not in rstudioSnippets
# if no snippets are missing go to next file
#
snippetsToCopy <- setdiff(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
snippetsNotToCopy <- intersect(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
if (length(snippetsToCopy) == 0) {
# cat(paste0("(\nFollowing snippets will NOT be added because there is already a snippet with that name: ",
# paste0(snippetsNotToCopy, collapse=", ") ,")"))
next()
}
# Inform user about changes, ask to confirm action
#
if (interactive()) {
cat(paste0("You are about to add the following ", length(snippetsToCopy),
" snippets to '", rstudioSnippetsFilePath, "':\n",
paste0(paste0("-", snippetsToCopy), collapse="\n")))
if (length(snippetsNotToCopy) > 0) {
cat(paste0("\n(The following snippets will NOT be added because there is already a snippet with that name:\n",
paste0(snippetsNotToCopy, collapse=", ") ,")"))
}
answer <- readline(prompt="Do you want to procedd (y/n): ")
if (substr(answer, 1, 1) == "n") {
next()
}
}
# Create list of line numbers where snippet definitons start
# This list is used to determine the end of each definition block
#
allPckgSnippetDefinitonStarts <- grep("^snippet .*", pckgSnippetsFileContentSanitized)
for (s in snippetsToCopy) {
startLine <- grep(paste0("^", s, ".*"), pckgSnippetsFileContentSanitized)
# Find last line of snippet definition:
# First find start of next defintion and return
# previous line number or lastline if already in last definiton
#
endLine <- allPckgSnippetDefinitonStarts[allPckgSnippetDefinitonStarts > startLine][1] -1
if (is.na(endLine)) {
endLine <- length(pckgSnippetsFileContentSanitized)
}
snippetText <- paste0(pckgSnippetsFileContentSanitized[startLine:endLine], collapse = "\n")
# Make sure there is at least one empty line between entries
#
if (tail(readLines(rstudioSnippetsFilePath), n=1) != "") {
snippetText <- paste0("\n", snippetText)
}
# Append snippet block, print message
#
cat(paste0(snippetText, "\n"), file = rstudioSnippetsFilePath, append = TRUE)
cat(paste0("* Added '", s, "' to '", rstudioSnippetsFilePath, "'\n"))
added <- TRUE
}
}
if (added) {
cat("Restart RStudio to use new snippets")
}
return(invisible(added))
}
For anyone who comes across this thread, and to add to Dario's great answer: from RStudio v1.3, the filepaths have changes. So in his function, the section for setting rstudioSnippetsPathBase would need to change into something like the following
if (rstudioapi::versionInfo()$version < "1.3") {
rstudioSnippetsPathBase <- file.path(path.expand('~'),".R", "snippets")
} else {
if (.Platform$OS.type == "windows") {
rstudioSnippetsPathBase <- file.path(Sys.getenv("APPDATA"), "RStudio", "snippets")
} else {
rstudioSnippetsPathBase <- file.path(path.expand('~'), ".config/rstudio", "snippets")
}
}

Adding additional documentation to a package in R

Aside from a vignette, I wish to add an additional document as PDF to my package. I can, of course, copy it to the inst/doc directory and it will then be included in the package documentation.
However, I would like to make it easy for the user to display this file. The authors of the edgeR package decided to do the following: the main users manual is distributed as PDF (and is not a regular vignette), and the authors include a function called edgeRUsersGuide() which shows the PDF by means of the following code:
edgeRUsersGuide <- function (view = TRUE) {
f <- system.file("doc", "edgeRUsersGuide.pdf", package = "edgeR")
if (view) {
if (.Platform$OS.type == "windows")
shell.exec(f)
else system(paste(Sys.getenv("R_PDFVIEWER"), f, "&"))
}
return(f)
}
It appears to work. Do you think it is a reasonable approach?
Or should one use something else? Potentially, the following code would also work and be more robust:
z <- list(PDF="edgeR.pdf", Dir=system.file(package="edgeR"))
class(z) <- "vignette"
return(z)
My solution was to ape the code in utils:::print.vignette():
function(docfile) {
## code inspired by tools:::print.vignette
pdfviewer <- getOption("pdfviewer")
f <- system.file("doc", docfile, package = "tmod")
if(identical(pdfviewer, "false"))
stop(sprintf("Cannot display the file %s", f))
if (.Platform$OS.type == "windows" &&
identical(pdfviewer, file.path(R.home("bin"), "open.exe"))) {
shell.exec(f)
} else {
system2(pdfviewer, shQuote(f), wait = FALSE)
}
return(invisible(f))
}

Suppress messages in markdown, but not in R console

I currently use the following header:
```{r, message=FALSE}
foo <- function(x) message(x)
for(i in 1:10) foo(i)
```
Inside this code chunk, there is a loop over simulated scenarios, with message() function that prints status of currently executed scenario.
I would like to suppress those messages from display in RStudio and final HTML output, but I still want to control the simulation progress and see the message() output in console. Is this achievable? Maybe with other arguments/functions?
You can write/append status to a file (this is a workaround solution, there should be a more direct answer).
For example:
file <- file("status.txt", open = "wt")
sink(file, type = "message")
message("all good")
In this example message won't be displayed - it'll be written to a status.txt file.
In you're using specific function and iterating over a set you can try this example:
foo <- function(x) {
message(x)
}
file <- file("status.txt", open = "wt")
sink(file, type = "message")
for(i in 1:3) {
foo(i)
}
Function foo should return (message) value, however it appends it to a status.txt file.
You can track changes in status.txt file using bash tail command with -f argument. First send R to background and then use tail -f status.txt in your console.
One approach would be to put this in the start of your file.
mymessage <- function (text) {
if(knitr::opts_knit$get('out.format') != NULL) message(text)
}
There are various ways to know if you are within knitr, recent versions have knitr::is_latex_output and similar.

Unable to edit R function

I am trying to edit and save a function from an R package. So far I have tried
my_edited_func <- edit(package_func)
my_edited_func <- package_func
fix(my_edited_func)
In both cases, a text editor opens up, where I can make changes, but when exiting with :wq, I get the following error:
Error in .External2(C_edit, name, file, title, editor) :
problem with running editor vi
I am using R 3.3.1 on OS X 10.11
Fixing the Editor
We're going to modify the R_HOME/etc/Rprofile.site file to change default editors from vi to vim:
Rscript -e "R.home()"
You probably will get:
[1] "/Library/Frameworks/R.framework/Resources"
Then use:
vim /Library/Frameworks/R.framework/Resources/etc/Rprofile.site
Find:
options(editor="vi")
And switch it to:
options(editor="/usr/bin/vim")
Misc notes
To edit a function simply do:
my_edited_func = edit(package_func)
From now on, call my_edited_func().
In RStudio:
In Terminal:
Though, for more control (and more effective saves) note the following...
Free Function Info
You can grab the function source by just typing the function name:
e.g.
Declaring trash
trash = function(x = TRUE){
!x
}
Calling:
trash
Output:
function(x = TRUE){
!x
}
Grabbing the source here and making a slight change is then possible, e.g.:
trash2 = function(x = TRUE){
x
}

Resources