OS-independent way to select directory interactively in R

In the time since posting this question and an earlier version of this answer, I've managed to test the various options that have been suggested on a range of computers. This process has converged on a fairly simple solution. The only cases I have found where tcltk::tk_choose.dir() fails due to conflicts are on Windows computers running Autodesk software. But on Windows, we have utils::choose.dir available instead. So the answer I am currently running with is:

choose_directory = function(caption = 'Select data directory') {
  if (exists('utils::choose.dir')) {
    choose.dir(caption = caption) 
  } else {
    tk_choose.dir(caption = caption)
  }
}

For completeness, I think it is useful to summarise some of the issues with other approaches and why they do not meet the criteria of being generally robust on a variety of platforms (including robustness against potentially unresolved external dependencies that can't be fixed from within R and that that may require administrator privileges and/or expertise to fix):

  1. easycsv::choose_dir in Linux depends on zenity, which may not be available.
  2. rstudioapi::selectDirectory requires that we are in RStudio Version greater than 1.1.287.
  3. rChoiceDialogs::rchoose.dir requires not only that java runtime environment is installed, but also java compiler must be installed and configured correctly to work with rJava.
  4. utils::menu does not work if the R function is run from the command line, rather than in an interactive session. Also on Linux X11 it frequently leaves an orphan window open after execution, which can't be readily closed.
  5. gWidgets2::gfile has external dependency on either gtk2 or tcltk or Qt. Resolving these dependencies was found to be non-trivial in some cases.

Archived earlier version of this answer

Finally, an earlier version of this answer contained some longer code that tries out several possible solutions to find one that works. Although I have settled on the simple version above, I leave this version archived here in case it proves useful to someone else.

What it tries:

  1. Check whether the function utils::choose.dir exists (will only be available on Windows). If so, use that
  2. Check whether the user is working from within RStudio version 1.1.287 or greater. If so use the RStudio API.
  3. Check if we can load the tcltk package and then open and close a tcltk window without throwing an error. If so, use tcltk.
  4. Check whether we can load gWidgets2 and the RGtk2 widgets. If so, use gWidgets2. I don't try to load the tcltk widgets here, because if they worked, presumably we would already be using the tcltk package. I also do not try to load the Qt widgets, as they seem somewhat unmaintained and are not currently available on CRAN.
  5. Check if we can load rJava and rChoiceDialogs. If so, use rChoiceDialogs.
  6. If none of the above are successful, use a fallback position of requesting the directory name at the console.

Here's the longer version of the code:

# First a helper function to load packages, installing them first if necessary
# Returns logical value for whether successful
ensure_library = function (lib.name){
    x = require(lib.name, quietly = TRUE, character.only = TRUE)
    if (!x) {
      install.packages(lib.name, dependencies = TRUE, quiet = TRUE)
      x = require(lib.name, quietly = TRUE, character.only = TRUE)
      }
  x
}

select_directory_method = function() {
  # Tries out a sequence of potential methods for selecting a directory to find one that works 
  # The fallback default method if nothing else works is to get user input from the console
  if (!exists('.dir.method')){  # if we already established the best method, just use that
    # otherwise lets try out some options to find the best one that works here
    if (exists('utils::choose.dir')) {
      .dir.method = 'choose.dir'
    } else if (rstudioapi::isAvailable() & rstudioapi::getVersion() > '1.1.287') {
      .dir.method = 'RStudioAPI'
      ensure_library('rstudioapi')
    } else if(ensure_library('tcltk') & 
              class(try({tt  <- tktoplevel(); tkdestroy(tt)}, silent = TRUE)) != "try-error") {
      .dir.method = 'tcltk'
    } else if (ensure_library('gWidgets2') & ensure_library('RGtk2')) {
      .dir.method = 'gWidgets2RGtk2'
    } else if (ensure_library('rJava') & ensure_library('rChoiceDialogs')) {
      .dir.method = 'rChoiceDialogs'
    } else {
      .dir.method = 'console'
    }
    assign('.dir.method', .dir.method, envir = .GlobalEnv) # remember the chosen method for later
  }
  return(.dir.method)
}

choose_directory = function(method = select_directory_method(), title = 'Select data directory') {
  switch (method,
          'choose.dir' = choose.dir(caption = title),
          'RStudioAPI' = selectDirectory(caption = title),
          'tcltk' = tk_choose.dir(caption = title),
          'rChoiceDialogs' = rchoose.dir(caption = title),
          'gWidgets2RGtk2' = gfile(type = 'selectdir', text = title),
          readline('Please enter directory path: ')
  )
}

you can use the choose_dir function from easycsv.
it works on Windows, Linux and OSX

easycsv::choose_dir() # can be run without parameters to prompt a folder selection window

Here is a simple directory navigation menu (using menu{utils}):

d=1
while(d != 0) {
  a = getwd()
  a = strsplit(a, "/")
  a = unlist(a)
  b = list.dirs(recursive = F, full.names = F)
  c = paste("..", a[length(a) - 1], a[length(a)], sep = "/")
  d = menu(c("..", b), title = c, graphics = T)
  if(d==1){
    e=paste(paste(a[1:(length(a)-1)],collapse = '/',sep = ''),'/',sep = '')
    #print(e)
    setwd(e)
  }else{
    e=paste(paste(a,collapse = '/',sep = ''),'/',b[d-1],sep='')
    #print(e)
    setwd(e)
  }
}

Note: I did not (yet) test it under different systems. Here is what the documentation says:

If graphics = TRUE and a windowing system is available (Windows, macOS or X11 via Tcl/Tk) a listbox widget is used, otherwise a text menu. It is an error to use menu in a non-interactive session.

One limitation: The title = can only be a single line.


for some use cases a little trick might be to use dirname() around file.choose()

dir <- dirname(file.choose())

this will return the directory. It does however require at least one file to be present in the directory

Tags:

R