Видаліть стовпці з фрейму даних, де ВСІ значення є NA


149

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

Питання:
Як позбутися стовпців, де для ВСІХ рядків значення NA ?

Відповіді:


155

Спробуйте це:

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

3
Це створює об'єкт розміром із старим об'єктом, що є проблемою з пам'яттю на великих об'єктах. Краще використовувати функцію зменшення розміру. Нижче наведено відповіді за допомогою фільтра або за допомогою даних datatable.
mtelesha

3
Здається, це не працює з нечисловими стовпцями.
verbamour

Він змінює назву стовпця, якщо вони дублюються
Peter.k

97

Два запропоновані підходи поки що не спрацьовують з великими наборами даних, оскільки (серед інших проблем із пам'яттю) вони створюються is.na(df), що буде об'єктом такого ж розміру, як df.

Ось два підходи, які ефективніше пам’яті та часом

Підхід із використанням Filter

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

і підхід із використанням даних.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)- дякую за приклад btw
Thieme Hennis

1
Чи можна це зробити швидше за допомогою: = або з набором ()?
скан

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 роки до вас ... Дивіться відповідь Мнеля нижче.
Андре.B

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

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