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 <[email protected]>
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With