1  Function conflicts

Function conflicts occur when you attach (via ’library()`) multiple packages and these packages contain functions that share identical names. When you do this, the functions that are attached later will “mask” the functions with the same name that are loaded earlier. This means that if you call the function in your script (without its namespace), R will excute the code associated with the latter function. This can cause errors if you are not aware of this conflict and subsequent masking. R does tell you about these conflicts and associated masking when you load packages but they are only warnings and they are easy to miss. You should think carefully about situations where function conflicts exist across your attached packages (and base R packages) and decide intentionly how to resolve them (or prevent them from happening in the first place). In the next sections, we describe what we believe to be best practices to handle these situations.

1.1 Minmize loading of full packages

Function conflicts can be minimized by limiting the number of packages that you attach in your scripts. For our work, we will almost always use library(tidyverse) and frequently use library(tidymodels) next. You should carefully consider if you need to attach any other packages. You very well may not!

You should only attach full packages if you intend to use multiple functions from that package. If instead, you only need a single function (or several), there are two alternatives that are preferred over attaching the full package with library().

Option 1: Use the namespace of the function when calling it in your script. For example, if I need to simulate multivariate normal data, I might want to use the mvrnorm() function from the MASS package. I do NOT need to use library(MASS) to use this function. Instead, I can simply call the function with its namespace MASS::mvrnorm(). This will avoid conflicts between other functions in MASS and your other attached packages (e.g., select() in MASS conflicts with select() in dplyr/tidyverse).

Option 2: If you find using the namespace of the function cumbersome, you can attach a single function from a package rather than the full package. For example, if we wanted to use only mvrnorm() from MASS, we could use this code: library(MASS, include.only = "mvrnorm). Now you can call mvrnorm() without pre-pending its namespace (MASS::). You can pass a character vector containing multiple function names rather than a single function to include.only if you intend to use several functions from the package (e.g., library(MASS, include.only = c("mvrnorm", "lda"))).

1.2 Base R conflict managemnt

As of version 3.6, R now includes all the necessary tools (in our opinion) to handle and clearly resolve function conflicts. These tools are well-documented and should be reviewed to better understand how to use them.

For our purposes, it is generally sufficient to use one of the two named conflict policies that are included (depends.ok or strict). We prefer the use of the depends.ok policy.

To implement the depends.ok policy, simply set this option at the top of your script using options(conflicts.policy = "depends.ok"). You can now combine this option with limited use of library() for important packages and the use of either namespace or include.only methods described above and you should be good to go (with one exception noted below).

To get a better sense of what the depends.ok policy does, it is a shortcut to implement the following set of conflict options.

options(conflicts.policy =
            list(error = TRUE,
                 generics.ok = TRUE,
                 can.mask = c("base", "methods", "utils",
                              "grDevices", "graphics",
                              "stats"),
                 depends.ok = TRUE))

This means that packages that you attach with library will produce an error if their functions conflict with previously loaded packages (error = TRUE).

However, errors will not occur if the functions conflict with functions in base R (i.e., base R packages are listed in can.mask =) or S4 generic versions (generics.ok = TRUE). These exceptions generally make sense because package functions are often explicitly intended to mask or extend these functions.

An error will also not be produced if function conflicts exist within a single package (depends.ok = TRUE) because the package creator typically intended this as well.

Errors due to other function conflicts will happen immediately when you try to load the new package so you can address these conflicts up front (by either including only a subset of functions from the library or using the function’s namespace instead).

There are more advanced tools to handle conflicts in special cases that are also described in the documentation but they should rarely be necessary.

There is one more detail that affects our common use of both tidyverse and tidymodels collections of packages. The depends.ok policy allows for conflicts/masking within a package. However, there are a couple of instances where some of the functions in tidymodels will conflict/mask functions in packages within tidyverse. R doesn’t recognize these as the same package so the conflicts need to be handled explicitly. You can do this using conflictRules() Here are the conflict rules you will need to attach tidymodels after tidyverse

conflictRules("scales", mask.ok = c("discard"), exclude = c("col_factor"))
conflictRules("recipes", mask.ok = c("fixed"))
conflictRules("yardstick", mask.ok = c("spec"))

However, given how often we do this, we have wrapped these rules into a simple function. This function is reproduced here, but also shared in our fun_ml.R script in lab_support on github. If you source that script, you can call this function directly along with setting the depends.ok policy.

tidymodels_conflictRules <- function(){
  conflictRules("scales", mask.ok = c("discard"), exclude = c("col_factor"))
  conflictRules("recipes", mask.ok = c("fixed"))
  conflictRules("yardstick", mask.ok = c("spec"))
}

1.3 A Short Example

To implement the depends.ok policy and the rules for tidymodels, put this code chunk at the top of your script, prior to attaching any packages

# source
devtools::source_url("https://github.com/jjcurtin/lab_support/blob/main/fun_ml.R?raw=true")

# handle conflicts
options(conflicts.policy = "depends.ok")
tidymodels_conflictRules()

Now you can attach your packages and resolve any errors

  • Load tidyverse first and then load tidymodels, allowing it to mask functions in packages within tidyverse as indicated below
  • We demonstrate loading only clean_names() from the janitor package
library(tidyverse)
library(tidymodels)
library(janitor, include.only = "clean_names")

And if you later decided you wanted to simulate data you could use the appropriate MASS function directly with its namespace. Notice use of mvrnorm() with namespace because we did not attach the MASS package. Notice use of clean_names() without namespace because we attached just that function for janitor package.

d <- MASS::mvrnorm(n = 10, mu = c(0,0), Sigma = diag(2)) %>% 
  as_tibble(.name_repair = "minimal") %>% 
  clean_names("snake")

1.4 conflicted package

You should be aware that an alternative solution to handling function conflicts is provided in the conflicted package. However, this is no longer our preferred solution as the base R conflict policies are sufficient (so why load another package!). We also prefer to have conflicts detected immediately (when packages are attached) rather than at some later point when we call the function. The conflicted package is also less customizable than the base R polices.