Suggerimenti generali per il debug in R


120

Ottengo un errore quando utilizzo una funzione R che ho scritto:

Warning messages:
1: glm.fit: algorithm did not converge 
2: glm.fit: algorithm did not converge 

Cosa ho fatto:

  1. Scorri la funzione
  2. L'aggiunta di print per scoprire in quale riga si verifica l'errore suggerisce due funzioni che non dovrebbero essere utilizzate glm.fit. Sono window()e save().

I miei approcci generali includono l'aggiunta di printestop comandi, e passando attraverso una linea di funzione per linea fino a che non riesco a trovare l'eccezione.

Tuttavia, non è chiaro per me utilizzare quelle tecniche da cui proviene questo errore nel codice. Non sono nemmeno sicuro da quali funzioni all'interno del codice dipendono glm.fit. Come posso diagnosticare questo problema?


5
Dai un'occhiata alla pagina di Duncan Murdoch sul debug in R
Rob Hyndman

10
Ok, dichiaro l'ovvio: questo è un avvertimento non un errore .
Gavin Simpson

10
@ gavin-simpson Non mi ero reso conto che ci fosse una differenza tecnica, grazie per averlo fatto notare. Ma alla fine, indica che la mia funzione precedentemente funzionale è disfunzionale.
David LeBauer

11
@David +1 per "... la mia funzione precedentemente funzionale è disfunzionale."
Joshua Ulrich

5
@David: re il tuo ps. Ciò aggiunge una dimensione alla domanda che sarebbe stata persa senza l'esempio; vale a dire come fare in modo che R passi in modalità di debug quando vengono prodotti solo avvisi? Se avessi tralasciato questo dettaglio, non ti avremmo indicato tutti options(warn = 2). Quindi, in questo caso, il dettaglio è essenziale per rispondere alla tua domanda generale. +1 da me.
Gavin Simpson

Risposte:


167

Direi che il debugging è una forma d'arte, quindi non c'è un proiettile d'argento chiaro. Esistono buone strategie per il debug in qualsiasi lingua e si applicano anche qui (ad esempio, leggi questo bell'articolo ). Ad esempio, la prima cosa è riprodurre il problema ... se non puoi farlo, allora hai bisogno di ottenere maggiori informazioni (ad esempio con la registrazione). Una volta che puoi riprodurlo, devi ridurlo alla fonte.

Piuttosto che un "trucco", direi che ho una routine di debug preferita:

  1. Quando si verifica un errore, la prima cosa che faccio di solito è guardare la traccia dello stack chiamando traceback(): che mostra dove si è verificato l'errore, il che è particolarmente utile se hai diverse funzioni annidate.
  2. Successivamente mi sistemerò options(error=recover) ; questo passa immediatamente alla modalità browser in cui si verifica l'errore, in modo da poter navigare nell'area di lavoro da lì.
  3. Se ancora non ho informazioni sufficienti, di solito utilizzo la debug()funzione e passo attraverso lo script riga per riga.

Il miglior nuovo trucco in R 2.10 (quando si lavora con file di script) è usare le funzioni findLineNum()e setBreakpoint().

Come commento finale: a seconda dell'errore, è anche molto utile impostare try()otryCatch() istruzioni attorno alle chiamate di funzioni esterne (specialmente quando si tratta di classi S4). Ciò a volte fornisce ancora più informazioni e offre anche un maggiore controllo su come vengono gestiti gli errori in fase di esecuzione.

Queste domande correlate hanno molti suggerimenti:


8
Puoi anche aggiungere debugonce () a debug ().
Joris Meys

2
Sebbene non sia utile solo durante il debug, fix (df1) apre l'editor grafico R con il data frame df1 caricato al suo interno che puoi modificare al volo o semplicemente dare un'occhiata.
Dmitrii I.

il debug in R sembra essere molto difficile, ad esempio non esiste una soluzione semplice per visualizzare le righe di codice degli avvisi
TMS

browser()per quando ci sono errori che non attivano avvisi / errori (credito: Roman Luštrik in questa pagina). Qualche altro strumento come browser()?
PatrickT


32

Come è stato sottolineato a me in un'altra domanda , Rprof()e summaryRprof()sono belle strumenti per trovare le parti lente del programma che potrebbe beneficiare da accelerare o lo spostamento alla realizzazione di un C / C ++. Questo probabilmente si applica di più se stai facendo lavori di simulazione o altre attività ad alta intensità di dati o di calcolo. Il profrpacchetto può aiutare a visualizzare i risultati.

Sono un po 'impegnato nel debugging, quindi un altro suggerimento da un altro thread :

  • Impostato options(warn=2)per trattare gli avvisi come errori

Puoi anche usare optionsper lasciarti cadere nel vivo dell'azione quando si verifica un errore o un avviso, usando la tua funzione di debug preferita. Per esempio:

  • Impostato options(error=recover)per essere eseguito recover()quando si verifica un errore, come ha notato Shane (e come è documentato nella guida al debug di R. O qualsiasi altra funzione utile che potresti trovare utile eseguire.

E altri due metodi da uno dei link di @ Shane :

  • Avvolgi una chiamata di funzione interna con try()per restituire ulteriori informazioni su di essa.
  • Per le funzioni * apply, usa .inform=TRUE(dal pacchetto plyr) come opzione per il comando apply

@JoshuaUlrich ha anche sottolineato un modo accurato di utilizzare le capacità condizionali del browser()comando classico per attivare / disattivare il debug:

  • Inserisci la funzione di cui potresti voler eseguire il debug browser(expr=isTRUE(getOption("myDebug")))
  • E imposta l'opzione globale di options(myDebug=TRUE)
  • Potresti anche avvolgere la chiamata del browser: myBrowse <- browser(expr=isTRUE(getOption("myDebug")))e quindi chiamare con myBrowse()poiché utilizza le globali.

Poi ci sono le nuove funzioni disponibili nella R 2.10:

  • findLineNum()prende un nome di file sorgente e un numero di riga e restituisce la funzione e l'ambiente. Questo sembra essere utile quando si ha source()un file .R e restituisce un errore alla riga #n, ma è necessario sapere quale funzione si trova alla riga #n.
  • setBreakpoint() prende un nome di file sorgente e un numero di riga e vi imposta un punto di interruzione

Il pacchetto codetools , e in particolare il suocheckUsage funzione, può essere particolarmente utile per raccogliere rapidamente errori di sintassi ed errori stilistici che un compilatore tipicamente segnalerebbe (variabili locali non utilizzate, funzioni globali e variabili indefinite, corrispondenza parziale degli argomenti e così via).

setBreakpoint()è un front-end più intuitivo per trace(). I dettagli sugli interni di come funziona sono disponibili in un recente articolo di R Journal .

Se stai tentando di eseguire il debug del pacchetto di qualcun altro, una volta individuato il problema puoi sovrascrivere le sue funzioni con fixInNamespacee assignInNamespace, ma non usarlo nel codice di produzione.

Niente di tutto ciò dovrebbe precludere i collaudati strumenti di debug R standard , alcuni dei quali sono sopra e altri no. In particolare, gli strumenti di debug post-mortem sono utili quando hai un mucchio di codice che richiede tempo che preferiresti non rieseguire.

Infine, per problemi delicati che non sembrano generare un messaggio di errore, puoi usare options(error=dump.frames)come descritto in questa domanda: Errore senza che venga generato un errore


1
+1 per tutto il lavoro che hai fatto per fondere queste domande in una e poi tenerle aperte!
GSee

29

Ad un certo punto, glm.fitviene chiamato. Ciò significa una delle funzioni si chiama o una delle funzioni chiamate da quelle funzioni è utilizzando glm, glm.fit.

Inoltre, come ho detto nel mio commento sopra, questo è un avvertimento, non un errore , il che fa una grande differenza. Non puoi attivare nessuno degli strumenti di debug di R da un avviso (con le opzioni predefinite prima che qualcuno mi dica che ho torto ;-).

Se cambiamo le opzioni per trasformare gli avvisi in errori, possiamo iniziare a utilizzare gli strumenti di debug di R. Da ?optionsnoi abbiamo:

 ‘warn’: sets the handling of warning messages.  If ‘warn’ is
      negative all warnings are ignored.  If ‘warn’ is zero (the
      default) warnings are stored until the top-level function
      returns.  If fewer than 10 warnings were signalled they will
      be printed otherwise a message saying how many (max 50) were
      signalled.  An object called ‘last.warning’ is created and
      can be printed through the function ‘warnings’.  If ‘warn’ is
      one, warnings are printed as they occur.  If ‘warn’ is two or
      larger all warnings are turned into errors.

Quindi se corri

options(warn = 2)

quindi esegui il tuo codice, R genererà un errore. A quel punto potresti correre

traceback()

per vedere lo stack di chiamate. Ecco un esempio.

> options(warn = 2)
> foo <- function(x) bar(x + 2)
> bar <- function(y) warning("don't want to use 'y'!")
> foo(1)
Error in bar(x + 2) : (converted from warning) don't want to use 'y'!
> traceback()
7: doWithOneRestart(return(expr), restart)
6: withOneRestart(expr, restarts[[1L]])
5: withRestarts({
       .Internal(.signalCondition(simpleWarning(msg, call), msg, 
           call))
       .Internal(.dfltWarn(msg, call))
   }, muffleWarning = function() NULL)
4: .signalSimpleWarning("don't want to use 'y'!", quote(bar(x + 
       2)))
3: warning("don't want to use 'y'!")
2: bar(x + 2)
1: foo(1)

Qui puoi ignorare i frame contrassegnati 4:e superiori. Vediamo che è stato foochiamato bare che ha bargenerato l'avviso. Questo dovrebbe mostrarti quali funzioni stavano chiamando glm.fit.

Se ora vuoi eseguire il debug di questo, possiamo passare a un'altra opzione per dire a R di entrare nel debugger quando incontra un errore, e poiché abbiamo commesso errori di avviso, otterremo un debugger quando viene attivato l'avviso originale. Per questo dovresti eseguire:

options(error = recover)

Ecco un esempio:

> options(error = recover)
> foo(1)
Error in bar(x + 2) : (converted from warning) don't want to use 'y'!

Enter a frame number, or 0 to exit   

1: foo(1)
2: bar(x + 2)
3: warning("don't want to use 'y'!")
4: .signalSimpleWarning("don't want to use 'y'!", quote(bar(x + 2)))
5: withRestarts({
6: withOneRestart(expr, restarts[[1]])
7: doWithOneRestart(return(expr), restart)

Selection:

Puoi quindi entrare in uno di questi frame per vedere cosa stava succedendo quando è stato lanciato l'avviso.

Per ripristinare le opzioni precedenti ai valori predefiniti, immettere

options(error = NULL, warn = 0)

Per quanto riguarda l'avviso specifico che citi, è molto probabile che tu debba consentire più iterazioni nel codice. Una volta che hai scoperto cosa sta chiamando glm.fit, scopri come passargli l' controlargomento usandoglm.control - vedi ?glm.control.


4
Bella risposta. una nota di pessimismo è che questi tipi di errori di convergenza si verificano spesso con set di dati instabili / instabili (separazione completa, ecc.) e la finestra tra 'converge bene' e 'non convergente ma non può essere risolta aumentando il numero di iterazioni - necessita di qualche cambiamento piu 'drastico' è spesso stretto
Ben Bolker il

3
Gavin, ti ho battuto di 25 secondi. Ti chiedo di rimuovere la tua risposta eccessivamente utile e di smetterla di rubare i miei voti. ;-)
Joshua Ulrich

@ Ben ottimo punto. Se il problema di David è la separazione, l'aumento del numero di iterazioni non dovrebbe aiutare, dovrebbe comunque non riuscire a convergere. A quel punto, guardare le stime e gli errori standard potrebbe suggerire che c'è un problema. Mi aspetto anche di vedere l'avviso sui valori adattati numericamente 0 o 1 se la separazione o simili fossero un problema. Se aumentare il numero di iterazioni non aiuta, David può inviare un altro Q per chiedere aiuto e io posso rubare più voti positivi di @ Joshua ;-)
Gavin Simpson

1
@ Joshua, non c'è modo di batterlo. Ho smesso di contare i voti che avrei potuto perdere a causa sua. Ma comunque l'aiuto che fornisce lo spiega di gran lunga. Devi trovare le tue nicchie dove l'hai picchiato. Suggerisco voti positivi per sequenza di tasti qui ... :)
Matt Bannert

1
Dannazione @ ran2, hai sventato il mio piano vile e subdolo di conquistare il mondo , Mwahahahahaha !!!!
Gavin Simpson

21

Quindi browser(), traceback()ed debug()entra in un bar, ma trace()aspetta fuori e tiene il motore acceso.

Inserendo browserda qualche parte nella funzione, l'esecuzione si interromperà e attenderà il tuo input. Puoi andare avanti usando n(o Enter), eseguire l'intero blocco (iterazione) con c, terminare il ciclo / funzione corrente con fo uscire con Q; vedere ?browser.

Con debugsi ottiene lo stesso effetto del browser, ma questo interrompe l'esecuzione di una funzione all'inizio. Si applicano le stesse scorciatoie. Questa funzione sarà in una modalità "debug" fino a quando non la disattivi usando undebug(cioè, dopo debug(foo), l'esecuzione della funzione fooentrerà in modalità "debug" ogni volta finché non la esegui undebug(foo)).

Un'alternativa più transitoria è debugonce, che rimuoverà la modalità "debug" dalla funzione dopo la prossima volta che verrà valutata.

traceback ti darà il flusso di esecuzione delle funzioni fino al punto in cui qualcosa è andato storto (un errore reale).

È possibile inserire bit di codice (cioè funzioni personalizzate) nelle funzioni utilizzando trace, ad esempio browser. Questo è utile per le funzioni dei pacchetti e sei troppo pigro per ottenere il codice sorgente ben piegato.


18

La mia strategia generale è simile a:

  1. Corri traceback()a vedere se ci sono problemi evidenti
  2. Impostato options(warn=2)per trattare gli avvisi come errori
  3. Impostato options(error=recover)per entrare nello stack di chiamate in caso di errore

15

Dopo aver attraversato tutti i passaggi suggeriti qui Ho appena saputo che l'impostazione .verbose = TRUEin foreach()anche mi dà tonnellate di informazioni utili. In particolare foreach(.verbose=TRUE)mostra esattamente dove si verifica un errore all'interno del ciclo foreach, mentre traceback()non guarda all'interno del ciclo foreach.


13

Il debugger di Mark Bravington, disponibile come pacchetto debugsu CRAN, è molto buono e piuttosto semplice.

library(debug);
mtrace(myfunction);
myfunction(a,b);
#... debugging, can query objects, step, skip, run, breakpoints etc..
qqq(); # quit the debugger only
mtrace.off(); # turn off debugging

Il codice si apre in una finestra Tk evidenziata in modo che tu possa vedere cosa sta succedendo e, ovviamente, puoi chiamare un altro mtrace() mentre in una funzione diversa.

HTH


11

Mi piace la risposta di Gavin: non conoscevo le opzioni (errore = ripristino). Mi piace anche usare il pacchetto "debug" che offre un modo visivo per scorrere il codice.

require(debug)
mtrace(foo)
foo(1)

A questo punto si apre una finestra di debug separata che mostra la tua funzione, con una linea gialla che mostra dove ti trovi nel codice. Nella finestra principale il codice entra in modalità di debug e puoi continuare a premere Invio per scorrere il codice (e ci sono anche altri comandi) ed esaminare i valori delle variabili, ecc. La linea gialla nella finestra di debug continua a muoversi per mostrare dove sei nel codice. Al termine del debug, puoi disattivare la traccia con:

mtrace.off()

5

Sulla base della risposta che ho ricevuto qui , dovresti assolutamente controllare l' options(error=recover)impostazione. Quando è impostato, quando si verifica un errore, vedrai un testo sulla console simile al seguente ( tracebackoutput):

> source(<my filename>)
Error in plot.window(...) : need finite 'xlim' values
In addition: Warning messages:
1: In xy.coords(x, y, xlabel, ylabel, log) : NAs introduced by coercion
2: In min(x) : no non-missing arguments to min; returning Inf
3: In max(x) : no non-missing arguments to max; returning -Inf

Enter a frame number, or 0 to exit   

1: source(<my filename>)
2: eval.with.vis(ei, envir)
3: eval.with.vis(expr, envir, enclos)
4: LinearParamSearch(data = dataset, y = data.frame(LGD = dataset$LGD10), data.names = data
5: LinearParamSearch.R#66: plot(x = x, y = y.data, xlab = names(y), ylab = data.names[i])
6: LinearParamSearch.R#66: plot.default(x = x, y = y.data, xlab = names(y), ylab = data.nam
7: LinearParamSearch.R#66: localWindow(xlim, ylim, log, asp, ...)
8: LinearParamSearch.R#66: plot.window(...)

Selection:

A quel punto puoi scegliere quale "frame" inserire. Quando effettui una selezione, verrai inserito in browser()modalità:

Selection: 4
Called from: stop(gettextf("replacement has %d rows, data has %d", N, n), 
    domain = NA)
Browse[1]> 

E puoi esaminare l'ambiente com'era al momento dell'errore. Quando hai finito, digita cper tornare al menu di selezione della cornice. Quando hai finito, come ti dice, digita 0per uscire.


4

Ho dato questa risposta a una domanda più recente, ma la aggiungo qui per completezza.

Personalmente tendo a non usare le funzioni per eseguire il debug. Trovo spesso che questo causi tanti problemi quanti ne risolve. Inoltre, provenendo da un background Matlab, mi piace essere in grado di farlo in un ambiente di sviluppo integrato (IDE) piuttosto che farlo nel codice. L'uso di un IDE mantiene il tuo codice pulito e semplice.

Per R, utilizzo un IDE chiamato "RStudio" ( http://www.rstudio.com ), disponibile per Windows, Mac e Linux ed è abbastanza facile da usare.

Le versioni di Rstudio da circa ottobre 2013 (0.98ish?) Hanno la capacità di aggiungere punti di interruzione negli script e nelle funzioni: per fare ciò, basta fare clic sul margine sinistro del file per aggiungere un punto di interruzione. È possibile impostare un punto di interruzione e quindi passare da quel punto in poi. Hai anche accesso a tutti i dati in quell'ambiente, quindi puoi provare i comandi.

Vedere http://www.rstudio.com/ide/docs/debugging/overview per i dettagli. Se hai già installato Rstudio, potrebbe essere necessario eseguire l'aggiornamento: questa è una funzionalità relativamente nuova (fine 2013).

Potresti anche trovare altri IDE con funzionalità simili.

Certo, se si tratta di una funzione integrata potresti dover ricorrere ad alcuni dei suggerimenti forniti da altre persone in questa discussione. Ma se è il tuo codice che deve essere corretto, una soluzione basata su IDE potrebbe essere proprio ciò di cui hai bisogno.


1

Per eseguire il debug dei metodi della classe di riferimento senza riferimento all'istanza

ClassName$trace(methodName, browser)

0

Sto cominciando a pensare che non stampare il numero di riga di errore - un requisito fondamentale - PER DEFAILT - sia una specie di scherzo in R / Rstudio . L'unico metodo affidabile che ho trovato per trovare dove si è verificato un errore è fare lo sforzo aggiuntivo di chiamare traceback () e vedere la riga superiore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.