Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices to alert users of package vignettes when `library(packagename)` is loaded? [closed]

I'm writing an R package and spend lots of time on the documentation vignettes. I'm wondering if i should add something like

.onAttach <- function( libname , pkgname ){
   packageStartupMessage("use `browseVignettes('packagename')` to see vignettes")
}

that will show up immediately when a user calls

library(packagename)

What is the best way to inform users about package vignettes? Or is it just assumed that users will look for them without the explicit notification?

like image 405
Anthony Damico Avatar asked May 28 '16 04:05

Anthony Damico


1 Answers

Ubiquity of Vignettes

Few things to keep in mind about vignettes in the eyes of an everyday R user...

  1. Available on the CRAN package page (it's a great quality check on the package if the authors have one!)
  2. Added to the package binaries and show up on the vignette help section via browseVignettes(package = "pkgname") [as you have pointed out]
  3. Embedded within the R package source
  4. Possibility to use embedded code in vignette as a separate script demo in /demos via demo()
  5. The most important part of all: None of the users really know about vignettes at all.

Thus, if you spend a considerable amount of time on the package documentation, you probably should at least indicate on startup such a feature exists.

Exploiting packageStartupMessage() within .onAttach() for the users benefit.

On package load, the console is relatively clear and the user will definitely see the red text as the contrast is relatively high between normal blue R text (assuming no crayon). However, there are a few cases where it does not make sense to alert the user to the presence of vignettes.

  1. If the session is non-interactive (e.g. bash script).
  2. If the user is an R 'pro'
  3. If the user has been using your package for a long, long, long time leading the user to disregard messages.

Thus, there is negative to a poorly implemented packageStartupMessage().

As a result, I would recommend introducing the startup message under four different conditions.

  1. (All) Check to see if a human is present by querying interactive(). If so, then proceed:
  2. (Standard) Have a list of at least 3 different startup messages and randomly select one per startup to display;
  3. (Normal) Randomly generate a number to determine if a start up message should be displayed;
  4. (Pro) Count package loads, after x package loads kill the startup message until the user reinstalls the package.

I'll present two solutions next that follows the tenet of (1) - (3) and then just (1) and (4) for simplicity sake.

Randomly Selecting Package Hints

Within this version we seek to simply check for humans, provide a break from adding a package load message, and randomly picking one hint to display. Only one dependency is needed outside of the scope of base, the stats::runif command to generate a probability between [0,1].

#' @importFrom stats runif
.onAttach <- function(...) {

  # If interactive, hide message
  # o.w. check against rng seed.
  if (!interactive() || stats::runif(1) > 0.5){
     return()
  }

  # Create a list of helpful tips
  pkg_hints = c(
    "Check for updates and report bugs at https://cran.r-project.org/web/packages/pkgname/.",
    "Use `suppressPackageStartupMessages()` to remove package startup messages.",
    "To see the user guides use `browseVignettes('pkgname')`"
  )

  # Randomly pick one hint
  startup_hint = sample(pkg_hints, 1)

  # Display hint
  packageStartupMessage(paste(strwrap(startup_hint), collapse = "\n"))
}

Counting Package Loads

To use a counter, we make use of the ability to save into the package's install directory and not the "working" directory / user space. This has a few problems associated with it that I'll briefly mention:

  1. The users might only have the rights to view but not modify the system library (user libraries are okay).
    • Thus, they will always receive package hints under this method as they cannot increment the counter.
  2. The counter might be shared if the package is in a system library.
    • This would cause all the hints to be used quickly.
  3. Error logs may increase in size.

Of course, you could create your own folder within the user space via R_USER or HOME environment variables to negate these problems. (Exercise left to the reader, hint: Use Sys.getenv() and dir.create().)

Regardless, one of the beauties of this function is at a later time you could probably include a "send package usage statistics" function within the package. This would actual give a reasonably accurate - phone home - statistic vs. the current RStudio CRAN mirror package download info. But, I digress.

For this method to work, we need to do a bit more prep work on the initial package supplied to CRAN. Specifically, we should probably pre-setup a counter via:

# Set working directory to package
setwd("package_dir")

# Create the inst directory if it does not exist
if(!dir.exists("inst")){
  dir.create("inst")
}

# Create a counter variable
pkg_count = 0

# Save it into a .rda file
save(pkg_count, file="inst/counter.rda")

And now, onto the .onAttach() counter implementation!

.onAttach <- function(...){
  if (!interactive()) return()

  # Get the install directory location of the counter
  path_count = system.file("counter.rda", package = "pkgname")

  # Suppress messages by default
  displayMsg = FALSE

  # Are we able to load the counter?
  a = tryCatch(load(path_count), error = function(e){"error"}, warning = function(e){"warning"})

  # Set error variable
  count_error = a %in% c("error","warning")

  # Check if the count is loaded
  if(!count_error){

    # Check if load count is low... 
    if(pkg_count < 10){
      pkg_count = pkg_count + 1

      # Try to save out
      try(save(pkg_count, file = path_count), silent=TRUE)
      displayMsg = T
    }
  }

  # Display message (can be suppressed with suppressPackageStartupMessages)
  if(displayMsg){
    packageStartupMessage("use `browseVignettes('packagename')` to see vignettes")
  }
}

A last thought

Please keep in mind that if you have any package dependencies (e.g. Depend: in DESCRIPTION), they may have their own set of startup messages that will be displayed before the ones written in your package.

like image 52
coatless Avatar answered Oct 27 '22 03:10

coatless