Удалить столбцы из кадра данных, где ВСЕ значения равны NA


149

У меня возникли проблемы с кадром данных и не может реально решить эту проблему сам: dataframe имеет произвольные свойства, столбцов и каждая строка представляет собой один набор данных .

Вопрос в том,
как избавиться от столбцов, где для ВСЕХ строк значение равно NA ?

Ответы:


155

Попробуй это:

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

3
Это создает объект размером с старый объект, что является проблемой с памятью для больших объектов. Лучше использовать функцию для уменьшения размера. Ответ ниже с помощью Filter или data.table поможет использовать вашу память.
Мтелеша

3
Это не похоже на работу с нечисловыми столбцами.
17

Он меняет имя столбца, если они дублируются
Peter.k

97

Два подхода, предложенных до сих пор, терпят неудачу с большими наборами данных, которые (среди прочих проблем с памятью) они создают 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 

6
Очень хорошо. Вы можете сделать то же самое с data.frame, хотя. Здесь нет ничего, что действительно нужно data.table. Ключ - это то lapply, что позволяет избежать копирования всего объекта is.na(df). +10 за указание на это.
Мэтт Доул

1
Как бы вы сделали это с data.frame? @ matt-dowle
s_a

8
@s_a, bd1 <- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
mnel

6
@mnel Я думаю, что вам нужно удалить ,после function(x)- спасибо за пример, кстати
Thieme Hennis

1
Можете ли вы сделать это быстрее с: = или с помощью set ()?
Скан

49

dplyrтеперь есть select_ifглагол, который может быть полезен здесь:

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_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решения. Не был разочарован. Спасибо!
Андрей Бреза

Я обнаружил, что проблема заключалась в том, что он также удалял переменные, у которых большинство, но не все значения отсутствовали
MBorg

15

Другим способом было бы использовать 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()чтобы увидеть, какие столбцы соответствуют вашему условию, и вы можете просто выполнить то же подмножество, что и в ответе Мусы, только с 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

3
Я ожидал, что это будет быстрее, поскольку решение colSum (), кажется, делает больше работы. Но в моем тестовом наборе (213 наблюдений из 1614 переменных до 1377 и после), это занимает ровно в 3 раза больше времени. (Но +1 для интересного подхода.)
Даррен Кук

10

Поздно к игре, но вы также можете использовать janitorпакет. Эта функция удалит все столбцы, которые являются NA, и может быть изменена для удаления строк, которые также являются NA.

df <- janitor::remove_empty(df, which = "cols")



4

Принятый ответ не работает с нечисловыми столбцами. Из этого ответа следующее работает со столбцами, содержащими разные типы данных

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

Кто-то уже опубликовал тот же ответ в этой теме за 4 года до вас ... Смотрите ответ mnel ниже.
Андре Б.

2

Другие варианты с 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(.)))

1

Я надеюсь, что это также может помочь. Это может быть сделано в одну команду, но мне было легче читать, разделив его на две команды. Я сделал функцию со следующей инструкцией и работал молниеносно.

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

При желании .SD позволит ограничить проверку частью таблицы, но при этом вся таблица будет считаться


1

Удобный base Rвариант может быть colMeans():

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

0

Вы можете использовать пакет Дворник 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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.