【问题标题】:R: data.table count !NA per rowR:data.table 计数 !NA 每行
【发布时间】:2016-02-10 03:51:45
【问题描述】:

我正在尝试计算每行不包含 NA 的列数,并将该值放入该行的新列中。

示例数据:

library(data.table)

a = c(1,2,3,4,NA)
b = c(6,NA,8,9,10)
c = c(11,12,NA,14,15)
d = data.table(a,b,c)

> d 
    a  b  c
1:  1  6 11
2:  2 NA 12
3:  3  8 NA
4:  4  9 14
5: NA 10 15

我想要的输出将包括一个新列 num_obs,其中包含每行非 NA 条目的数量:

    a  b  c num_obs
1:  1  6 11       3
2:  2 NA 12       2
3:  3  8 NA       2
4:  4  9 14       3
5: NA 10 15       2

我已经阅读了几个小时,到目前为止,我想出的最好的方法是循环遍历行,我知道这在 R 或 data.table 中是不可取的。我确信有更好的方法可以做到这一点,请赐教。

我的蹩脚方式:

len = (1:NROW(d))
for (n in len) {
  d[n, num_obs := length(which(!is.na(d[n])))]
}

【问题讨论】:

  • 喜欢d[, num_obs := sum(!is.na(.SD)), by = 1:nrow(d)][]d[, num_obs := rowSums(!is.na(d))][]? (不确定哪个会更快。)
  • 是的!这行得通。你能解释一下吗?如果我使用by = 1:nrow(d),我认为 .SD 将等于整个数据集。这是如何按行执行的?编辑:另外,添加空链[ ] 到底是做什么的?

标签: r data.table


【解决方案1】:

尝试使用Reduce+ 调用链接在一起:

d[, num_obs := Reduce(`+`, lapply(.SD,function(x) !is.na(x)))]

如果速度很关键,您可以参考 Ananda 的建议,对要评估的列数进行硬编码:

d[, num_obs := 4 - Reduce("+", lapply(.SD, is.na))]

从上面使用 Ananda 的较大 data.table d 进行基准测试:

fun1 <- function(indt) indt[, num_obs := rowSums(!is.na(indt))][]
fun3 <- function(indt) indt[, num_obs := Reduce(`+`, lapply(.SD,function(x) !is.na(x)))][]
fun4 <- function(indt) indt[, num_obs := 4 - Reduce("+", lapply(.SD, is.na))][]

library(microbenchmark)
microbenchmark(fun1(copy(d)), fun3(copy(d)), fun4(copy(d)), times=10L)

#Unit: milliseconds
#          expr      min       lq     mean   median       uq      max neval
# fun1(copy(d)) 3.565866 3.639361 3.912554 3.703091 4.023724 4.596130    10
# fun3(copy(d)) 2.543878 2.611745 2.973861 2.664550 3.657239 4.011475    10
# fun4(copy(d)) 2.265786 2.293927 2.798597 2.345242 3.385437 4.128339    10

【讨论】:

  • 不错的选择。加一。
  • 鉴于 OP 的描述:indt[, num_obs := 4 - Reduce("+", lapply(.SD, is.na))][],这可能会加快速度。我已经硬编码了“4”以避免按行重新计算。
  • @AnandaMahto - 它节省了更多时间 - 我已经更新了上面的答案。
  • @thelatemail - 如果这是一个愚蠢的问题,我很抱歉,但是你能告诉我这个问题是否正确吗? .SD 表示当前数据子集,如果 data.table 中的 i 位置没有给出参数,则 .SD 等于 当前行 的所有列的数据,而不是所有行
  • @Reilstein - 根本不是一个愚蠢的问题。如果i 中没有任何内容,我认为.SD 实际上代表整个data.table - 例如d[,dim(.SD)] 返回行数和列数。
【解决方案2】:

很快想到的两个选项是:

d[, num_obs := sum(!is.na(.SD)), by = 1:nrow(d)][]
d[, num_obs := rowSums(!is.na(d))][]

第一个方法是创建一个“组”,每组只有一行 (1:nrow(d))。没有它,它只会对整个表中的 NA 值求和。

第二个使用了已经非常高效的基本 R 函数,rowSums

这里是更大数据的基准:

set.seed(1)
nrow = 10000
ncol = 15
d <- as.data.table(matrix(sample(c(NA, -5:10), nrow*ncol, TRUE), nrow = nrow, ncol = ncol))

fun1 <- function(indt) indt[, num_obs := rowSums(!is.na(indt))][]
fun2 <- function(indt) indt[, num_obs := sum(!is.na(.SD)), by = 1:nrow(indt)][]

library(microbenchmark)
microbenchmark(fun1(copy(d)), fun2(copy(d)))
# Unit: milliseconds
#           expr        min         lq       mean     median         uq      max neval
#  fun1(copy(d))   3.727958   3.906458   5.507632   4.159704   4.475201 106.5708   100
#  fun2(copy(d)) 584.499120 655.634889 684.889614 681.054752 712.428684 861.1650   100

顺便说一句,空的[] 只是为了打印结果data.table。当您想要从“data.table”中的set* 函数返回输出时,这是必需的。

【讨论】:

  • 这太好了,谢谢@AnandaMahto!我对您如何使用1:nrow(d) 创建一组一行感到有些困惑。我最初的想法是,该语句将创建一个从第 1 行到文件最后一行的组。我显然错过了 by 语句的工作原理或其他内容。
  • 我的基准测试表明这又更快了:fun3 &lt;- function(indt) indt[, num_obs := Reduce(`+`, lapply(.SD,function(x) !is.na(x)))][]
  • 是的,在我更大的数据集上,大约 115 秒对 0.06 秒,所以有了很大的改进!感谢您添加此内容!
  • @Reilstein,出于好奇,您的较大数据集的维度是多少?
  • 我用于一些基准测试的“更大”数据集是 4 列,150 万行。我最终需要在大约 20 列、600 万行的东西上运行它,所以速度的提高是一个很大的帮助:)。
猜你喜欢
  • 1970-01-01
  • 2016-10-14
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
  • 1970-01-01
  • 2021-07-12
相关资源
最近更新 更多