NA oznacza "nieznane" i się rozprzestrzenia
Prawdziwe zbiory danych mają dziury: pytanie w ankiecie pozostawione bez odpowiedzi, czujnik, który zgubił odczyt. R oznacza każdą taką dziurę przez NA (not available, niedostępne). Kluczowy model myślowy: NA to nie zero, nie pusty napis i nie specjalna liczba. Oznacza "jest tu jakaś wartość, ale nie wiemy jaka."
Potraktuj to poważnie, a zachowanie R stanie się logiczne. Ile to nieznana liczba plus jeden? Nieznane. Jaka jest średnia z 4, 8 i czegoś nieznanego? Nieznana: brakująca wartość może być czymkolwiek, więc średnia też może być czymkolwiek:
Wszystkie trzy wypisują NA. Ta zaraźliwość to zaleta, a nie błąd: R odmawia cichego udawania, że zna odpowiedź, której nie zna. Arkusz kalkulacyjny, który po cichu pomija puste komórki, może ukryć fakt, że brakuje połowy kolumny. R zmusza cię, żeby to zauważyć i zdecydować, co brakujące wartości mają znaczyć dla twojej analizy. Reszta artykułu dotyczy podejmowania tej decyzji świadomie.
Sprawdzanie NA: is.na(), nigdy ==
Oto pierwsza pułapka, w którą wpada każdy. Chcesz znaleźć brakujące wartości, więc piszesz porównanie, a ono nie działa:
x == NA zwraca NA NA NA, ani jednego TRUE. Dlaczego? Idź za logiką "nieznanego": czy 4 równa się jakiejś nieznanej wartości? Nie wiadomo, bo nieznane może być równe 4. Nawet pozycja z NA porównuje się jako NA: czy jedno nieznane równa się drugiemu? Nieznane. Porównanie z NA nigdy nie zwróci TRUE ani FALSE, więc jako test jest bezużyteczne, a co gorsza, maska złożona z samych NA wewnątrz [ ] nie wybiera tego, czego się spodziewasz.
Właściwym narzędziem jest is.na(), które zbudowano dokładnie do tego pytania i które zwraca uczciwe wartości logiczne: FALSE TRUE FALSE. Z tego wynikają dwa idiomy, których będziesz używać stale:
sum(is.na(x)) liczy brakujące wartości (TRUE sumuje się jako 1): uruchom to na każdej kolumnie nowego zbioru danych, zanim zrobisz cokolwiek innego. which(is.na(x)) je lokalizuje. A x[!is.na(x)] zostawia tylko zaobserwowane wartości: ręczne usuwanie przez maskę logiczną, ten sam wzorzec filtrowania wektorów co zawsze.
Pomijanie NA w podsumowaniach: na.rm = TRUE
Zwykle nie musisz usuwać NA ręcznie, bo funkcje podsumowujące w R mają wbudowane wyjście awaryjne, czyli argument na.rm (NA remove):
Z na.rm = TRUE funkcja odrzuca brakujące wartości i liczy na tym, co zostało: średnia z 4 i 8 to 6. sum(), sd(), median(), min(), max(), var(): przyjmuje go cała rodzina funkcji statystyki opisowej.
Zauważ, że domyślna wartość to FALSE. To R ma zdanie i działa na twoją korzyść: chce, żeby ignorowanie brakujących danych było jawnym wyborem, który zapisujesz, a nie cichym ustawieniem domyślnym. Pisząc na.rm = TRUE, stwierdzasz, że "brakujące wartości można tu bezpiecznie zignorować". To prawda, gdy czujnik losowo zgubił kilka odczytów, i groźna nieprawda, gdy np. najmniej zarabiający pominęli pytanie o dochód. Argument sprawia, że ta decyzja należy do ciebie.
Usuwanie niepełnych wierszy: na.omit() i complete.cases()
W ramce danych braki siedzą w komórkach, ale analiza często działa wiersz po wierszu, więc typową operacją jest usunięcie wierszy, które mają jakiekolwiek NA:
na.omit(df) zwraca ramkę danych bez każdego wiersza zawierającego co najmniej jedno NA; tutaj zostaje tylko Rosa. complete.cases(df) zwraca logiczną maskę wierszy (TRUE FALSE FALSE) stojącą za tym samym pomysłem, a indeksowanie nią daje identyczny wynik z dwiema zaletami: najpierw możesz policzyć, co zaraz stracisz (sum(!complete.cases(df))), i możesz ograniczyć, które kolumny mają znaczenie. df[complete.cases(df[, "age"]), ] usuwa tylko wiersze bez age i zostawia Mię, choć jej wynik jest nieznany.
Ta wersja ograniczona do kolumn ma większe znaczenie, niż się wydaje: na.omit() na szerokiej ramce danych może po cichu wyrzucić większość wierszy z powodu NA w kolumnach, których wcale nie zamierzasz analizować. Zanim usuniesz wiersze, wiedz, ile ich usuwasz i dlaczego.
Zastępowanie NA
Czasem właściwym ruchem jest wypełnienie dziur, a nie usuwanie wierszy. Idiom łączy is.na() z przypisaniem:
Czytaj to jako: "na pozycjach, gdzie brakuje visits, wstaw 0". Jest to uprawnione dokładnie wtedy, gdy NA koduje znaną wartość: klient bez zapisu o wizytach naprawdę miał zero wizyt.
Trzeba jednak uczciwie ocenić, kiedy to prawda. Jeśli NA znaczy "nie udało się tego zmierzyć", wstawienie 0 fabrykuje dane: zastąpienie brakujących wyników testu zerami ściąga średnią w dół, jakby ci uczniowie zdobyli zero punktów, podczas gdy w rzeczywistości nie wiesz, ile zdobyli. Porównaj powyższą średnią (2.4) ze średnią z na.rm dla oryginału (4): te same dane, różne twierdzenia. Zastępowanie średnią albo medianą zniekształca mniej, ale nadal zaniża zmienność. Zasada: wstawiaj wartość tylko wtedy, gdy potrafisz prostymi słowami powiedzieć, dlaczego właśnie taka była brakująca pozycja. W przeciwnym razie zostaw NA i używaj na.rm: "nieznane" to często najbardziej prawdziwa wartość w zbiorze danych.
NA kontra NULL kontra NaN kontra Inf
R ma cztery podobnie wyglądające wartości specjalne, które znaczą naprawdę różne rzeczy:
| Wartość | Znaczenie | Długość | Typowe źródło |
|---|---|---|---|
NA | Wartość istnieje, ale jest nieznana | 1 (zajmuje miejsce) | Brakujące dane |
NULL | Brak jakiegokolwiek obiektu | 0 (brak miejsca) | Usunięty element listy, pusty wynik |
NaN | Działanie z nieokreślonym wynikiem | 1 | 0 / 0, log(-1) |
Inf | Liczba poza zakresem reprezentacji | 1 | 1 / 0, przepełnienie |
Rozróżnienia, które mają znaczenie w praktyce: NULL znika w wektorach (c(1, NULL, 3) ma dwa elementy, więc nie może reprezentować brakującej obserwacji), a NA zachowuje swoje miejsce. NaN liczy się jako brak (is.na(NaN) to TRUE, więc na.rm też je usuwa), ale odwrotnie to nie działa: is.nan(NA) to FALSE. A Inf nie jest brakiem: to prawdziwa, porównywalna liczba (Inf > 1e300 to TRUE), więc na.rm jej nie usunie. Do odfiltrowania zwykłych liczb użyj is.finite().
Dla kompletności: NA po cichu występuje w wersjach z typem (NA_integer_, NA_real_, NA_character_), żeby mogło stać w dowolnym wektorze bez łamania zasady jednego typu. Rzadko będziesz je wpisywać, ale zobaczysz je w kodzie pakietów i w komunikatach o błędach dplyr.
Co warto zapamiętać
NAoznacza "nieznane", a nieznane jest zaraźliwe: każde obliczenie, które dotyka NA, zwraca NA, i tak to zaprojektowano.- Sprawdzaj przez
is.na(), nigdy przez== NA; licz dziury przezsum(is.na(x)). na.rm = TRUEsprawia, że funkcje podsumowujące pomijają NA: to jawna decyzja przy każdym wywołaniu, że brakujące wartości można zignorować.na.omit()usuwa niepełne wiersze hurtowo;complete.cases()daje maskę, którą można policzyć i ograniczyć do ważnych kolumn.- Zastępuj NA (
x[is.na(x)] <- value) tylko wtedy, gdy potrafisz uzasadnić, jaka naprawdę była brakująca wartość. - NA ≠ NULL ≠ NaN ≠ Inf: brakująca wartość, nieobecny obiekt, nieokreślone działanie, nieograniczona liczba.
Dalej: funkcje, czyli pakowanie logiki w nazwane kawałki wielokrotnego użytku.
Najczęściej zadawane pytania
Dlaczego mean() zwraca NA w R?
Bo co najmniej jedna wartość w wektorze to NA, a NA jest zaraźliwe: jeśli którekolwiek wejście jest nieznane, R mówi, że odpowiedź też jest nieznana. Przekaż na.rm = TRUE, czyli mean(x, na.rm = TRUE), żeby policzyć średnią z obecnych wartości. Większość funkcji podsumowujących (sum, sd, median, min, max) przyjmuje ten sam argument.
Jak sprawdzić, czy w R jest NA?
Przez is.na(x), które zwraca TRUE wszędzie tam, gdzie brakuje wartości. Nigdy nie sprawdzaj przez x == NA: porównanie czegokolwiek z nieznanym daje NA, a nie TRUE ani FALSE, więc test po cichu zawodzi. sum(is.na(x)) liczy brakujące wartości, a which(is.na(x)) znajduje ich pozycje.
Jak usunąć wiersze z NA z ramki danych w R?
na.omit(df) usuwa każdy wiersz zawierający co najmniej jedno NA. Dla większej kontroli complete.cases(df) zwraca wektor logiczny oznaczający w pełni obserwowane wiersze, więc df[complete.cases(df), ] robi to samo jawnie. Możesz to też zastosować tylko do kolumn, które mają znaczenie, np. df[complete.cases(df[, c("age", "score")]), ].
Czym różni się NA od NULL w R?
NA to brakująca wartość: zajmuje miejsce w wektorze i ma długość 1. NULL to brak obiektu: ma długość 0 i znika, gdy włożysz go do wektora, więc c(1, NULL, 3) ma tylko dwa elementy. Używaj NA dla brakującego punktu danych; NULL pojawia się przy usuwaniu elementów listy albo jako pusty wynik.