I'm making a package for data manipulation that uses some other libraries under the hood. Let's say that my data always has a class "custom"
and that I have a function custom_select()
to select some columns.
I would like my package to have few dependencies but also a similar syntax as functions from dplyr
. Because several dplyr
functions are generics, I can use the same function names for a different input type. In my situation, I could make a method select.custom()
so that the user can either pass a data.frame
or a custom
object to select()
and both would work.
Now from my understanding, this requires putting dplyr
in Imports
because I need to have access to its select()
generic. I'd like to avoid doing this because I want to limit the number of hard dependencies.
The scenario I have in mind is:
dplyr
anyway, then they can use select()
with the data of class custom
and it should workdplyr
installed/loaded, and I don't want to force them to have it, so they can use the function custom_select()
instead.Ideally, I'd like to put dplyr
in Suggests
so that it's not strictly necessary but it adds something if the user has it.
custom.R
:
#' @export
#' @importFrom dplyr select
custom_select <- function(data, select) {
print("Hello, world!")
}
#' @export
select.custom <- custom_select
NAMESPACE
:
# Generated by roxygen2: do not edit by hand
export(custom_select)
export(select.custom)
importFrom(dplyr,select)
R CMD check errors if I don't put dplyr
in Imports
and putting it in Suggests
also doesn't work (same error for both cases):
❯ checking package dependencies ... ERROR
Namespace dependency missing from DESCRIPTION Imports/Depends entries: 'dplyr'
In summary, is there a way to keep dplyr
out of hard dependencies while still providing methods for dplyr
's generics if it is available?
Edit: I tried @VonC's answer but couldn't make it work. In the example below, dplyr
is loaded before my custom package so select.custom()
should be available but isn't:
library(dplyr, warn.conflicts = FALSE)
library(custompackage)
foo <- letters
class(foo) <- "custom"
custom_select(foo)
#> [1] "Hello, world!"
select(foo)
#> Error in UseMethod("select"): no applicable method for 'select' applied to an object of class "custom"
Here are the important files:
custom.R
#' @export
custom_select <- function(data, select) {
print("Hello, world!")
}
if (requireNamespace("dplyr", quietly = TRUE)) {
select.custom <- function(data, select) {
custom_select(data, select)
}
utils::globalVariables("select.custom")
}
NAMESPACE
# Generated by roxygen2: do not edit by hand
export(custom_select)
DESCRIPTION
(no Imports
)
[...]
Suggests:
dplyr
You need to put dplyr in Enhances
and use .onLoad
to conditionally register your method in the dplyr namespace, depending on whether dplyr is installed at load time.
nm <- package <- "TestPackage"
dir.create(file.path(package, "R"), recursive = TRUE)
dir.create(file.path(package, "man"), recursive = TRUE)
dir.create(file.path(package, "tests"), recursive = TRUE)
cat(file = file.path(package, "DESCRIPTION"), "
Package: TestPackage
Version: 0.0-0
License: GPL (>= 2)
Description: A (one paragraph) description of what
the package does and why it may be useful.
Title: My First Collection of Functions
Author: First Last [aut, cre]
Maintainer: First Last <First.Last@some.domain.net>
Enhances: dplyr
")
cat(file = file.path(package, "NAMESPACE"), "
export(selectDotZzz)
")
cat(file = file.path(package, "R", paste0(nm, ".R")), "
selectDotZzz <- function(.data, ...) 0
.onLoad <- function(libname, pkgname) {
if(requireNamespace(\"dplyr\", quietly = TRUE))
registerS3method(\"select\", \"zzz\", selectDotZzz,
envir = asNamespace(\"dplyr\"))
}
")
cat(file = file.path(package, "man", paste0(nm, ".Rd")), "
\\name{whatever}
\\alias{selectDotZzz}
\\title{whatever}
\\description{whatever}
")
cat(file = file.path(package, "tests", paste0(nm, ".R")),
sprintf("library(%s)", nm))
cat(file = file.path(package, "tests", paste0(nm, ".R")), append = TRUE, "
if(requireNamespace(\"dplyr\", quietly = TRUE))
stopifnot(identical(dplyr::select(structure(0, class = \"zzz\")), 0))
")
getRversion()
packageVersion("dplyr")
tools:::Rcmd(c("build", package))
tools:::Rcmd(c("check", Sys.glob(paste0(nm, "_*.tar.gz"))))
unlink(Sys.glob(paste0(nm, "*")), recursive = TRUE)
The relevant output:
> getRversion()
[1] '4.3.1'
> packageVersion("dplyr")
[1] '1.1.2'
> tools:::Rcmd(c("build", package))
* checking for file 'TestPackage/DESCRIPTION' ... OK
* preparing 'TestPackage':
* checking DESCRIPTION meta-information ... OK
* checking for LF line-endings in source and make files and shell scripts
* checking for empty or unneeded directories
* building 'TestPackage_0.0-0.tar.gz'
> tools:::Rcmd(c("check", Sys.glob(paste0(nm, "_*.tar.gz"))))
* using log directory '/Users/mikael/Desktop/R-experiments/codetools/TestPackage.Rcheck'
* using R version 4.3.1 Patched (2023-06-19 r84580)
* using platform: aarch64-apple-darwin22.5.0 (64-bit)
* R was compiled by
Apple clang version 14.0.3 (clang-1403.0.22.14.1)
GNU Fortran (GCC) 12.2.0
* running under: macOS Ventura 13.4
* using session charset: UTF-8
* checking for file 'TestPackage/DESCRIPTION' ... OK
* this is package 'TestPackage' version '0.0-0'
* checking package namespace information ... OK
* checking package dependencies ... OK
* checking if this is a source package ... OK
* checking if there is a namespace ... OK
* checking for executable files ... OK
* checking for hidden files and directories ... OK
* checking for portable file names ... OK
* checking for sufficient/correct file permissions ... OK
* checking whether package 'TestPackage' can be installed ... OK
* checking installed package size ... OK
* checking package directory ... OK
* checking DESCRIPTION meta-information ... OK
* checking top-level files ... OK
* checking for left-over files ... OK
* checking index information ... OK
* checking package subdirectories ... OK
* checking R files for non-ASCII characters ... OK
* checking R files for syntax errors ... OK
* checking whether the package can be loaded ... OK
* checking whether the package can be loaded with stated dependencies ... OK
* checking whether the package can be unloaded cleanly ... OK
* checking whether the namespace can be loaded with stated dependencies ... OK
* checking whether the namespace can be unloaded cleanly ... OK
* checking loading without being on the library search path ... OK
* checking startup messages can be suppressed ... OK
* checking dependencies in R code ... OK
* checking S3 generic/method consistency ... OK
* checking replacement functions ... OK
* checking foreign function calls ... OK
* checking R code for possible problems ... OK
* checking Rd files ... OK
* checking Rd metadata ... OK
* checking Rd cross-references ... OK
* checking for missing documentation entries ... OK
* checking for code/documentation mismatches ... OK
* checking Rd \usage sections ... OK
* checking Rd contents ... OK
* checking for unstated dependencies in examples ... OK
* checking examples ... NONE
* checking for unstated dependencies in 'tests' ... OK
* checking tests ...
Running ‘TestPackage.R’
OK
* checking PDF version of manual ... OK
* DONE
Status: OK