Jak usunąć kolumny zawierające TYLKO NA?


83

Mam plik data.frame zawierający kilka kolumn ze wszystkimi wartościami NA, jak mogę je usunąć z data.frame.

Czy mogę skorzystać z funkcji

na.omit(...) 

określając dodatkowe argumenty?


1
Cześć! Postaraj się, aby Twój post był powtarzalny. Przeczytaj post, jak zrobić wspaniały, powtarzalny przykład, jak to zrobić. Dziękuję Ci.
Arun


można zakładać head(data)? Czy chcesz usunąć odpowiednie kolumny lub wiersze?
Nishanth,

@ e4e5f4 Chcę usunąć odpowiednie kolumny (wszystkie wartości kolumn, które chcę usunąć, to NA)
Lorenzo Rigamonti Kwietnia

Odpowiedzi:


123

Jeden sposób na zrobienie tego:

df[, colSums(is.na(df)) != nrow(df)]

Jeśli liczba NA w kolumnie jest równa liczbie wierszy, musi to być w całości NA.

Lub podobnie

df[colSums(!is.na(df)) > 0]

1
Jak mogę usunąć kolumny mające więcej niż próg NA? czy w procentach (powiedzmy powyżej 50%)?
discipulus

2
@lovedynasty Prawdopodobnie najlepiej przesłać osobne pytanie, zakładając, że nie zostało to jeszcze zrobione od czasu opublikowania komentarza. Ale tak czy inaczej, zawsze możesz zrobić coś takiego, jak df[, colSums(is.na(df)) < nrow(df) * 0.5]np. Zachować tylko kolumny zawierające co najmniej 50% wartości niepustych.
Ciarán Tobin

2
Osoby pracujące z macierzą korelacji muszą używać, df[, colSums(is.na(df)) != nrow(df) - 1]ponieważ przekątna to zawsze1
Boern

9
Można tego również użyć z funkcją dplyr (wersja 0.5.0) select_if. df %>% select_if(colSums(!is.na(.)) > 0)
Stefan Avey

@MadScone daje mi błąd składni na "," dla df [, colSums (is.na (df))! = Nrow (df)] i błąd składni na "!" w df [colSums (! is.na (df))> 0]. Czy czegoś mi brakuje
Aravind S

53

Oto rozwiązanie dplyr:

df %>% select_if(~sum(!is.na(.)) > 0)

Aktualizacja:summarise_if() funkcja została zastąpiona z dniem dplyr 1.0. Oto dwa inne rozwiązania, które wykorzystują funkcję where()tidyselect:

df %>% 
  select(
    where(
      ~sum(!is.na(.x)) > 0
    )
  )
df %>% 
  select(
    where(
      ~!all(is.na(.x))
    )
  )

4
Przy ~ 15 tysiącach wierszy i ~ 5 tysiącach kolumn to naprawdę trwa wieczność.
EngrStudent

@EngrStudent Czy z rozwiązaniem zaakceptowanej odpowiedzi było szybciej?
johnny

Minęło wiele lat. Nie pamiętam. DJV ma poniżej niezły wpis dotyczący synchronizacji.
EngrStudent


24

Wygląda na to, że chcesz usunąć TYLKO kolumny ze WSZYSTKIMI NA s, pozostawiając kolumny z wierszami, które mają NAs. Zrobiłbym to (ale jestem pewien, że istnieje wydajne rozwiązanie wektoryzowane:

#set seed for reproducibility
set.seed <- 103
df <- data.frame( id = 1:10 , nas = rep( NA , 10 ) , vals = sample( c( 1:3 , NA ) , 10 , repl = TRUE ) )
df
#      id nas vals
#   1   1  NA   NA
#   2   2  NA    2
#   3   3  NA    1
#   4   4  NA    2
#   5   5  NA    2
#   6   6  NA    3
#   7   7  NA    2
#   8   8  NA    3
#   9   9  NA    3
#   10 10  NA    2

#Use this command to remove columns that are entirely NA values, it will elave columns where only some vlaues are NA
df[ , ! apply( df , 2 , function(x) all(is.na(x)) ) ]
#      id vals
#   1   1   NA
#   2   2    2
#   3   3    1
#   4   4    2
#   5   5    2
#   6   6    3
#   7   7    2
#   8   8    3
#   9   9    3
#   10 10    2

Jeśli znajdziesz się w sytuacji, w której chcesz usunąć kolumny, które mają jakiekolwiek NAwartości, możesz po prostu zmienić allpowyższe polecenie na any.


Data.frame posiada dwa rodzaje kolumn: jeden w whohc wszystkie wartości są liczbami, a drugi, w którym wszystkie wartości są NA
Lorenzo Rigamonti

Więc to zadziała. Usuwa tylko kolumny, w których są WSZYSTKIE wartości NA.
Simon O'Hanlon,

1
Dobre rozwiązanie. Zrobiłbym to apply(is.na(df), 1, all)tylko dlatego, że jest nieco schludniejszy i is.na()jest używany we wszystkich dfwierszach, a nie w jednym na raz (pokaż trochę szybciej).
Ciarán Tobin

@MadScone dobra wskazówka - wygląda schludniej. Należy jednak zastosować w kolumnach, a nie wierszach.
Simon O'Hanlon

@MadScone Edycje są blokowane po 5 minutach od komentarzy. Nie powinienem się martwić, to nic wielkiego !! :-)
Simon O'Hanlon

20

Intuicyjny skrypt: dplyr::select_if(~!all(is.na(.))). Dosłownie zachowuje tylko kolumny, w których brakuje nie wszystkich elementów. (aby usunąć wszystkie brakujące kolumny).

> df <- data.frame( id = 1:10 , nas = rep( NA , 10 ) , vals = sample( c( 1:3 , NA ) , 10 , repl = TRUE ) )

> df %>% glimpse()
Observations: 10
Variables: 3
$ id   <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
$ nas  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA
$ vals <int> NA, 1, 1, NA, 1, 1, 1, 2, 3, NA

> df %>% select_if(~!all(is.na(.))) 
   id vals
1   1   NA
2   2    1
3   3    1
4   4   NA
5   5    1
6   6    1
7   7    1
8   8    2
9   9    3
10 10   NA

17

Inna opcja z Filter

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

UWAGA: Dane z posta @Simon O'Hanlon.


5

Ponieważ wydajność była dla mnie naprawdę ważna, przetestowałem wszystkie powyższe funkcje.

UWAGA: Dane z posta @Simon O'Hanlon. Tylko w rozmiarze 15000 zamiast 10.

library(tidyverse)
library(microbenchmark)

set.seed(123)
df <- data.frame(id = 1:15000,
                 nas = rep(NA, 15000), 
                 vals = sample(c(1:3, NA), 15000,
                               repl = TRUE))
df

MadSconeF1 <- function(x) x[, colSums(is.na(x)) != nrow(x)]

MadSconeF2 <- function(x) x[colSums(!is.na(x)) > 0]

BradCannell <- function(x) x %>% select_if(~sum(!is.na(.)) > 0)

SimonOHanlon <- function(x) x[ , !apply(x, 2 ,function(y) all(is.na(y)))]

jsta <- function(x) janitor::remove_empty(x)

SiboJiang <- function(x) x %>% dplyr::select_if(~!all(is.na(.)))

akrun <- function(x) Filter(function(y) !all(is.na(y)), x)

mbm <- microbenchmark(
  "MadSconeF1" = {MadSconeF1(df)},
  "MadSconeF2" = {MadSconeF2(df)},
  "BradCannell" = {BradCannell(df)},
  "SimonOHanlon" = {SimonOHanlon(df)},
  "SiboJiang" = {SiboJiang(df)},
  "jsta" = {jsta(df)}, 
  "akrun" = {akrun(df)},
  times = 1000)

mbm

Wyniki:

Unit: microseconds
         expr    min      lq      mean  median      uq      max neval  cld
   MadSconeF1  154.5  178.35  257.9396  196.05  219.25   5001.0  1000 a   
   MadSconeF2  180.4  209.75  281.2541  226.40  251.05   6322.1  1000 a   
  BradCannell 2579.4 2884.90 3330.3700 3059.45 3379.30  33667.3  1000    d
 SimonOHanlon  511.0  565.00  943.3089  586.45  623.65 210338.4  1000  b  
    SiboJiang 2558.1 2853.05 3377.6702 3010.30 3310.00  89718.0  1000    d
         jsta 1544.8 1652.45 2031.5065 1706.05 1872.65  11594.9  1000   c 
        akrun   93.8  111.60  139.9482  121.90  135.45   3851.2  1000 a


autoplot(mbm)

wprowadź opis obrazu tutaj

mbm %>% 
  tbl_df() %>%
  ggplot(aes(sample = time)) + 
  stat_qq() + 
  stat_qq_line() +
  facet_wrap(~expr, scales = "free")

wprowadź opis obrazu tutaj


Czasami pierwsza iteracja jest skompilowana w JIT, więc ma bardzo słabe i niezbyt charakterystyczne czasy. Myślę, że interesujące jest to, co większy rozmiar próbki robi z właściwymi ogonami rozkładu. To dobra robota.
EngrStudent

Uruchomiłem go jeszcze raz, nie byłem pewien, czy zmieniłem fabułę. Jeśli chodzi o dystrybucję, rzeczywiście. Powinienem prawdopodobnie porównać różne rozmiary próbek, kiedy będę miał czas.
DJV

1
jeśli qqplot ( ggplot2.tidyverse.org/reference/geom_qq.html ) jeden z trendów, taki jak „akrun”, to założę się, że jest jeden punkt, który bardzo różni się od dystrybucji pozostałych. Reszta przedstawia, ile czasu potrzeba, jeśli uruchomisz go wielokrotnie, ale to pokazuje, co się stanie, jeśli uruchomisz go raz. Jest takie stare powiedzenie: możesz mieć 20-letnie doświadczenie lub możesz mieć tylko roczne doświadczenie 20 razy.
EngrStudent

bardzo dobrze! Jestem zaskoczony, że kilka próbek znajduje się w skrajnym ogonie. Zastanawiam się, dlaczego są one o wiele droższe. JIT może wynosić 1 lub 2, ale nie 20. Warunek? Przerwania? Inny? Jeszcze raz dziękuję za aktualizację.
EngrStudent

Nie ma za co, dziękuję za przemyślenia. Nie wiem, faktycznie pozwoliłem mu działać „swobodnie”.
DJV
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.