7

How can I deselect columns given in the ... argument of a self written function. (I also need to select the columns at another point, so just specifying the columns with - in ... does not solve my problem.)

Any soltions are apreciated, select-helpers, manipulating quosures or expressions, ...

# very simple example data
test <- data.frame(a=1:3, b=1:3, c=1:3)

# function skeleton
testfun <- function(x, ...){
  y <- select(x, ...)
  z <- select(x, -...) # does of course not work like this
  return(list(y, z))   # just as an example
}

# calling the function to select different columns
testfun(test, a)
testfun(test, a, b)
2
  • 1
    I would use select_ and pass in a character (vector). Commented Oct 19, 2017 at 11:15
  • Lionel Henry uses this example in his webinar rstudio.com/resources/webinars/?wvideo=zt9kl921rh and solve it with neg_dots <- map(dots, ~ quo(- !!.x)) Commented Nov 5, 2018 at 8:24

4 Answers 4

6

These easiest solution would be to select the positive columns, and then compare names to figure out which columns to drop, as in this answer.

To work on the dots directly,

  1. We will capture them in a list of quosures (quos).
  2. Unquote and splice the dots in with UQS for the positive selection.
  3. Do the same thing inside of c() so that we have a vector of selection.
  4. Negate that vector to do the negative selection.

This is the transformation described by (3) and (4).

library(dplyr)
dots <- quos(a, b)
quos(-c(UQS(dots)))
#> [[1]]
#> <quosure: frame>
#> ~-c(~a, ~b)
#> 
#> attr(,"class")
#> [1] "quosures"

The full solution then would be

test <- data.frame(a = 1:3, b = 1:3, c = 1:3)

# function skeleton
testfun <- function(x, ...) {
  dots <- quos(...)
  y <- select(x, UQS(dots))
  z <- select(x, -c(UQS(dots)))
  return(list(y, z))   
}

testfun(test, a)
#> [[1]]
#>   a
#> 1 1
#> 2 2
#> 3 3
#> 
#> [[2]]
#>   b c
#> 1 1 1
#> 2 2 2
#> 3 3 3

testfun(test, a, b)
#> [[1]]
#>   a b
#> 1 1 1
#> 2 2 2
#> 3 3 3
#> 
#> [[2]]
#>   c
#> 1 1
#> 2 2
#> 3 3

Test on selection helpers.

testfun(test, starts_with("b"), one_of("c"))
#> [[1]]
#>   b c
#> 1 1 1
#> 2 2 2
#> 3 3 3
#> 
#> [[2]]
#>   a
#> 1 1
#> 2 2
#> 3 3
Sign up to request clarification or add additional context in comments.

2 Comments

it seems c is not necessary, and the line for y was already working in OP's question, so you could just change the line for z into z <- select(x, -!!!quos(...))
my bad, you need c or testfun(test, a,b) will fail, the line then becomes z <- select(x, -c(!!!quos(...)))
2

You can use this trick with purrr::modify_at

library(purrr)
testfun <- function(x, ...){
  y <- select(x, ...)
  z <- modify_at(x,c(...),~NULL)
  return(list(y, z))   # just as an example
}

testfun(test,"a")
# [[1]]
#   a
# 1 1
# 2 2
# 3 3
# 
# [[2]]
#   b c
# 1 1 1
# 2 2 2
# 3 3 3

1 Comment

This looks really nice, I'll try this, accepted the other answer, because it was closer to what I had in mind when I asked the question, but this looks more practical.
1

What about that one?

testfun <- function(x, ...){
  y <- select(x, ...)
  z <- x[, !names(x) %in% names(y)]
  return(list(y, z))
}

Comments

0

You can try:

testfun <- function(x, y, z){
  y1 <- select(x, y)
  z1 <- select(x, -one_of(z)) 
  return(list(y1, z1)) 
}
testfun(test, "a", "b")
[[1]]
  a
1 1
2 2
3 3

[[2]]
  a c
1 1 1
2 2 2
3 3 3

You can also specify more variables using c

testfun(test, c("a", "c"), c("b", "a"))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.