Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to create generic/method consistency for sort.data.frame?

I've finally decided to put the sort.data.frame method that's floating around the internet into an R package. It just gets requested too much to be left to an ad hoc method of distribution.

However, it's written with arguments that make it incompatible with the generic sort function:

sort(x,decreasing,...)
sort.data.frame(form,dat)

If I change sort.data.frame to take decreasing as an argument as in sort.data.frame(form,decreasing,dat) and discard decreasing, then it loses its simplicity because you'll always have to specify dat= and can't really use positional arguments. If I add it to the end as in sort.data.frame(form,dat,decreasing), then the order doesn't match with the generic function. If I hope that decreasing gets caught up in the dots `sort.data.frame(form,dat,...), then when using position-based matching I believe the generic function will assign the second position to decreasing and it will get discarded. What's the best way to harmonize these two functions?

The full function is:

# Sort a data frame
sort.data.frame <- function(form,dat){
# Author: Kevin Wright
# http://tolstoy.newcastle.edu.au/R/help/04/09/4300.html
# Some ideas from Andy Liaw
# http://tolstoy.newcastle.edu.au/R/help/04/07/1076.html
# Use + for ascending, - for decending.
# Sorting is left to right in the formula
# Useage is either of the following:
# sort.data.frame(~Block-Variety,Oats)
# sort.data.frame(Oats,~-Variety+Block)

# If dat is the formula, then switch form and dat
  if(inherits(dat,"formula")){
    f=dat
    dat=form
    form=f
  }
  if(form[[1]] != "~") {
    stop("Formula must be one-sided.")
  }
# Make the formula into character and remove spaces
  formc <- as.character(form[2])
  formc <- gsub(" ","",formc)
# If the first character is not + or -, add +
  if(!is.element(substring(formc,1,1),c("+","-"))) {
    formc <- paste("+",formc,sep="")
  }
# Extract the variables from the formula
  vars <- unlist(strsplit(formc, "[\\+\\-]"))
  vars <- vars[vars!=""] # Remove spurious "" terms
# Build a list of arguments to pass to "order" function
  calllist <- list()
  pos=1 # Position of + or -
  for(i in 1:length(vars)){
    varsign <- substring(formc,pos,pos)
    pos <- pos+1+nchar(vars[i])
    if(is.factor(dat[,vars[i]])){
      if(varsign=="-")
        calllist[[i]] <- -rank(dat[,vars[i]])
      else
        calllist[[i]] <- rank(dat[,vars[i]])
    }
    else {
      if(varsign=="-")
        calllist[[i]] <- -dat[,vars[i]]
      else
        calllist[[i]] <- dat[,vars[i]]
    }
  }
  dat[do.call("order",calllist),]
} 

Example:

library(datasets)
sort.data.frame(~len+dose,ToothGrowth)
like image 796
Ari B. Friedman Avatar asked Jul 26 '11 21:07

Ari B. Friedman


2 Answers

Use the arrange function in plyr. It allows you to individually pick which variables should be in ascending and descending order:

arrange(ToothGrowth, len, dose)
arrange(ToothGrowth, desc(len), dose)
arrange(ToothGrowth, len, desc(dose))
arrange(ToothGrowth, desc(len), desc(dose))

It also has an elegant implementation:

arrange <- function (df, ...) {
  ord <- eval(substitute(order(...)), df, parent.frame())
  unrowname(df[ord, ])
}

And desc is just an ordinary function:

desc <- function (x) -xtfrm(x)

Reading the help for xtfrm is highly recommended if you're writing this sort of function.

like image 87
hadley Avatar answered Nov 17 '22 12:11

hadley


There are a few problems there. sort.data.frame needs to have the same arguments as the generic, so at a minimum it needs to be

sort.data.frame(x, decreasing = FALSE, ...) {
....
}

To have dispatch work, the first argument needs to be the object dispatched on. So I would start with:

sort.data.frame(x, decreasing = FALSE, formula = ~ ., ...) {
....
}

where x is your dat, formula is your form, and we provide a default for formula to include everything. (I haven't studied your code in detail to see exactly what form represents.)

Of course, you don't need to specify decreasing in the call, so:

sort(ToothGrowth, formula = ~ len + dose)

would be how to call the function using the above specifications.

Otherwise, if you don't want sort.data.frame to be an S3 generic, call it something else and then you are free to have whatever arguments you want.

like image 37
Gavin Simpson Avatar answered Nov 17 '22 11:11

Gavin Simpson