Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order by multiple columns using non-standard evaluation

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.

like image 408
nathaneastwood Avatar asked Dec 10 '25 12:12

nathaneastwood


2 Answers

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
like image 128
Artem Sokolov Avatar answered Dec 12 '25 01:12

Artem Sokolov


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)
like image 32
MrFlick Avatar answered Dec 12 '25 01:12

MrFlick



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!