从数据框中删除所有值为 NA 的列

Posted

技术标签:

【中文标题】从数据框中删除所有值为 NA 的列【英文标题】:Remove columns from dataframe where ALL values are NA 【发布时间】:2011-02-08 07:01:37 【问题描述】:

我在使用数据框时遇到了问题,我自己无法真正解决该问题:数据框具有任意列属性每一行代表一个数据集

问题是: 如何删除 ALL 行的值为 NA 的列?

【问题讨论】:

【参考方案1】:

试试这个:

df <- df[,colSums(is.na(df))<nrow(df)]

【讨论】:

这会创建一个与旧对象大小相同的对象,这是大型对象的内存问题。最好使用一个函数来减小大小。下面使用 Filter 或使用 data.table 的答案将有助于您的内存使用。 这似乎不适用于非数字列。 如果列名重复则更改列名 要对非数字列执行此操作,@mnel 使用 Filter() 的解决方案是一个很好的解决方案。可以找到多种方法的基准in this post【参考方案2】:

到目前为止,提供的两种方法在处理大型数据集时都失败了,因为(以及其他内存问题)它们创建了is.na(df),这将是一个与df 大小相同的对象。

这里有两种更节省内存和时间的方法

一种使用Filter的方法

Filter(function(x)!all(is.na(x)), df)

以及使用 data.table 的方法(用于一般时间和内存效率)

library(data.table)
DT <- as.data.table(df)
DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]

使用大数据的示例(30 列,1e6 行)

big_data <- replicate(10, data.frame(rep(NA, 1e6), sample(c(1:8,NA),1e6,T), sample(250,1e6,T)),simplify=F)
bd <- do.call(data.frame,big_data)
names(bd) <- paste0('X',seq_len(30))
DT <- as.data.table(bd)

system.time(df1 <- bd[,colSums(is.na(bd) < nrow(bd))])
# error -- can't allocate vector of size ...
system.time(df2 <- bd[, !apply(is.na(bd), 2, all)])
# error -- can't allocate vector of size ...
system.time(df3 <- Filter(function(x)!all(is.na(x)), bd))
## user  system elapsed 
## 0.26    0.03    0.29 
system.time(DT1 <- DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F])
## user  system elapsed 
## 0.14    0.03    0.18 

【讨论】:

非常好。不过,你可以对data.frame 做同样的事情。这里没有什么真正需要data.table。关键是lapply,它避免了is.na(df)对整个对象的复制。 +10 指出这一点。 你会如何使用 data.frame? @matt-dowle @s_a, bd1 &lt;- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))] @mnel 我认为您需要在function(x) 之后删除, - 感谢您提供的示例 你能用 := 或 set() 更快吗?【参考方案3】:

更新

您现在可以将selectwhere 选择助手一起使用。 select_if 已被取代,但从 dplyr 1.0.2 开始仍然有效。 (感谢@mcstrother 提请注意)。

library(dplyr)
temp <- data.frame(x = 1:5, y = c(1,2,NA,4, 5), z = rep(NA, 5))
not_all_na <- function(x) any(!is.na(x))
not_any_na <- function(x) all(!is.na(x))

> temp
  x  y  z
1 1  1 NA
2 2  2 NA
3 3 NA NA
4 4  4 NA
5 5  5 NA

> temp %>% select(where(not_all_na))
  x  y
1 1  1
2 2  2
3 3 NA
4 4  4
5 5  5

> temp %>% select(where(not_any_na))
  x
1 1
2 2
3 3
4 4
5 5

旧答案

dplyr 现在有一个 select_if 动词,可能在这里有用:

> temp
  x  y  z
1 1  1 NA
2 2  2 NA
3 3 NA NA
4 4  4 NA
5 5  5 NA

> temp %>% select_if(not_all_na)
  x  y
1 1  1
2 2  2
3 3 NA
4 4  4
5 5  5

> temp %>% select_if(not_any_na)
  x
1 1
2 2
3 3
4 4
5 5

【讨论】:

来到这里寻找dplyr 解决方案。没有失望。谢谢! 我发现这有一个问题,它还会删除大多数但不是所有值都缺失的变量 select_if 现在在 dplyr 中被取代,所以最后两行在最新的语法中将是 temp %&gt;% select(where(not_all_na)) - 尽管 select_if 现在在 dplyr 1.0.2 中仍然有效。如果您不想在单独的行上定义函数,temp %&gt;% select(where(~!all(is.na(.x)))) 也可以使用。 @mcstrother 谢谢 - 这是对我的回答非常有用的更新。如果您想自己回答,我很乐意回滚编辑。 not_any_na 不适合我。这是从哪里来的?我有dplyr 加载.....【参考方案4】:

另一种方法是使用apply() 函数。

如果你有data.frame

df <- data.frame (var1 = c(1:7,NA),
                  var2 = c(1,2,1,3,4,NA,NA,9),
                  var3 = c(NA)
                  )

然后您可以使用 apply() 查看哪些列满足您的条件,因此您可以简单地执行与 Musa 的答案相同的子集,仅使用 apply 方法。

> !apply (is.na(df), 2, all)
 var1  var2  var3 
 TRUE  TRUE FALSE 

> df[, !apply(is.na(df), 2, all)]
  var1 var2
1    1    1
2    2    2
3    3    1
4    4    3
5    5    4
6    6   NA
7    7   NA
8   NA    9

【讨论】:

我预计这会更快,因为 colSum() 解决方案似乎做了更多的工作。但是在我的测试集上(之前的 1614 个变量中的 213 个,之后的 1377 个变量),它需要的时间正好是 3 倍。 (但是 +1 是一种有趣的方法。)【参考方案5】:

游戏晚了,但您也可以使用janitor 包。此函数将删除所有为 NA 的列,并且可以更改为删除所有为 NA 的行。

df &lt;- janitor::remove_empty(df, which = "cols")

【讨论】:

【参考方案6】:
df[sapply(df, function(x) all(is.na(x)))] <- NULL

【讨论】:

【参考方案7】:

purrr 包的另一个选项:

library(dplyr)

df <- data.frame(a = NA,
                 b = seq(1:5), 
                 c = c(rep(1, 4), NA))

df %>% purrr::discard(~all(is.na(.)))
df %>% purrr::keep(~!all(is.na(.)))

【讨论】:

【参考方案8】:

你可以使用 Janitor 包remove_empty

library(janitor)

df %>%
  remove_empty(c("rows", "cols")) #select either row or cols or both

另外,另一种 dplyr 方法

 library(dplyr) 
 df %>% select_if(~all(!is.na(.)))

df %>% select_if(colSums(!is.na(.)) == nrow(df))

如果您只想排除/保留具有一定数量缺失值的列,这也很有用,例如

 df %>% select_if(colSums(!is.na(.))>500)

【讨论】:

【参考方案9】:

一个老问题,但我认为我们可以用更简单的 data.table 解决方案更新@mnel 的好答案:

DT[, .SD, .SDcols = \(x) !all(is.na(x))]

(我正在使用 R>=4.1 中可用的新 \(x) lambda 函数语法,但真正关键的是通过 .SDcols 传递逻辑子集。

速度相当。

microbenchmark::microbenchmark(
  which_unlist = DT[, which(unlist(lapply(DT, \(x) !all(is.na(x))))), with=FALSE],
  sdcols       = DT[, .SD, .SDcols = \(x) !all(is.na(x))],
  times = 2
)
#> Unit: milliseconds
#>          expr      min       lq     mean   median       uq      max neval cld
#>  which_unlist 51.32227 51.32227 56.78501 56.78501 62.24776 62.24776     2   a
#>        sdcols 43.14361 43.14361 49.33491 49.33491 55.52621 55.52621     2   a

【讨论】:

【参考方案10】:

我希望这也能有所帮助。它可以做成一个命令,但我发现把它分成两个命令更容易阅读。我按照以下说明制作了一个函数,并且工作得很快。

naColsRemoval = function (DataTable) na.cols = DataTable [ , .( which ( apply ( is.na ( .SD ) , 2 , all ) ) )] DataTable [ , unlist (na.cols) := NULL , with = F]

.SD 将允许将验证限制在表格的一部分,如果您愿意,但它会将整个表格作为

【讨论】:

【参考方案11】:

一个方便的base R 选项可以是colMeans()

df[, colMeans(is.na(df)) != 1]

【讨论】:

【参考方案12】:

根据我在应用以前的答案时遇到问题的经验,我发现我需要修改他们的方法才能实现这里的问题:

如何删除所有行的值为 NA 的列?

首先请注意,我的解决方案仅在您没有重复列时才有效(该问题已处理 here (on stack overflow)

其次,它使用dplyr

代替

df <- df %>% select_if(~all(!is.na(.)))

我发现有效的是

df <- df %>% select_if(~!all(is.na(.)))

关键是“非”符号“!”需要在全称量词之外。 IE。 select_if 运算符作用于列。在这种情况下,它只选择那些满足标准的

每个元素都等于“NA”

【讨论】:

【参考方案13】:

janitor::remove_constant() 做得很好。

【讨论】:

janitor::remove_empty() 在这里更合适。 ?remove_empty = "从 data.frame 或矩阵中删除空行和/或列"

以上是关于从数据框中删除所有值为 NA 的列的主要内容,如果未能解决你的问题,请参考以下文章

如何删除R数据框中的列[重复]

R - 从数据框中删除空白[重复]

如何删除所有单元格在我指定的列中都有 NA 的 NA?

r 从数据框中删除给定的列

从scala中的数据框中删除不需要的列

从 R 中的数据框中删除重复的列组合