R function to order data.frames held in a list object

Viewed 143

I'm trying to find an elegant function to order data.frames held in a list object. I already know that lapply(df, function(x) x[with(x, order(var)), ]) works fine, but that seems way too complicated. I'm trying to use the "[" function, which works fine if I input the row numbers manually. But I'd like to use the row numbers generated by the order function, obviously.

df <- list(
    data.frame(name = c("John", "Paul", "George", "Ringo"),   height = c(60, 58, 65, 55)),
    data.frame(name = c("Frank", "Tony", "Arthur", "Edward"), height = c(55, 65, 60, 50))
)

lapply(df, "[", c("height", "name"))

lapply(df, "[", c(3:1), )

order <- lapply(df, with, order(name))

order

lapply(df, with, order(name), "[")

lapply(df, with, "[", order(name), )

lapply(df, "[", with, order(name), )

Map("[", order , , df)
4 Answers

If you use library(data.table) rather than data.frame then the solution is quite elegant.

We can convert your object to data.table using lapply(df, setDT). Note this step won't be required if you create your objects as data.table in the first place, which would be a more typical workflow.

Then the ordering can be done using

lapply(df, setkey, 'name')

# [[1]]
#      name height
# 1: George     65
# 2:   John     60
# 3:   Paul     58
# 4:  Ringo     55
# 
# [[2]]
#      name height
# 1: Arthur     60
# 2: Edward     50
# 3:  Frank     55
# 4:   Tony     65

Other packages would simplify this. If you want to use base, you can extend what you were attempting with Map():

order <- lapply(df, with, order(name))
Map("[", df, order, TRUE)
#pseudo translation: df[order, TRUE]
## where TRUE will repeat and select all columns of df

The simplest way with base is to move forward with what you suggest is too complicated. You could simplify with a premade function:

lapply(df, function(x) x[order(x[["name"]]), ])

#or
fx_reorder = function (x, col){
  x[order(x[[col]]), ]
}

lapply(df, fx_reorder, "name")

## or to accept multiple columns
fx_reorder2 = function(x, cols) {
  if (missing(cols)) cols = names(x)
  x[do.call("order", x[cols]), ]
}

lapply(df, fx_reorder2)
lapply(df, fx_reorder2, "name")
lapply(df, fx_reorder2, 1:2)

We can use arrange with map

library(dplyr)
library(purrr)
map(df,  ~ .x %>% 
              arrange(name))

Are you familiar with the dplyr package? You can put the arrange function inside lapply.

lapply(df, arrange, -height)

If you're not familiar with dplyr I would look into it. It sounds like your problem might be solvable with bind_rows and group_by.

Related