507

I often find myself writing R scripts that generate a lot of output. I find it cleaner to put this output into its own directory(s). What I've written below will check for the existence of a directory and move into it, or create the directory and then move into it. Is there a better way to approach this?

mainDir <- "c:/path/to/main/dir"
subDir <- "outputDirectory"

if (file.exists(subDir)){
    setwd(file.path(mainDir, subDir))
} else {
    dir.create(file.path(mainDir, subDir))
    setwd(file.path(mainDir, subDir))
    
}
Wael
  • 1,640
  • 1
  • 9
  • 20
Chase
  • 67,710
  • 18
  • 144
  • 161
  • 1
    I'm sure I've seen an R function that creates a temporary directory with a randomly generated name and returns the name. I think there's a similar one that creates a temp file. I can't find them offhand, but the Databel package (http://cran.r-project.org/web/packages/DatABEL/index.html) has a function get_temporary_file_name. – PaulHurleyuk Nov 18 '10 at 19:37
  • 65
    You should never use `setwd()` in R code - it basically defeats the idea of using a working directory because you can no longer easily move your code between computers. – hadley Nov 20 '10 at 23:44
  • 7
    @hadley interesting topic to ponder, I'd appreciate your thoughts on other methods to the same end. At work, all computers are sync'd to the same network so file paths are consistent. If they aren't, we have bigger issues to deal with than portability of a script. In this particular example, I was writing a script that would be loaded on a machine that will be carried around our national parks for 2 years. This script will grab data from a local SQL instance, do some processing, and spit out a .csv. The end product will be a `.bat` file that the end user will never have to modify. – Chase Nov 22 '10 at 04:20
  • @Chase But you don't need to `setwd` to work with network paths. You just need to provide paths to save results and still work with current path (that one that is established when R session started). Or start R with desire working directory. – Marek Nov 29 '10 at 22:17
  • 1
    @Marek - ahh, I see. So you're saying I should replace my calls to `setwd()` with something like `write.table(file = "path/to/output/directory", ...)`? – Chase Nov 29 '10 at 22:18
  • 8
    Yep. Or parametrize `out_dir <- "path/to/output/directory"` and then use `write.table(file = file.path(out_dir,"table_1.csv"), ...)`. Or even `out_file <- function(fnm) file.path("path/to/output/directory", fnm)` and then `write.table(file = out_file("table_1.csv"), ...)` (similar method I use when working with network drives). – Marek Nov 29 '10 at 22:23
  • @Marek - well I learned something new today, time to go home! Thanks for the tips, that is very helpful. – Chase Nov 29 '10 at 22:28
  • @hadley that is good to know. what about having setwd() in the top level, so that when you source the code it will change the working dir, but it's still somewhat portable? IMO the main reason to use `setwd()` is when you're working on a project that needs to know paths, but it's not long term enough to change rstudio's default directory. also, are there any technical problems with using `setwd()` or is it more of a paradigmatic issue? thanks!! – 3pitt Nov 02 '17 at 13:25
  • @PaulHurleyuk, were you looking for `tempdir()` and `tempfile()`? Theoretically `tempdir()` could be used for storing the output, as long as the output isn't needed after the R session is done running. – Josh Apr 03 '19 at 19:06

10 Answers10

505

Use showWarnings = FALSE:

dir.create(file.path(mainDir, subDir), showWarnings = FALSE)
setwd(file.path(mainDir, subDir))

dir.create() does not crash if the directory already exists, it just prints out a warning. So if you can live with seeing warnings, there is no problem with just doing this:

dir.create(file.path(mainDir, subDir))
setwd(file.path(mainDir, subDir))
David Arenburg
  • 91,361
  • 17
  • 137
  • 196
robbrit
  • 17,560
  • 4
  • 48
  • 68
  • 72
    Be aware when using `showWarnings = FALSE` that this will also hide other warnings such as the directory being uncreateable. – zelanix Jan 19 '14 at 22:12
  • 6
    ^ Is there a way to only suppress one specific warning? – Bas Jun 03 '16 at 09:18
  • 3
    Hi, I want ot create nested directory, like if I am in folder test1 then inside it test2 inside it test3 ... but right now I am facing problem. Is there a way that I can create 3 level of directory even if directory1 does not exits ?? – Praveen Kesani Aug 08 '16 at 06:18
  • 12
    @PraveenKesani Is this what you are looking for: `dir.create("test1/test2/test3/", recursive=TRUE)` ? – dean. Aug 30 '16 at 08:22
  • 11
    @Bas Really late response but `suppressWarnings()` will suppress warnings for just that statement. – Ram RS Jun 07 '18 at 20:49
201

As of April 16, 2015, with the release of R 3.2.0 there's a new function called dir.exists(). To use this function and create the directory if it doesn't exist, you can use:

ifelse(!dir.exists(file.path(mainDir, subDir)), dir.create(file.path(mainDir, subDir)), FALSE)

This will return FALSE if the directory already exists or is uncreatable, and TRUE if it didn't exist but was succesfully created.

Note that to simply check if the directory exists you can use

dir.exists(file.path(mainDir, subDir))
Molx
  • 6,816
  • 2
  • 31
  • 47
  • 13
    Just to note it's not good practice to use `ifelse()` for non-vectorised branching. – Lionel Henry Oct 02 '15 at 10:22
  • What's the additional advantage of using `dir.exists()` over `file_test("-d", dir)` proposed by @G Poole? I prefer using the latter because it's backward compatible. – Ehsan88 Jan 04 '16 at 05:27
  • 3
    @Bas because your code falsely reads as if something vectorised is happening. It's like using vectorised `|` instead of scalar `||`. It works but is bad practice. – Lionel Henry Jun 04 '16 at 11:02
  • 2
    Oh damn, so I've been doing my if statements wrong as well by using `|`, is the vectorization the reason it doesn't work with `||` sometimes? I know this is off topic but I'm just too eager to find out. I'll defo go and read more about vectorization. Thanks – Bas Jun 04 '16 at 22:21
  • 5
    So what is the best practice way of doing this if we should avoid `ifelse`? – KillerSnail Aug 18 '16 at 01:29
  • I get a the following error: **cannot create dir path, reason: no such directory**. Isn't it supposed to create on if it doesn't exists??? – TSR Oct 13 '16 at 06:40
  • @TSR Maybe you're trying to created nested directories? You can't create `dir1\dir2\ ` if `dir1` doesn't exist. If that's the case you have to do it step by step. – Molx Oct 13 '16 at 16:02
  • @lionel, how to rewrite this function without `ifelse()`? – Y. Z. Aug 02 '17 at 20:06
  • 9
    using if and else ;) – Lionel Henry Aug 03 '17 at 10:23
  • @TSR @Molx You can use `recursive = TRUE` as an argument to `dir.create` like so: `dir.create("abc/def/ghi", recursive = TRUE)` to create nested directories in one go. – Dannid Mar 07 '19 at 19:31
40

Here's the simple check, and creates the dir if doesn't exists:

## Provide the dir name(i.e sub dir) that you want to create under main dir:
output_dir <- file.path(main_dir, sub_dir)

if (!dir.exists(output_dir)){
dir.create(output_dir)
} else {
    print("Dir already exists!")
}
Surya
  • 11,002
  • 4
  • 57
  • 39
36

One-liner:

if (!dir.exists(output_dir)) {dir.create(output_dir)}

Example:

dateDIR <- as.character(Sys.Date())
outputDIR <- file.path(outD, dateDIR)
if (!dir.exists(outputDIR)) {dir.create(outputDIR)}
den2042
  • 497
  • 4
  • 4
19

In terms of general architecture I would recommend the following structure with regard to directory creation. This will cover most potential issues and any other issues with directory creation will be detected by the dir.create call.

mainDir <- "~"
subDir <- "outputDirectory"

if (file.exists(paste(mainDir, subDir, "/", sep = "/", collapse = "/"))) {
    cat("subDir exists in mainDir and is a directory")
} else if (file.exists(paste(mainDir, subDir, sep = "/", collapse = "/"))) {
    cat("subDir exists in mainDir but is a file")
    # you will probably want to handle this separately
} else {
    cat("subDir does not exist in mainDir - creating")
    dir.create(file.path(mainDir, subDir))
}

if (file.exists(paste(mainDir, subDir, "/", sep = "/", collapse = "/"))) {
    # By this point, the directory either existed or has been successfully created
    setwd(file.path(mainDir, subDir))
} else {
    cat("subDir does not exist")
    # Handle this error as appropriate
}

Also be aware that if ~/foo doesn't exist then a call to dir.create('~/foo/bar') will fail unless you specify recursive = TRUE.

zelanix
  • 3,326
  • 1
  • 25
  • 35
  • 6
    is there a reason you use paste( ... ) vs file.path(mainDir, subDir). Also if you did a path<- file.path(mainDir, subDir) you could reuse it 5 times making the if statements more readable. – MikeF Feb 26 '18 at 15:46
13

I had an issue with R 2.15.3 whereby while trying to create a tree structure recursively on a shared network drive I would get a permission error.

To get around this oddity I manually create the structure;

mkdirs <- function(fp) {
    if(!file.exists(fp)) {
        mkdirs(dirname(fp))
        dir.create(fp)
    }
} 

mkdirs("H:/foo/bar")
user425678
  • 720
  • 7
  • 17
11

The use of file.exists() to test for the existence of the directory is a problem in the original post. If subDir included the name of an existing file (rather than just a path), file.exists() would return TRUE, but the call to setwd() would fail because you can't set the working directory to point at a file.

I would recommend the use of file_test(op="-d", subDir), which will return "TRUE" if subDir is an existing directory, but FALSE if subDir is an existing file or a non-existent file or directory. Similarly, checking for a file can be accomplished with op="-f".

Additionally, as described in another comment, the working directory is part of the R environment and should be controlled by the user, not a script. Scripts should, ideally, not change the R environment. To address this problem, I might use options() to store a globally available directory where I wanted all of my output.

So, consider the following solution, where someUniqueTag is just a programmer-defined prefix for the option name, which makes it unlikely that an option with the same name already exists. (For instance, if you were developing a package called "filer", you might use filer.mainDir and filer.subDir).

The following code would be used to set options that are available for use later in other scripts (thus avoiding the use of setwd() in a script), and to create the folder if necessary:

mainDir = "c:/path/to/main/dir"
subDir = "outputDirectory"

options(someUniqueTag.mainDir = mainDir)
options(someUniqueTag.subDir = "subDir")

if (!file_test("-d", file.path(mainDir, subDir)){
  if(file_test("-f", file.path(mainDir, subDir)) {
    stop("Path can't be created because a file with that name already exists.")
  } else {
    dir.create(file.path(mainDir, subDir))
  }
}

Then, in any subsequent script that needed to manipulate a file in subDir, you might use something like:

mainDir = getOption(someUniqueTag.mainDir)
subDir = getOption(someUniqueTag.subDir)
filename = "fileToBeCreated.txt"
file.create(file.path(mainDir, subDir, filename))

This solution leaves the working directory under the control of the user.

G Poole
  • 111
  • 1
  • 2
8

I know this question was asked a while ago, but in case useful, the here package is really helpful for not having to reference specific file paths and making code more portable. It will automatically define your working directory as the one that your .Rproj file resides in, so the following will often suffice without having to define the file path to your working directory:

library(here)

if (!dir.exists(here(outputDir))) {dir.create(here(outputDir))}

Amy M
  • 967
  • 1
  • 9
  • 19
4

Package hutils (which I authored) has the functions provide.dir(path) and provide.file(path) to check the directories/files at path exist, creating them if they are absent.

Hugh
  • 15,521
  • 12
  • 57
  • 100
3

To find out if a path is a valid directory try:

file.info(cacheDir)[1,"isdir"]

file.info does not care about a slash on the end.

file.exists on Windows will fail for a directory if it ends in a slash, and succeeds without it. So this cannot be used to determine if a path is a directory.

file.exists("R:/data/CCAM/CCAMC160b_echam5_A2-ct-uf.-5t05N.190to240E_level1000/cache/")
[1] FALSE

file.exists("R:/data/CCAM/CCAMC160b_echam5_A2-ct-uf.-5t05N.190to240E_level1000/cache")
[1] TRUE

file.info(cacheDir)["isdir"]
David Arenburg
  • 91,361
  • 17
  • 137
  • 196
  • What is wrong about this answer (besided not including the `dir.create()` part)? Are the statements wrong or just considered not helpful to solve the question at hand? – mschilli Apr 02 '15 at 08:44