How to use namespaced function with dplyr :: mutate_each?

I am trying to use dplyr::mutate_each with some external functions without attaching the actual libraries

 dplyr::tbl_df(iris) %>% dplyr::mutate_each(dplyr::funs(stringi::stri_trim_both)) 

but it does not work with the following error:

Error: unsupported type for column "Sepal.Length" (CLOSXP, classes = function)

When I use data.table instead of data.frame :

Error in `.data.table` (` _dt`, `: =` (Sepal.Length, stringi :: stri_trim_both) :: The assignment RHS is not NULL, not an atomic vector (see Is.atomic), not a column list.

If I use a local variable as shown below, everything works as expected.

 trim_both <- stringi::stri_trim_both dplyr::tbl_df(iris) %>% dplyr::mutate_each(dplyr::funs(trim_both)) 

This is not an optimal solution, but I can live with it. However, I would be grateful for an explanation of what is the source of the problem.

Session Information:

 R version 3.1.1 (2014-07-10) Platform: x86_64-pc-linux-gnu (64-bit) locale: [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 [7] LC_PAPER=en_US.UTF-8 LC_NAME=C [9] LC_ADDRESS=C LC_TELEPHONE=C [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] dplyr_0.4.1 loaded via a namespace (and not attached): [1] assertthat_0.1 DBI_0.3.1 lazyeval_0.1.10.9000 [4] magrittr_1.5 parallel_3.1.1 Rcpp_0.11.4 [7] stringi_0.4-1 tools_3.1.1 

Note : this problem is no longer encountered in dplyr 0.7.2.

+6
source share
1 answer

The main reason is that dplyr::funs_ calls dplyr:::make_call . And dplyr:::make_call distinguishes cases using the class object generated by lazyeval::lazy_dots .

 class(lazyeval::lazy_dots(trim_both)[[1]]$expr) ## "name" class(lazyeval::lazy_dots(stringi::stri_trim_both)[[1]]$expr) ## "call" 

The following is the my_funs function to solve this problem. I have not tested this in detail, and I'm sure there is a reason that it was different in dplyr , so do not use this by default. This basically meant clarifying the problem.

 # calling my_funs_ (instead of funs_) my_funs <- function (...) my_funs_(lazyeval::lazy_dots(...)) my_funs_ <- function(dots){ dots <- lazyeval::as.lazy_dots(dots) env <- lazyeval::common_env(dots) names(dots) <- dplyr:::names2(dots) # difference here dots[] <- lapply(dots, function(x) { if (is.character(x$expr)) { x$expr <- substitute(f(.), list(f = as.name(x$expr))) } else if (is.name(x$expr)) { x$expr <- substitute(f(.), list(f = x$expr)) } else if (is.call(x$expr)) { x$expr <- substitute(f(.), list(f = x$expr)) #### this line was different # originally x$expr <- x$expr } else { stop("Unknown inputs") } x }) missing_names <- names(dots) == "" ### this is also different default_names <- vapply(dots[missing_names], function(x) as.character(x)[1], character(1)) ## originally dplyr:::make_name(x) instead of as.character(x)[1] names(dots)[missing_names] <- default_names class(dots) <- c("fun_list", "lazy_dots") dots } dplyr::tbl_df(iris) %>% dplyr::mutate_each(my_funs(stringi::stri_trim_both)) 
+7
source

Source: https://habr.com/ru/post/982833/


All Articles