Menu

Errori comuni in R e come fare il debug

Un decodificatore per i classici messaggi di errore di R (object not found, could not find function, non-numeric argument e simili), più tryCatch, traceback() e il buon vecchio debug con print.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Leggere un messaggio di errore di R

Un errore di R ha due parti, ed entrambe sono utili:

Error in "10" + 5 : non-numeric argument to binary operator

Dopo Error in c'è la chiamata, il pezzo esatto di codice che è fallito ("10" + 5). Dopo i due punti c'è la condizione, cioè cosa è andato storto (non-numeric argument to binary operator). Leggi prima la chiamata: ti dice dove, e molto spesso vedi già il problema lì, nel codice citato. Poi leggi la condizione per capire il perché.

Due abitudini separano chi fa debug in fretta da chi soffre. Primo, leggi davvero il messaggio: i messaggi di R di solito sono precisi, solo scritti in modo stringato. Secondo, fai il debug del primo errore, non dell'ultimo: un fallimento all'inizio di uno script si propaga in una pila di errori "object not found" a valle, che spariscono tutti quando correggi quello originale. Il resto di questa pagina è un decodificatore per i messaggi che incontrerai più spesso, seguito dagli strumenti per quando leggere non basta.

Gli errori sui nomi: not found

Error: object 'total' not found: R ha cercato in ogni ambiente che conosce e nessuna variabile ha quel nome. Tre cause coprono quasi tutti i casi:

  • Un errore di battitura, maiuscole comprese. R distingue maiuscole e minuscole: Total, total e TOTAL sono tre nomi diversi, e R non proverà a indovinare quale intendevi.
  • La riga che la definisce non è stata eseguita. Hai scritto total <- sum(x) nello script ma non l'hai mai eseguita in questa sessione. Succede spesso dopo aver riavviato R: il file dello script mostra ancora la riga, ma la sessione non l'ha mai vista. Esegui lo script dall'inizio.
  • Ambiente sbagliato. Le variabili create dentro una funzione nascono e muoiono dentro quella chiamata. Usarne una fuori dalla funzione significa chiedere qualcosa che non esiste più: restituisci invece il valore.

Error: could not find function "read_excel": stessa idea, ma per il nome di una funzione. Nove volte su dieci la funzione sta in un pacchetto che hai installato ma non hai caricato in questa sessione:

library(readxl)          # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")

Se è library(readxl) stessa a dare errore, il pacchetto non è installato: prima install.packages("readxl"). E se la funzione è di R base, hai sbagliato a scriverla (lenght() è un rito di passaggio per tutti).

Errori di sintassi: unexpected symbol

Error: unexpected symbol in "..." (e i suoi cugini unexpected ')', unexpected string constant) significa che R non è riuscito nemmeno a interpretare il codice. Il messaggio indica il punto in cui R se n'è accorto, che spesso è dopo quello in cui sta l'errore vero. I soliti sospetti:

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

Quando la riga segnalata sembra innocente, l'errore è quasi sempre sopra: una virgoletta, una parentesi tonda o graffa non chiusa più in alto nel file. Un editor di codice che evidenzia le coppie corrispondenti li trova in pochi secondi.

Errori di tipo e di indicizzazione, decodificati

non-numeric argument to binary operator: hai fatto un calcolo su qualcosa che non è un numero, di solito un numero arrivato come testo (le importazioni sono la fonte classica: una colonna con una sola parola sparsa arriva come character, come spiegato nei tipi di dati). La versione rotta:

x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator

E la correzione: prima converti, poi calcola:

subscript out of bounds: hai chiesto la posizione n con [[ ]] in qualcosa che ha meno di n elementi:

scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds

Controlla length() prima di indicizzare, o meglio ancora chiedi per nome (scores[["grace"]]), così un riordino non può romperti il codice. Nota l'asimmetria: le parentesi singole sono più tolleranti. Un [ ] fuori intervallo su un vettore restituisce in silenzio NA invece di dare errore, il che scambia un bug rumoroso con uno silenzioso.

$ operator is invalid for atomic vectors: $ appartiene a liste e data frame. Su un vettore con nomi, usa le parentesi quadre:

([[ ]] dà il valore nudo; [ ] mantiene il nome attaccato.) Questo errore spesso significa che qualcosa a monte ha restituito un vettore mentre ti aspettavi un data frame: vai a verificare quell'ipotesi invece di cambiare semplicemente operatore.

argument is of length zero: un if () ha ricevuto una condizione vuota, quasi sempre un NULL intrufolato da un elemento di lista mancante o da una funzione che non ha restituito nulla:

threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero

Proteggi il controllo, e nota che && smette di valutare appena conosce la risposta, quindi il confronto non viene mai eseguito su un NULL:

(Un errore collegato, missing value where TRUE/FALSE needed, è lo stesso fallimento con NA al posto di NULL: lì la protezione è is.na(), trattata nei valori mancanti.)

replacement has length zero: la versione con assegnamento della stessa malattia. x[2] <- numeric(0) prova a riempire una casella con zero valori. Ciò che ha prodotto il lato destro è tornato vuoto: fai il debug di quello, non dell'assegnamento.

I warning non sono errori, ed è proprio questo il pericolo

Un errore ferma l'esecuzione; un warning no. R finisce il calcolo, ti consegna un risultato e dopo esprime le sue riserve. Quel risultato a volte va bene e a volte è sbagliato in silenzio:

Entrambe le righe vengono completate. La prima ricicla il vettore più corto e avvisa con longer object length is not a multiple of shorter object length, e riciclare un vettore di lunghezza 2 contro uno di lunghezza 3 non è quasi mai ciò che si intendeva. La seconda avvisa con NAs introduced by coercion e consegna un vettore con un buco dentro, che farà restituire NA a ogni mean() successivo. Tratta entrambi i warning come bug da indagare, non come rumore da scorrere via. Negli script puoi imporre questo atteggiamento con options(warn = 2), che trasforma ogni warning in un errore così nulla passa inosservato.

Gestire i fallimenti con tryCatch()

A volte un errore è previsto, un file corrotto in una cartella di centinaia, una riga sbagliata, e vuoi gestirlo e andare avanti invece di fermarti. tryCatch() avvolge un'espressione a rischio con dei gestori:

Il meccanismo: se il blocco principale riesce, il suo valore è il risultato. Se dà errore, viene eseguito il gestore error = e il suo valore di ritorno (qui NA) diventa il risultato: lo script va avanti. conditionMessage(e) recupera il messaggio originale da registrare. finally = viene eseguito in ogni caso, successo o fallimento, ed è lì che va la pulizia, come la chiusura delle connessioni: puoi vederlo stampare prima di ogni risultato qui sopra.

Esiste anche un gestore warning =: tryCatch(as.numeric(x), warning = function(w) NA) cattura il warning di coercizione della sezione precedente invece di lasciarlo passare. Un'avvertenza: un gestore che restituisce un valore di riserva senza registrare nulla è un modo per nascondere i fallimenti, non per gestirli. Registra sempre conditionMessage(): il te stesso del futuro ne avrà bisogno.

Trovare il punto del fallimento: traceback(), browser() e le stampe oneste

Quando l'errore arriva dal profondo di chiamate di funzione annidate, il messaggio da solo non dice quale catena di chiamate ti ha portato lì. Esegui traceback() subito dopo l'errore:

f <- function(x) g(x)
g <- function(x) stop("boom")

f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)

Stampa lo stack delle chiamate al momento del fallimento: la tua chiamata a un'estremità, la chiamata fallita all'altra. Deve essere la prima cosa che esegui dopo l'errore; lo stack viene scartato appena si verifica un altro errore.

Per uno sguardo dal vivo, browser() mette in pausa l'esecuzione ovunque tu lo inserisca e ti porta a un prompt interattivo dentro la funzione: ispeziona le variabili, avanza con n, continua con c, esci con Q. debug(f) fa lo stesso senza modificare il codice: contrassegna f in modo che la sua prossima chiamata si apra nel browser (annulla con undebug(f)).

E poi c'è la tecnica che nessuno mette nelle slide delle conferenze ma che tutti usano: stampare. Semina print() o cat() nei punti di controllo, esegui e guarda dove la realtà smette di corrispondere alle tue aspettative. È legittima, è veloce e negli script è spesso lo strumento più pratico. La sua migliore amica è str(), che risponde alla domanda dietro forse metà di tutti gli errori di R: "che cos'è davvero questo oggetto?":

Un unico resoconto compatto: è una lista di due elementi, uno è un vettore di interi, l'altro un data frame con queste colonne e questi tipi. Quando un $ fallisce o un calcolo si comporta in modo strano, usa str() sull'oggetto prima di fare teorie: la risposta di solito è lì ("...ah, è una lista di lunghezza 1 che contiene il mio data frame").

Cosa ti porti a casa

  • Leggi il messaggio: la parte dopo Error in dice dove, la parte dopo i due punti dice perché. Correggi il primo errore, non quello più rumoroso.
  • object not found = errore di battitura, codice non ancora eseguito o una variabile che esisteva solo dentro una funzione. could not find function = chiamata a library() mancante, quasi sempre.
  • unexpected symbol significa grammatica non interpretabile, e l'errore vero spesso è una virgoletta o una parentesi non chiusa prima del punto segnalato.
  • I classici di tipo e indicizzazione, non-numeric argument, subscript out of bounds, $ on atomic vectors, argument is of length zero, indicano tutti un'ipotesi sbagliata su cosa sia un oggetto; str() verifica l'ipotesi con una chiamata.
  • I warning non fermano l'esecuzione, ed è proprio per questo che meritano attenzione: il risultato potrebbe essere sbagliato in silenzio.
  • tryCatch(error =, warning =, finally =) gestisce i fallimenti previsti senza fermarsi (registra sempre conditionMessage()); traceback() subito dopo un errore mostra la catena delle chiamate; browser()/debug() mettono in pausa al suo interno; il debug con print() è un lavoro onesto.

Prossimo passo: molti "errori" che non sono affatto errori risalgono a un valore di due caratteri, NA, e a come i valori mancanti attraversano tutto ciò che R calcola.

Domande frequenti

Cosa significa "object 'x' not found" in R?

R ha cercato una variabile chiamata x e quel nome non esiste in nessuno degli ambienti in cui ha guardato. Le cause, in ordine di probabilità: un errore di battitura nel nome (R distingue maiuscole e minuscole: Total non è total), la riga che crea x non è ancora stata eseguita in questa sessione, oppure x è stata creata dentro una funzione e stai provando a usarla fuori.

Cosa significa "could not find function" in R?

La funzione esiste in un pacchetto che non hai caricato in questa sessione. Installare un pacchetto si fa una volta per computer; library() si fa una volta per sessione, e dimenticare la chiamata a library() è la causa abituale. Se fallisce library() stessa, il pacchetto non è installato. Un errore di battitura nel nome della funzione produce lo stesso errore.

Come si gestiscono gli errori in R con tryCatch?

Avvolgi l'espressione a rischio: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). Se expr fallisce, viene eseguito il gestore corrispondente invece di far morire lo script, e ciò che il gestore restituisce diventa il risultato. conditionMessage(e) dentro un gestore ti dà il messaggio originale da registrare nei log.

Cosa fa traceback() in R?

Eseguito subito dopo un errore, traceback() stampa la catena di chiamate di funzione attiva nel momento in cui l'errore è scattato: la tua chiamata a un'estremità, la chiamata che è fallita all'altra. Non corregge nulla; ti dice dove guardare, ed è metà della battaglia quando l'errore arriva dal profondo di funzioni annidate.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA