5

I would like to do an absolute descending sort (i.e. sort ignoring sign so e.g. 5, -2, 1) of my data.table by passing a sort.field into a function.

I have looked at descending sort, but in my efforts I get errors or I am transforming the sign of my variable, and not sorting it properly.

This works:

library(data.table)
DT <- data.table(id = c("a","b","z"), 
                 score = c(1, 5, -2))
DT1 <- copy(DT)

#doing sort direct works
DT1 <- DT1[order(-abs(score))] 
# i.e. b, Z, a

But when passing a parameter I cannot find the right syntax (math errors, j must be provided, etc.)

#in function
sort.field = "score"

sortme <- function(dt, sort.field){

  dt <- dt[order(-abs(sort.field))]  
}

DT2 <- sortme(DT, sort.field)
#  ERROR get non-numeric argument to maths function as it sees string

I have tried various evals, as.name, with = F, etc.

   dt <- dt[, order(-abs(as.name(sort.field))]  

   # even
    expr <- substitute(x := -abs(x),  list(x=as.name(sort.field)))
    dt<- dt[,eval(expr)]

DT3 <- DT[,eval(expr)] # changes all to negative
DT4 <- DT[order(eval(expr))] # DT not happy

Please put me out of my misery! Thanks so much.

P.S. setorderv() handles straight Ascending and Descending cases. Yes, I could add a column, absolute it and then use setorderv, then delete the temp column, but I am looking for a more elegant solution.

Edit: Others have pointed to the similar answer for filtering. That question also covers data.frames, and not solely data tables, and is focused on filtering rows of data. It does not do a transformative ordering with a function like abs() of the field text, keeping all the rows and not changing the data. Also this question could help others looking at this absolute sort type for data.table which is not covered in its setorderv().

Community
  • 1
  • 1
micstr
  • 5,080
  • 8
  • 48
  • 76
  • Similar question, identical answers (and answerers) over here: http://stackoverflow.com/questions/29564002/filter-data-table-by-dynamic-column-name – Frank Apr 11 '15 at 14:17
  • possible duplicate of [Variably selecting/assigning to fields in a data.table](http://stackoverflow.com/questions/12391950/variably-selecting-assigning-to-fields-in-a-data-table) – Frank Apr 11 '15 at 14:21
  • 1
    Thanks Frank for linking answers. I did not see these when I searched. You will see I tried the `with = FALSE`. My error was reassigning the data table and not just searching by ref - so should have avoided DT <- DT[..] when ordering. My answer is different as I am trying to put a function on colname which is a bit trickier and I hope this answer will help others.. – micstr Apr 11 '15 at 14:31
  • For what it's worth, I see all of these questions as the same because they all involve passing a string column name (or vector of names) for evaluation inside a `[.data.table` call. Whether it's in the `i` or `j` field; what the particular operation is; or whether you ask that it be turned into a function doesn't change the core of the question and answer. I'm fine with it not being voted a dupe, but I think there's a pretty good case. I hope when someone comes by asking for "case-independent sort" of a string column, `DT[order(upper(v))]`, we can mark that as a dupe with this at least. – Frank Apr 12 '15 at 01:50
  • 1
    I appreciate your sentiments. Getting to grips with `data.table` as a beginner can be tricky given how sophisticated the package is. When do you use `as.name()`, when do you use `with = F`, its no wonder the FAQ starts "why DT[, 5] returns 5!". Beginners might also not realise that *variable selecting* = *putting through function* = *parameterising* are similar, although this is probably obvious to full time coders. – micstr Apr 12 '15 at 06:59

2 Answers2

3

Try this:

sort.field = "score"

sortme <- function(dt, sort.field) dt[order(-abs(dt[[sort.field]]))]

sortme(DT, sort.field)
#   id score
#1:  b     5
#2:  z    -2
#3:  a     1

What you did after the first function definition was to pass a string to the abs function, hence the ERROR get non-numeric argument to maths function as it sees string

David Arenburg
  • 91,361
  • 17
  • 137
  • 196
Colonel Beauvel
  • 30,423
  • 11
  • 47
  • 87
3

The eval(as.name()) would work

 sort.field = "score"
 sort_me <- function(dt, sort.field){
  dt[order(-abs(eval(as.name(sort.field))))]
 }
 sort_me(DT, sort.field)
 #   id score
 #1:  b     5
 #2:  z    -2
 #3:  a     1
swihart
  • 2,648
  • 2
  • 18
  • 42
akrun
  • 874,273
  • 37
  • 540
  • 662