select 数据帧中非 NA 数据范围的简单有效方法

Simple and efficient way to select non-NA data range in data frames

假设您有以下数据框:

dat <- data.frame(a = c(1:3, NA), b = c(letters[1:3], NA), c = NA)

> dat
   a    b  c
1  1    a NA
2  2    b NA
3  3    c NA
4 NA <NA> NA

如何以非常有效的方式 select 非 NA 区域?

这是我目前使用的:

ensureNonNaRange <- function(dat) {
  idx_col <- ! sapply(dat, function(ii) all(is.na(ii)))
  idx_row <- ! sapply(1:nrow(dat), function(ii) all(is.na(unlist(dat[ii, ]))))
  dat[idx_row, idx_col]
}

> ensureNonNaRange(dat)
  a b
1 1 a
2 2 b
3 3 c

因为直到今天我才被指出我以前不知道的非常有用的函数 type.convert,我认为在 base R 中可能也存在一些像这个任务一样的东西 "of-the-shelf"。

更新

基于我得到的answers/comments的一些比较:

ensureNonNaRange2 <- function(dat) {
  dat[rowSums(!is.na(dat)) != 0, colSums(!is.na(dat)) != 0]
}

microbenchmark::microbenchmark(
  a = ensureNonNaRange(dat),
  b = ensureNonNaRange2(dat)
)

Unit: microseconds
 expr     min       lq     mean   median       uq     max neval
    a 296.178 310.1070 346.2259 329.0210 349.9875 680.035   100
    b 112.313 120.0845 134.1716 125.6555 133.7200 338.112   100

虽然可能还有一些内置函数可以执行此操作,但您可以通过子集化来完成。

is.na 传递整个 data.frame 时,它会生成一个布尔掩码,因此如果您对 !is.na(dat) 的行和列求和(即添加 TRUE not NA) 的值,对于具有 only NAs 的行和列,您得到的总和为零.

因此,如果我们在行和列总和为 != 0 时进行子集化,我们将剩下具有非 NA 值的行和列:

> dat[rowSums(!is.na(dat)) != 0, colSums(!is.na(dat)) != 0]
  a b
1 1 a
2 2 b
3 3 c

如果行或列中的某些值(但不是所有值)为 NA,则此方法会留下 row/column:

> dat[2,2] <- NA
> dat[rowSums(!is.na(dat)) != 0, colSums(!is.na(dat)) != 0]
  a    b
1 1    a
2 2 <NA>
3 3    c

(如果您更愿意将 rows/columns 替换为 any NA,请调整感叹号,或使用 complete.cases。)

此外,它应该非常快,因为 rowSumscolSums 是非常高度优化的,所以它应该仍然可以快速处理大型数据结构。