R: Use Magrittr Pipe Operator in Self Written Package

R: use magrittr pipe operator in self written package

It should have worked correctly if you had magrittr listed in Depends. However, this is not advised. Instead, you leave magrittr in Imports and add the following line to NAMESPACE:

importFrom(magrittr,"%>%")

I suggest reading Writing R extensions. Your question is covered in paragraphs 1.1.3 and 1.5.1.

Understanding the correct use of magrittr pipe `%%` syntax

Following @akrun's hint: ?Syntax has

  ‘%any% |>’         special operators (including ‘%%’ and ‘%/%’) 
‘* /’ multiply, divide

showing that the pipe operator has higher precedence than /, so that your expression is effectively

v/(sum(v) %>% signif(digits = 3))

Adding parentheses will help: try

(v/sum(v)) %>% signif(digits = 3)

How do I import %% when writing an R package?

Create a reexports.R file in your package with the following lines:

#' @importFrom magrittr %>%
#' @export
magrittr::`%>%`

This will make the pipe available to your package and also reexport it to users of your package, so when they load or attach your package the pipe will be available to them (they won’t have to also load magrittr). This can be automated with usethis::use_pipe() (see https://usethis.r-lib.org/reference/use_pipe.html). As @user2554330 mentions below, this solution depends on the use of roxygen2.

use of pipe operator %% using :: in R

Your best approach is to import it from magrittr and re-export it if you want your users to be able to access it.

Using it internally to your package only, this may work:

`%>%` <- purrr::`%>%`

check:

iris %>% summary
Sepal.Length Sepal.Width Petal.Length Petal.Width
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
Median :5.800 Median :3.000 Median :4.350 Median :1.300
Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
Species
setosa :50
versicolor:50
virginica :50

You cannot use purrr::`%>%` directly in expressions as the operator needs to be syntactically exposed as a binary operator, i.e. as %>% (without qualification).

use magrittr pipe within closures

The binary operator + is creating the problem. It has lower precedence than the pipe (see ?Syntax). Either enclose the entire operation in parentheses before piping to sum, or use the functional form of +:

(1:3 %>% rep(.,2) + 1) %>% sum
[1] 18

1:3 %>% rep(.,2) %>% `+`(1) %>% sum
[1] 18

`magrittr` pipe into apply

Here are two ways, with the magrittr and native pipes.

suppressPackageStartupMessages(library(magrittr))
dat <- 1:10
locs <- list(c(1, 2),
c(3, 4),
c(5, 6))

foo <- function(x, y, z) {
out <- mean(c(x[y], x[z]))
return(out)
}

# wrap the function around parenthesis
dat %>%
(\(d) lapply(locs, \(z) foo(., z[1], z[2])))()
#> [[1]]
#> [1] 1.5
#>
#> [[2]]
#> [1] 3.5
#>
#> [[3]]
#> [1] 5.5

# New native pipe with anonymous function
dat |>
{\(d) lapply(locs, \(z) foo(x = d, z[1], z[2]))}()
#> [[1]]
#> [1] 1.5
#>
#> [[2]]
#> [1] 3.5
#>
#> [[3]]
#> [1] 5.5

Created on 2022-05-18 by the reprex package (v2.0.1)

R: transition from magrittr to native pipe and translation of a function

complete_data_native_wrong():

complete_data_native_wrong <- function(data){

res <- data |> (\(x) filter(x, complete.cases(x)))()

return(res)

}

Data masking is the reason that this lovely function doesn't work as expected.

"So, what actually happens?", you ask.

dplyr::filter() checks for a column named x, it indeed finds it, then passes the contents of that column to complete.cases(). The same happens when you use y instead of x.

complete.cases() ends up acting on a "vector" instead of a data.frame, hence the results.

"But... How do I ensure dplyr::filter() doesn't act that way?", you enquire.

That's where the bang-bang operator !! comes in. And we can now have complete_data_native_right():

complete_data_native_right <- function(data){

res <- data |> (\(x) filter(x, complete.cases(!!x)))()
# res <- data |> (\(y) filter(y, complete.cases(!!y)))()

return(res)

}


move_row_native_attempt():

For this one you can use the shorthand function notation without any hiccups:

move_row_native_attempt <-  function(df, ini_pos, fin_pos){
row_pick <- slice(df, ini_pos)

if (fin_pos=="last"){
res <- df |>
slice(-ini_pos) |>
(\(x) add_row(x, row_pick, .before = nrow(x)))()

} else{
res <- df |>
slice(-ini_pos) |>
add_row(row_pick, .before = fin_pos)
}

return(res)
}

Rewrite code using the magrittr compound assignment pipe-operator %%

You could replace the pipe operator (%>%) with %<>%.

cemetaries %<>% select(LEGAL, OWNER, PROJECT, everything())

The benefit of using %<>% is that it by default updates the lhs object so that you don't have to do cemetaries <- .

When you use pipe operator to save the changed object back you need to do :

cemetaries <- cemetaries %>% select(LEGAL, OWNER, PROJECT, everything())

cemetaries <- part can be avoided if you use %<>%.

Use magrittr dot placeholder IN FUNCTION DEFINITION to pass object via pipe operator to argument in arbitrary position by default?

The problem is that foo doesn't "know" how the pipe was written
it just knows how it is called once magrittr has added the dot (if relevant)
so from the function itself we cannot differentiate implicit and explicit dots.

This issue aside we can just switch the 2 first arguments if the first one is a dot and return the evaluated modified call:

library(magrittr)
foo <- function(a, b) {
mc <- match.call()
if(mc[[2]] == quote(.)) {
mc[[2]] <- mc[[3]]
mc[[3]] <- quote(.)
return(eval.parent(mc))
}
c(a,b)
}
1 %>% foo(2)
#> [1] 2 1
1 %>% foo(2, .)
#> [1] 2 1
# but also, because of disclaimer above
1 %>% foo(., 2)
#> [1] 2 1

Created on 2019-10-09 by the reprex package (v0.3.0)

It will need to be adjusted if a can take a default value and be left empty, and possibly other edge cases.

edit: I lied when I said foo doesn't know how the pipe was written, it's in the call stack and we can see it by calling sys.call() in the function, but I think the solution is convoluted enough as it is!


Another way would be to define a pipe that inserts in second position, it's a bit more flexible and possibly less surprising :

foo <- function(a=2, b) {
c(a,b)
}

`%>2%` <-
function (lhs, rhs) {
rhs_call <- insert_dot2(substitute(rhs))
eval(rhs_call, envir = list(. = lhs), enclos = parent.frame())
}

insert_dot2 <-
function(expr, special_cases = TRUE) {
if(is.symbol(expr) || expr[[1]] == quote(`(`)) {
# if a symbol or an expression inside parentheses, make it a call with
# a missing first argument and a dot on second position
expr <- as.call(c(expr,alist(x=)[[1]], quote(`.`)))
} else if(length(expr) ==1) {
# if a call without arg, same thing
expr <- as.call(c(expr[[1]],alist(x=)[[1]], quote(`.`)))
} else if (expr[[1]] != quote(`{`) &&
all(sapply(expr[-1], `!=`, quote(`.`)))) {
# if a call with args but no dot in arg, insert dot in second place first
expr <- as.call(c(as.list(expr[1:2]), quote(`.`), as.list(expr[-(1:2)])))
}
expr
}
1 %>2% foo(2)
#> [1] 2 1
1 %>2% foo(2, .)
#> [1] 2 1
1 %>2% foo(., 2)
#> [1] 1 2
1 %>2% foo()
#> [1] 2 1

Created on 2019-10-09 by the reprex package (v0.3.0)


Note: piping to second is a bit weird, I'd rather pipe to last (it would have the same result for your question's example), if you want to pipe to last you would do :

foo <- function(a=2, b) {
c(a,b)
}

`%>last%` <-
function (lhs, rhs) {
rhs_call <- insert_dot_last(substitute(rhs))
eval(rhs_call, envir = list(. = lhs), enclos = parent.frame())
}

insert_dot_last <-
function(expr, special_cases = TRUE) {
if(is.symbol(expr) || expr[[1]] == quote(`(`)) {
# if a symbol or an expression inside parentheses, make it a call with
# a dot arg
expr <- as.call(c(expr, quote(`.`)))
} else if(length(expr) ==1) {
# if a call without arg, same thing
expr <- as.call(c(expr[[1]], quote(`.`)))
} else if (expr[[1]] != quote(`{`) &&
all(sapply(expr[-1], `!=`, quote(`.`)))) {
# if a call with args but no dot in arg, insert dot in last place
expr <- as.call(c(as.list(expr), quote(`.`)))
}
expr
}
1 %>last% foo(2)
#> [1] 2 1
1 %>last% foo(2, .)
#> [1] 2 1
1 %>last% foo(., 2)
#> [1] 1 2

Created on 2019-10-09 by the reprex package (v0.3.0)



Related Topics



Leave a reply



Submit