【问题标题】:unable to use lapply with data.table无法将 lapply 与 data.table 一起使用
【发布时间】:2018-06-25 11:19:11
【问题描述】:

我正在尝试创建 data.table 中所有字符变量的摘要。基本上是为了获得总观察计数、缺失值、频率最高的类别等。但是我无法正确使用 lapply 。这是一个可重现的例子。

library(data.table)

#Function to analyze one variable at a time
analyze_char_var <- function(x) {
  y = names(x)
  z = x[,.N,by=y]
  w = setorder(z,-N)

  out = data.table( 
    total_obs = nrow(x),
    missing_obs = sum(is.na(x)),
    unique_cats = nrow(z),
    top_cat = z[1,1],
    top_freq = z[1,2]
  )
  return(out)
}
#Function to analyze all variables. I want to use lapply instead of loop
analyze_all_char <- function(dt) {
  dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
  mylist = vector('list', length(dt.char))
  for (i in 1:length(dt.char)){
    x = dt.char[,i,with=FALSE]
    mylist[[i]] = analyze_char_var(x)
  }
  return(mylist)
}

dt = data.table(
  var1 = c('a', 'a', 'b','b', 'c'),
  var2 = 1:5,
  var3 = c('low','low','high','med',NA)
)
dt.analysis = analyze_all_char(dt)

仅使用dt.analysis = dt.char[,lapply(.SD,analyze_char_var)] 会产生错误Error in x[, .N, by = y] : incorrect number of dimensions。我尝试了一些变化,但无法让它发挥作用。

编辑:最后这对我有用。但是,看起来很笨拙。将输入向量重新转换为data.table,然后以data.frame的方式使用lapply

test_func <- function(x) {
  dt = as.data.table(x)
  dt.summ = dt[,.N,by='x'] #by default name is x
  # I was stuck in the above line, I was trying all 
  # sort of bad tricks to get the name of grouping variable 


  dt.summ.sorted = setorder(dt.summ,-N)
  out = data.table(
    total_obs = nrow(dt),
    missing_obs = sum(is.na(dt)),
    unique_cats = nrow(dt.summ.sorted),
    top_cat = dt.summ.sorted[1,1],
    top_freq = dt.summ.sorted[1,2]
  )
  return(out)
}

dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
lapply(dt.char,test_func)

【问题讨论】:

  • 如果您一次分析一个变量,analyze_char_var x 将是一个向量而不是 data.table。您需要重新考虑功能的整体设计。
  • 谢谢,我明白这一点,并试图将向量“转换”回数据表,但不知何故犯了一些错误,我在 2 小时后就无法捕捉到。终于让它工作了,但它看起来一点也不优雅。在问题中将其作为编辑。

标签: r data.table lapply summarization


【解决方案1】:

我正在尝试创建 data.table 中所有字符变量的摘要。基本上是为了获得总观察计数、缺失值、频率最高的类别等。

由于所有感兴趣的列都具有相同的类型,您可以使用melt 转到长格式:

melt(dt.char <- Filter(is.character, dt), meas=names(dt.char))[, {

  tabula = setDT(list(value))[, .N, by="V1"][order(-N, V1)]

  .(
    NOBS  = .N,
    NNA   = sum(is.na(value)),
    NVALS = nrow(tabula),
    HIVAL = tabula$V1[1L],
    NHI   = tabula$N[1L]
  )
}, by=variable]

#    variable NOBS NNA NVALS HIVAL NHI
# 1:     var1    5   0     3     a   2
# 2:     var3    5   1     4   low   2

要将 NA 作为一个类别排除(显示在 NVALS 和可能的 HIVAL、NHI 中),请将上面的 [, .N, by="V1"] 更改为 [!is.na(V1), .N, by="V1"]

我怀疑性能对这项任务很重要,但这应该是相当有效的。

【讨论】:

  • 这肯定会比 for-loop 和 apply 更有效,尽管我得到的“NVALS”结果不同。您的代码给出 3 和 3,而其他代码给出 3 和 4。
  • @SeGa 啊,很好。阅读 OP 的描述,我认为他们不会将 NA 视为有效类别,但他们确实......现在将进行编辑。谢谢!
  • 实际上,我不会将 NA 视为有效类别,但这在当时并不重要。感谢一如既往的出色解决方案。
【解决方案2】:

应该这样做:

analyze_all_char <- function(dt) {
  dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
  mylist = lapply(1:length(dt.char), function(i) {
    x = dt.char[,i,with=FALSE]
    analyze_char_var(x)
  })
  return(mylist)
}

基准测试它,你不会看到太多的性能提升。如果您追求性能,我建议您使用 data.table 操作进行计算。

我增加了 data.frame 并检查了 for-loop、lapply 和 @Frank 的解决方案。明显的赢家是data.table

Unit: milliseconds
    expr      min       lq     mean   median       uq      max neval cld
 forloop 4.070700 4.685024 7.220436 6.709425 8.564480 35.81166   500   b
  lapply 3.988765 4.750347 7.367764 6.815147 8.613754 56.58692   500   b
 lapply1 4.008022 4.728257 7.390874 6.786074 8.551453 51.31551   500   b
     dtf 2.984400 3.320825 5.451909 4.699372 6.661660 40.85501   500  a

完整代码:

dt = data.table(
  var1 = rep(c('a', 'a', 'b','b', 'c'),100),
  var2 = rep(1:5,100),
  var3 = rep(c('low','low','high','med',NA),100)
)

analyze_all_char <- function(dt) {
  dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
  mylist = list()
  for (i in 1:length(dt.char)){
    x = dt.char[,i,with=FALSE]
    mylist[[i]] = analyze_char_var(x)
  }
  return(mylist)
}
analyze_all_char_l <- function(dt) {
  dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
  mylist = lapply(1:length(dt.char), function(i) {
    x = dt.char[,i,with=FALSE]
    analyze_char_var(x)
  })
  return(mylist)
}
analyze_all_char_l1 <- function(dt) {
  dt.char = dt[,sapply(dt,class)=="character", with=FALSE]
  mylist = lapply(1:length(dt.char), function(i) {
    analyze_char_var(dt.char[,i,with=FALSE])
  })
  return(mylist)
}
dtf <- function() {
  melt(dt.char <- Filter(is.character, dt), meas=names(dt.char))[, {
    tabula = setDT(list(value))[, .N, by="V1"][order(-N, V1)]
    .(
      NOBS  = .N,
      NNA   = sum(is.na(value)),
      NVALS = nrow(tabula),
      HIVAL = tabula$V1[1L],
      NHI   = tabula$N[1L]
    )
  }, by=variable]
}

analyze_all_char(dt)
analyze_all_char_l(dt)
analyze_all_char_l1(dt)
dtf()

library(microbenchmark)
mc <- microbenchmark(times=500,
  forloop = analyze_all_char(dt),
  lapply = analyze_all_char_l(dt),
  lapply1 = analyze_all_char_l1(dt),
  dtf = dtf()
)
mc

【讨论】:

  • 它有效,但看起来与我的 for 循环非常相似,我想知道它是否真的提高了性能。
  • 我刚刚包含了一些基准。使用lapply,您不会看到任何性能改进。实际上 lapply 有时也可能会更慢..
  • 感谢性能分析。 mc 块中有错字。它应该是 analyze_all_char_l 而不是 analyze_all_char_1
  • 谢谢,其实有两个错别字;)
猜你喜欢
  • 2019-06-29
  • 2021-07-26
  • 2012-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-01
  • 2014-03-20
  • 2011-10-23
相关资源
最近更新 更多