3

Let's say I want to order a data.frame using multiple columns and using non-standard evolution. I might have a function that looks something like this

my_order <- function(data, ...) {
  with(data, order(...))
}

I get an error when I use this function because my columns are not evaluated within the context of with.

my_order(mtcars, mpg, cyl)
# Error in order(...) : object 'mpg' not found 

NOTE: I do not wish to use dplyr::arrange() for this problem as it adds a dependency.

nathaneastwood
  • 3,664
  • 24
  • 41

2 Answers2

4

Here's one way to pass along the unresolved symbols using base R

my_order <- function(data, ...) {
  dots <- substitute(...())
  with(data, do.call("order", as.list(dots)))
}
my_order(mtcars, mpg, cyl)

Basically we use substitute do capture the symbol names, then use do.call to inject those into a call to order.

Alternative you could think about re-writing the call to the function and changing the evaulation environment

my_order <- function(data, ...) {
  call <-match.call()
  call[[1]] <- quote(order)
  call[2] <- NULL
  eval(call, data, parent.frame())
}
my_order(mtcars, mpg, cyl)
MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Yep, this is perfect. I was playing around with `do.call` and couldn't quite get it right (I had `do.call(order, list(with(data, ...)))` so almost there). By the way, you can do `do.call(order, as.list(dots))` and I believe it might be slightly faster than quoting the order. Thanks :) – nathaneastwood Nov 07 '19 at 21:56
4

One option is to wrap the expression into eval.parent(substitute(...)):

my_order <- function( data, ... ) {
  eval.parent(substitute( with(data, order(...)) ))
}

my_order( mtcars, cyl, mpg )
# [1] 32 21  3  9  8 27 26 19 28 18 20 11  6 10 30  1  2  4 15 16 24  7 17 31 14
# [26] 23 22 29 12 13  5 25

Note that we use eval.parent() instead of eval(), because eval/substitute combo doesn't play well with nested functions. The eval.parent() trick has been proposed by @MoodyMudskipper as a way to address this problem and allows us to seamlessly use my_order() inside other functions, including magrittr pipes:

mtcars %>% my_order(cyl)
# [1]  3  8  9 18 19 20 21 26 27 28 32  1  2  4  6 10 11 30  5  7 12 13 14 15 16
# [26] 17 22 23 24 25 29 31
Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
  • One point to note, if you use a parameter with a period, say `.data`, this approach doesn't seem to work. – nathaneastwood Nov 08 '19 at 07:52
  • @nathaneastwood: I can't replicate the issue. Using `.data` works just as well as `data` for me. – Artem Sokolov Nov 08 '19 at 16:29
  • So it seems as though it occurs when you try to use `my_order()` in combination with the pipe and it has nothing to do with the name of the variable. So `mtcars %>% my_order(cyl)` will give the error, for example. – nathaneastwood Nov 08 '19 at 16:43
  • 1
    @nathaneastwood: OK, I know what the problem is. Please see the updated answer. – Artem Sokolov Nov 08 '19 at 16:56