Jak czytać komunikat o błędzie w R
Błąd w R składa się z dwóch części i obie są przydatne:
Error in "10" + 5 : non-numeric argument to binary operator
Po Error in pojawia się wywołanie, czyli dokładny fragment kodu, który zawiódł ("10" + 5). Po dwukropku pojawia się warunek, czyli co poszło nie tak (non-numeric argument to binary operator). Najpierw przeczytaj wywołanie: mówi gdzie, a bardzo często problem widać od razu w cytowanym kodzie. Potem przeczytaj warunek, żeby poznać dlaczego.
Dwa nawyki odróżniają osoby, które debugują szybko, od tych, które się męczą. Po pierwsze, naprawdę czytaj komunikat: komunikaty R są zwykle precyzyjne, tylko zwięźle sformułowane. Po drugie, debuguj pierwszy błąd, nie ostatni: jedna awaria na początku skryptu wywołuje lawinę późniejszych błędów "object not found", które znikają wszystkie naraz, gdy naprawisz ten pierwszy. Reszta tej strony to słownik komunikatów, które spotkasz najczęściej, a potem narzędzia na sytuacje, gdy samo czytanie nie wystarcza.
Błędy nazw: not found
Error: object 'total' not found: R przeszukał każde znane środowisko i żadna zmienna nie ma tej nazwy. Trzy przyczyny wyjaśniają prawie każdy przypadek:
- Literówka, także w wielkości liter. R rozróżnia wielkość liter:
Total,totaliTOTALto trzy różne nazwy, a R nie zgadnie, o którą chodzi. - Linia definiująca nie została uruchomiona. W skrypcie jest
total <- sum(x), ale ta linia nie została wykonana w tej sesji. To częste po restarcie R: plik skryptu wciąż pokazuje linię, ale sesja nigdy jej nie widziała. Uruchom skrypt od początku. - Złe środowisko. Zmienne utworzone wewnątrz funkcji żyją i giną razem z tym wywołaniem. Użycie takiej zmiennej poza funkcją to prośba o coś, co już nie istnieje; zamiast tego zwróć wartość.
Error: could not find function "read_excel": ten sam pomysł, ale dla nazwy funkcji. W dziewięciu przypadkach na dziesięć funkcja należy do pakietu, który jest zainstalowany, ale nie został wczytany w tej sesji:
library(readxl) # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")
Jeśli samo library(readxl) zgłasza błąd, pakiet nie jest zainstalowany: najpierw install.packages("readxl"). A jeśli funkcja pochodzi z bazowego R, to literówka (lenght() to rytuał przejścia każdego programisty R).
Błędy składni: unexpected symbol
Error: unexpected symbol in "..." (i jego kuzyni unexpected ')', unexpected string constant) oznacza, że R nie potrafił nawet sparsować kodu. Komunikat wskazuje miejsce, w którym R zauważył problem, a to często leży za miejscem faktycznego błędu. Typowi podejrzani:
mean(x na.rm = TRUE) # missing comma - should be mean(x, na.rm = TRUE)
name <- "Ada # unclosed quote - swallows the following lines
total <- sum(c(1, 2, 3) # unclosed paren - the error fires lines later
Gdy wskazana linia wygląda niewinnie, błąd prawie zawsze leży powyżej: niezamknięty cudzysłów, nawias okrągły lub klamrowy wcześniej w pliku. Edytor kodu, który podświetla pasujące pary, znajdzie to w kilka sekund.
Błędy typów i indeksowania
non-numeric argument to binary operator: wykonujesz obliczenia na czymś, co nie jest liczbą, zwykle na liczbie, która przyszła jako tekst (klasycznym źródłem są importy: kolumna z jednym zabłąkanym słowem wczytuje się jako character, co opisują typy danych). Wersja z błędem:
x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator
I poprawka: najpierw konwertuj, potem licz:
subscript out of bounds: prosisz przez [[ ]] o pozycję n w czymś, co ma mniej niż n elementów:
scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds
Sprawdź length() przed indeksowaniem albo, jeszcze lepiej, pytaj po nazwie (scores[["grace"]]), żeby zmiana kolejności niczego nie zepsuła. Zwróć uwagę na asymetrię: pojedyncze nawiasy są bardziej wyrozumiałe, bo [ ] poza zakresem wektora po cichu zwraca NA zamiast błędu. To zamiana głośnego błędu na cichy.
$ operator is invalid for atomic vectors: $ należy do list i ramek danych. Na nazwanym wektorze używaj nawiasów:
([[ ]] daje samą wartość; [ ] zachowuje dołączoną nazwę.) Ten błąd często oznacza, że coś wcześniej zwróciło wektor, a spodziewana była ramka danych. Sprawdź to założenie, zamiast po prostu zmieniać operator.
argument is of length zero: if () dostał pusty warunek, prawie zawsze NULL, który wkradł się z brakującego elementu listy albo z funkcji, która nic nie zwróciła:
threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero
Zabezpiecz sprawdzenie. Zwróć uwagę, że && przestaje obliczać, gdy tylko odpowiedź jest znana, więc porównanie nigdy nie wykona się na NULL:
(Pokrewny błąd, missing value where TRUE/FALSE needed, to ta sama awaria z NA zamiast NULL. Zabezpieczeniem jest tam is.na(), opisane w brakujących wartościach.)
replacement has length zero: wersja tej samej choroby przy przypisaniu: x[2] <- numeric(0) próbuje wypełnić jedno miejsce zerem wartości. To, co wyprodukowało prawą stronę, wróciło puste; debuguj to, a nie przypisanie.
Ostrzeżenia to nie błędy i właśnie w tym tkwi niebezpieczeństwo
Błąd zatrzymuje wykonanie; ostrzeżenie nie. R kończy obliczenia, oddaje wynik i dopiero potem wspomina o swoich zastrzeżeniach. Taki wynik bywa w porządku, a bywa po cichu błędny:
Obie linie się wykonują. Pierwsza powiela krótszy wektor i ostrzega longer object length is not a multiple of shorter object length, a powielanie wektora o długości 2 względem wektora o długości 3 prawie nigdy nie jest zamierzone. Druga ostrzega NAs introduced by coercion i zwraca wektor z dziurą, przez którą każde późniejsze mean() zwróci NA. Traktuj oba ostrzeżenia jak błędy do zbadania, a nie szum do przewinięcia. W skryptach możesz wymusić takie podejście przez options(warn = 2), co zamienia każde ostrzeżenie w błąd, więc nic się nie prześlizgnie.
Obsługa awarii przez tryCatch()
Czasem błąd jest spodziewany (jeden uszkodzony plik w folderze setek plików, jeden zły wiersz) i chcesz go obsłużyć i iść dalej, zamiast przerywać działanie. tryCatch() owija ryzykowne wyrażenie funkcjami obsługi:
Mechanika: jeśli główny blok się powiedzie, jego wartość jest wynikiem. Jeśli zgłosi błąd, zamiast niego uruchamia się funkcja obsługi error = i to jej wartość zwracana (tutaj NA) staje się wynikiem, a skrypt działa dalej. conditionMessage(e) odzyskuje oryginalny komunikat do logów. finally = uruchamia się w każdym przypadku, przy sukcesie i porażce, więc to miejsce na sprzątanie, np. zamykanie połączeń. Widać, jak wypisuje się przed każdym wynikiem powyżej.
Jest też funkcja obsługi warning =: tryCatch(as.numeric(x), warning = function(w) NA) przechwytuje ostrzeżenie o koercji z poprzedniej sekcji, zamiast je przepuścić. Jedna przestroga: funkcja obsługi, która zwraca wartość zastępczą bez zapisania czegokolwiek w logach, ukrywa awarie, a nie je obsługuje. Zawsze zapisuj conditionMessage(), bo ty z przyszłości będziesz tego potrzebować.
Lokalizowanie awarii: traceback(), browser() i uczciwe wypisywanie
Gdy błąd pochodzi z głębi zagnieżdżonych wywołań funkcji, sam komunikat nie mówi, który łańcuch wywołań do niego doprowadził. Uruchom traceback() zaraz po błędzie:
f <- function(x) g(x)
g <- function(x) stop("boom")
f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)
Wypisuje stos wywołań w chwili awarii: twoje wywołanie na jednym końcu, wywołanie, które zawiodło, na drugim. Musi to być następna uruchomiona rzecz; stos jest odrzucany, gdy wystąpi kolejny błąd.
Żeby zajrzeć na żywo, browser() wstrzymuje wykonanie w miejscu, gdzie go umieścisz, i otwiera interaktywny wiersz poleceń wewnątrz funkcji: sprawdzaj zmienne, przechodź krokami przez n, kontynuuj przez c, wyjdź przez Q. debug(f) robi to samo bez edytowania kodu: oznacza f, więc jej następne wywołanie otwiera się w przeglądarce debugera (cofniesz to przez undebug(f)).
I jest jeszcze technika, której nikt nie pokazuje na slajdach konferencyjnych, a której używa każdy: wypisywanie. Rozsiej print() albo cat() w punktach kontrolnych, uruchom i zobacz, gdzie rzeczywistość przestaje zgadzać się z oczekiwaniami. To uczciwe, szybkie i w skryptach często najbardziej praktyczne narzędzie. Jego najlepszym przyjacielem jest str(), które odpowiada na pytanie stojące za może połową wszystkich błędów w R: "czym ten obiekt właściwie jest?":
Jeden zwarty odczyt: to lista, dwa elementy, jeden to wektor liczb całkowitych, drugi to ramka danych z takimi kolumnami i typami. Gdy $ zawodzi albo obliczenia zachowują się dziwnie, wywołaj str() na obiekcie, zanim zaczniesz teoretyzować. Odpowiedź zwykle jest od razu widoczna ("...aha, to lista o długości 1, która zawiera moją ramkę danych").
Co warto zapamiętać
- Czytaj komunikat: część po
Error inmówi gdzie, część po dwukropku mówi dlaczego. Naprawiaj pierwszy błąd, nie najgłośniejszy. object not found= literówka, kod jeszcze nieuruchomiony albo zmienna, która istniała tylko wewnątrz funkcji.could not find function= prawie zawsze brakujące wywołanielibrary().unexpected symboloznacza gramatykę, której nie da się sparsować, a prawdziwym błędem często jest niezamknięty cudzysłów lub nawias przed wskazanym miejscem.- Klasyki typów i indeksowania (
non-numeric argument,subscript out of bounds,$ on atomic vectors,argument is of length zero) wskazują na błędne założenie o tym, czym jest obiekt;str()sprawdza to założenie jednym wywołaniem. - Ostrzeżenia nie zatrzymują wykonania i właśnie dlatego zasługują na uwagę: wynik może być po cichu błędny.
tryCatch(error =, warning =, finally =)obsługuje spodziewane awarie bez przerywania (zawsze zapisujconditionMessage());traceback()zaraz po błędzie pokazuje łańcuch wywołań;browser()/debug()zatrzymują się wewnątrz; debugowanie przezprint()to uczciwa praca.
Dalej: wiele "błędów", które wcale nie są błędami, sprowadza się do jednej trzyznakowej wartości, czyli NA, i tego, jak brakujące wartości przepływają przez wszystko, co liczy R.
Najczęściej zadawane pytania
Co oznacza "object 'x' not found" w R?
R szukał zmiennej o nazwie x i takiej nazwy nie ma w żadnym przeszukanym środowisku. Przyczyny, od najbardziej prawdopodobnej: literówka w nazwie (R rozróżnia wielkość liter, Total to nie total), linia tworząca x nie została jeszcze uruchomiona w tej sesji albo x powstało wewnątrz funkcji, a próbujesz użyć go poza nią.
Co oznacza "could not find function" w R?
Funkcja istnieje w pakiecie, który nie został wczytany w tej sesji. Pakiet instaluje się raz na komputer; library() wywołuje się raz na sesję, a zapomniane wywołanie library() to typowa przyczyna. Jeśli samo library() się nie udaje, pakiet nie jest zainstalowany. Literówka w nazwie funkcji daje ten sam błąd.
Jak obsługiwać błędy w R przez tryCatch?
Owiń ryzykowne wyrażenie: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). Jeśli expr się nie powiedzie, zamiast przerwania skryptu uruchomi się pasująca funkcja obsługi, a to, co zwróci, stanie się wynikiem. conditionMessage(e) wewnątrz funkcji obsługi daje oryginalny komunikat do zapisania w logach.
Co robi traceback() w R?
Uruchomione zaraz po błędzie traceback() wypisuje łańcuch wywołań funkcji aktywny w chwili błędu: twoje wywołanie na jednym końcu, wywołanie, które zawiodło, na drugim. Niczego nie naprawia; mówi, gdzie szukać, a to połowa sukcesu, gdy błąd pochodzi z głębi zagnieżdżonych funkcji.