2

I'm trying to pipe a vector into an all() statement to check if all elements are equal to a certain value. I figure I need to use the exposition pipe %$% since all() does not have a built-in data argument. My attempt leads to an error:

library(tidyverse)
library(magrittr)

vec <- c("a", "b", "a")

vec %>%
  keep(!grepl("b", .)) %$%
  all(. == "a")
#> Error in eval(substitute(expr), data, enclos = parent.frame()): invalid 'envir' argument of type 'character'

If I break the pipe before all() and assign the output to an object p, and then pass p to all() as a second command it works fine:

vec %>%
  keep(!grepl("b", .)) -> p

all(p == "a")
#> [1] TRUE

I don't understand why this works while my first attempt does not. I'd like to be able to do this in a single pipe that results in TRUE.

If vec is instead a tibble the following works:

vec <- tibble(var = c("a", "b", "a"))

vec %>%
  filter(!grepl("b", var)) %$%
  all(.$var == "a")
#> [1] TRUE

This doesn't suit my purposes as well, and I'd for my own understanding to know why my first attempt does not work.

lost
  • 1,483
  • 1
  • 11
  • 19

1 Answers1

11

The way pipe works is it takes the left-hand side of the pipe operator and passes it as a first argument to the right hand side function. So here, in this case as we need to modify the data argument to all , we need to stop pipe from passing the LHS to the RHS. We can do that by using {}.

library(magrittr)

vec %>% purrr::keep(!grepl("b", .)) %>% {all(. == 'a')}
#[1] TRUE

In vec , let's check if all the elements are either "a" or "b". We can use %in% here.

vec <- c("a", "b", "a")

Plain version without pipes would be :

all(vec %in% c('a', 'b'))
#[1] TRUE

With pipes, if we try

vec %>% all(. %in% c('a', 'b'))

We get

#[1] NA

Warning message: In all(., . %in% c("a", "b")) : coercing argument of type 'character' to logical

what is happening here is

all(vec, vec %in% c('a', 'b'))
#[1] NA

Warning message: In all(vec, vec %in% c("a", "b")) : coercing argument of type 'character' to logical

which returns the same message.

To avoid this we use {}

vec %>% {all(. %in% c('a', 'b'))}
#[1] TRUE

which gives us the expected answer.

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • why does the issue not occur in the last example, then? – lost Feb 07 '20 at 03:28
  • @lost `%$%` and `%>%` are two different functions. `%$%` doesn't pass output of the previous function as first argument to next function. – Ronak Shah Feb 07 '20 at 03:33
  • I know they are different functions. I don't quite follow why we need to stop the pipe from passing the LHS to the RHS -- isn't that what we're trying to do? – lost Feb 07 '20 at 03:39
  • I'm familiar with the usage of curly brackets to keep `%>%` from injecting `.` twice, such as discussed [here](https://stackoverflow.com/questions/42385010/using-the-pipe-and-dot-notation), but it seems curly brackets are serving a different purpose in this case? – lost Feb 07 '20 at 03:47
  • thanks for the edit. It is helpful. Would I be right in concluding that in my last example, `%$% all(.$var == "a")` does not throw an error because subsetting `.` causes the first argument piping to be suppressed, such that it is not interpreted as `%$% all(., .$var == "a")`? – lost Feb 07 '20 at 04:00
  • 1
    `%$%` doesn't pass LHS output as first argument to RHS function. Check `mtcars %$% subset(cyl > 6)` vs `mtcars %>% subset(cyl > 6)`. However, issue looks like that `%$%` doesn't work for vectors. See `vec %$% .` and `mtcars %$% .` hence you get an error in first case. This is also mentioned in `?\`%$%\`` where `lhs` is `A list, environment, or a data.frame.` – Ronak Shah Feb 07 '20 at 04:07
  • Gotcha. I did not realize `%$%` was not intended for vectors. I think that was the biggest source of confusion for me. Thanks again. – lost Feb 07 '20 at 04:12