Usuń kolumny z ramki danych, w których WSZYSTKIE wartości to NA

149

Mam problemy z ramki danych i nie można było rozwiązać tę kwestię sobie: dataframe ma arbitralnych właściwości jak kolumny i każdy wiersz reprezentuje jeden zestaw danych .

Pytanie brzmi:
jak pozbyć się kolumn, w których dla WSZYSTKICH wierszy wartością jest NA ?

Gnark
źródło

Odpowiedzi:

155

Spróbuj tego:

df <- df[,colSums(is.na(df))<nrow(df)]
teucer
źródło
3
Tworzy to obiekt wielkości starego obiektu, który jest problemem z pamięcią na dużych obiektach. Lepiej jest użyć funkcji, aby zmniejszyć rozmiar. Odpowiedź poniżej, używając filtru lub używając data.table, pomoże w zużyciu pamięci.
mtelesha
3
Wydaje się, że nie działa to w przypadku kolumn nienumerycznych.
verbamour
Zmienia nazwę kolumny, jeśli są zduplikowane
Peter.k
97

Dwa dotychczas oferowane podejścia zawodzą w przypadku dużych zestawów danych, ponieważ (między innymi problemami z pamięcią), które tworzą is.na(df), który będzie obiektem tego samego rozmiaru co df.

Oto dwa podejścia, które są bardziej wydajne pod względem pamięci i czasu

Podejście wykorzystujące Filter

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

i podejście wykorzystujące data.table (dla ogólnego czasu i wydajności pamięci)

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

przykłady wykorzystujące duże dane (30 kolumn, 1e6 wierszy)

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 
mnel
źródło
6
Bardzo dobrze. data.frameAle możesz zrobić to samo . Nie ma tu niczego, co naprawdę potrzebuje data.table. Kluczem jest ten lapply, który pozwala uniknąć kopiowania całego obiektu wykonanego przez is.na(df). +10 za wskazanie tego.
Matt Dowle
1
Jak byś to zrobił z data.frame? @ matt-dowle
s_a
8
@s_a, bd1 <- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
mnel
6
@mnel Myślę, że musisz usunąć ,po function(x)- dzięki za przykład btw
Thieme Hennis
1
Czy możesz to zrobić szybciej za pomocą: = lub set ()?
skan
49

dplyrteraz ma select_ifczasownik, który może być tutaj pomocny:

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
Zack
źródło
Przyszedłem tutaj, szukając dplyrrozwiązania. Nie zawiodłem się. Dzięki!
Andrew Brēza
Zauważyłem, że ma to problem polegający na tym, że usuwa również zmienne, w których brakuje większości, ale nie wszystkich
MBorg
15

Innym sposobem byłoby użycie apply()funkcji.

Jeśli masz plik data.frame

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

następnie możesz użyć, apply()aby zobaczyć, które kolumny spełniają twój warunek, dzięki czemu możesz po prostu wykonać to samo podzbiór, co w odpowiedzi Musy, tylko z applypodejściem.

> !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
mropa
źródło
3
Spodziewałem się, że będzie to szybsze, ponieważ rozwiązanie colSum () wydawało się wykonywać więcej pracy. Ale w moim zestawie testowym (213 obs. 1614 zmiennych wcześniej, w porównaniu z 1377 zmiennymi później) trwa to dokładnie 3 razy dłużej. (Ale +1 za ciekawe podejście.)
Darren Cook,
10

Spóźniony do gry, ale możesz też skorzystać z janitorpakietu. Ta funkcja usunie kolumny, które są wszystkie na NA, i można ją zmienić, aby usunąć również wiersze, które są również NA.

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

André.B
źródło
5
df[sapply(df, function(x) all(is.na(x)))] <- NULL
jpmorris
źródło
4

Zaakceptowana odpowiedź nie działa w przypadku kolumn nienumerycznych. Na podstawie tej odpowiedzi poniższe działa z kolumnami zawierającymi różne typy danych

Filter(function(x) !all(is.na(x)), df)
jeromeResearch
źródło
Ktoś inny zamieścił już tę samą odpowiedź w tym wątku 4 lata przed tobą ... Zobacz odpowiedź mnella poniżej.
André.B
2

Inne opcje z purrrpakietem:

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(.)))
AlexB
źródło
1

Mam nadzieję, że to też może pomóc. Można go przekształcić w jedno polecenie, ale łatwiej było mi go czytać, dzieląc go na dwa polecenia. Wykonałem funkcję zgodnie z poniższą instrukcją i działałem błyskawicznie.

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

.SD pozwoli ograniczyć weryfikację do części tabeli, jeśli chcesz, ale zajmie całą tabelę jako

Luis M. Nieves
źródło
1

Przydatną base Ropcją może być colMeans():

df[, colMeans(is.na(df)) != 1]
tmfmnk
źródło
0

Możesz skorzystać z pakietu Janitor remove_empty

library(janitor)

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

Również inne podejście do dostawcy

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

LUB

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

jest to również przydatne, jeśli chcesz wykluczyć / zachować tylko kolumnę z pewną liczbą brakujących wartości, np

 df %>% select_if(colSums(!is.na(.))>500)
Ashish Singhal
źródło