4

I'd like to write a function that takes a quosure as an argument, appends - to the quosure, and passes that to gather, like so:

library(tidyverse)
my_gather <- function(d, not.columns) {
  dg <- tidyr::gather(d, key = k, value = v, .dots = -!!!not.columns)
  dg
}

de <- my_gather(mtcars, not.columns = quos(mpg, cyl, disp))

> Error in `-`(~mpg, ~cyl, ~disp) : operator needs one or two arguments

This is clearly because I need to append each element of the quosure with -, rather than appending the entire quosure with -. But in my work, it won't be easy to create this quosure in the form of quos(-mpg, -cyl, -disp) - so how can I modify quos(mpg, cyl, disp) to add the -?

I would like to see a result that is identical to gather(mtcars, key = k, value = v, -mpg, -cyl, -disp), the first 3 rows of which are

   mpg cyl disp  k   v
1 21.0   6  160 hp 110
2 21.0   6  160 hp 110
3 22.8   4  108 hp  93

There's a similar question here, but it is unanswered and doesn't seem to deal with the issue of quos() rather than quo().

Roland
  • 127,288
  • 10
  • 191
  • 288
Drew Steen
  • 16,045
  • 12
  • 62
  • 90

2 Answers2

3

We can do

my_gather <- function(d, not.columns) {
  tidyr::gather(d, key = k, value = v, .dots =  -c(UQS(not.columns)))
  #or use !!! instead of UQS
  #tidyr::gather(d, key = k, value = v, .dots =  -c(!!!(not.columns)))

}
de <- my_gather(mtcars, not.columns = quos(mpg, cyl, disp))
head(de, 3)
#   mpg cyl disp  k   v
#1 21.0   6  160 hp 110
#2 21.0   6  160 hp 110
#3 22.8   4  108 hp  93

Checking with the output without the function

de1 <- gather(mtcars, key = k, value = v, -mpg, -cyl, -disp)
identical(de, de1)
#[1] TRUE
akrun
  • 874,273
  • 37
  • 540
  • 662
  • How does this differ from OP’s attempt? `UQS` = `!!!`; the `c` shouldn’t make a difference here. – Konrad Rudolph Oct 29 '17 at 17:00
  • 2
    @KonradRudolph Yes, the `c` is the difference, But, I like to place it inside the braces – akrun Oct 29 '17 at 17:01
  • OK. Mind explaining why? It’s very much not obvious – Konrad Rudolph Oct 29 '17 at 17:08
  • 3
    @KonradRudolph I think the difference can be explained by comparing two commands: `'-'(1, 2, 3)` (gives error because function `-` needs one argument to change sign) and `'-'(c(1, 2, 3))` (gives expected result). So `c` wraps column names to become one argument. – echasnovski Oct 29 '17 at 19:09
2

I can offer "Answer the problem not the question" type of answer. You actually need a way to specify gathered columns having information about not used columns. Here is my way:

library(tidyverse)

negate_columns <- function(.tbl, not.columns) {
  not_columns <- colnames(select(.tbl, !!!not.columns))

  setdiff(colnames(.tbl), not_columns)
}

my_gather <- function(d, not.columns) {
  columns <- negate_columns(d, not.columns)

  tidyr::gather(d, key = k, value = v, !!!syms(columns))
}

This way it works as expected:

my_gather(mtcars, not.columns = quos(mpg, cyl, disp)) %>%
    head(3)
#>    mpg cyl disp  k   v
#> 1 21.0   6  160 hp 110
#> 2 21.0   6  160 hp 110
#> 3 22.8   4  108 hp  93
echasnovski
  • 1,161
  • 8
  • 13