在data.frame中有效地定位分组常量列

had*_*ley 7 r plyr dataframe

如何从数据框中有效地提取分组常量列?我在下面包含了一个plyr实现,以准确地说明我正在尝试做什么,但它很慢.我怎样才能尽可能高效地做到这一点?(理想情况下,根本不分割数据帧).

base <- data.frame(group = 1:1000, a = sample(1000), b = sample(1000))
df <- data.frame(
  base[rep(seq_len(nrow(base)), length = 1e6), ], 
  c = runif(1e6), 
  d = runif(1e6)
)


is.constant <- function(x) length(unique(x)) == 1
constant_cols <- function(x) head(Filter(is.constant, x), 1)
system.time(constant <- ddply(df, "group", constant_cols))
#   user  system elapsed 
# 20.531   1.670  22.378 
stopifnot(identical(names(constant), c("group", "a", "b")))
stopifnot(nrow(constant) == 1000)
Run Code Online (Sandbox Code Playgroud)

在我的实际用例(深入ggplot2内)中,可能存在任意数量的常量和非常量列.示例中的数据大小大约是正确的数量级.

had*_*ley 3

受到@Joran's回答的启发,这里有一个类似的策略,但速度更快一些(在我的机器上是1 s vs 1.5 s)

changed <- function(x) c(TRUE, x[-1] != x[-n])

constant_cols2 <- function(df,grp){
  df <- df[order(df[,grp]),]
  n <- nrow(df)
  changes <- lapply(df, changed)

  vapply(changes[-1], identical, changes[[1]], FUN.VALUE = logical(1))
}
system.time(cols <- constant_cols2(df, "group")) # about 1 s

system.time(constant <- df[changed(df$group), cols])
#   user  system elapsed 
#  1.057   0.230   1.314 

stopifnot(identical(names(constant), c("group", "a", "b")))
stopifnot(nrow(constant) == 1000)
Run Code Online (Sandbox Code Playgroud)

但它具有相同的缺陷,因为它不会检测相邻组具有相同值的列(例如df$f <- 1

再多思考一下@David 的想法:

constant_cols3 <- function(df, grp) {
  # If col == TRUE and group == FALSE, not constant
  matching_breaks <- function(group, col) {
    !any(col & !group)
  }

  n <- nrow(df)
  changed <- function(x) c(TRUE, x[-1] != x[-n])

  df <- df[order(df[,grp]),]
  changes <- lapply(df, changed)
  vapply(changes[-1], matching_breaks, group = changes[[1]], 
    FUN.VALUE = logical(1))
}

system.time(x <- constant_cols3(df, "group"))
#   user  system elapsed 
#  1.086   0.221   1.413 
Run Code Online (Sandbox Code Playgroud)

这给出了正确的结果。