Sugestões gerais para depuração no R

120

Eu recebo um erro ao usar uma função R que escrevi:

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

O que eu fiz:

  1. Percorrer a função
  2. Adicionar impressão para descobrir em que linha o erro ocorre sugere duas funções que não devem ser usadas glm.fit. Eles são window()e save().

Minhas abordagens gerais incluem adição printe stopcomandos, e percorrendo uma função linha por linha até que eu possa localizar a exceção.

No entanto, não está claro para mim usando essas técnicas de onde esse erro vem no código. Nem tenho certeza de quais funções do código dependem glm.fit. Como faço para diagnosticar esse problema?

David LeBauer
fonte
5
Confira página de Duncan Murdoch na depuração em R
Rob Hyndman
10
Ok, afirmo o óbvio: isso é um aviso, não um erro .
Gavin Simpson
10
@ Gavin-Simpson Eu não sabia que havia uma diferença técnica, obrigado por apontar isso. Mas, no final, indica que minha função anteriormente funcional é disfuncional.
David LeBauer
11
@ David David para "... minha função anteriormente funcional é disfuncional".
21410 Joshua Ulrich
5
@ David: re seu ps. Isso adiciona uma dimensão à pergunta que seria perdida sem o exemplo; ou seja, como fazer o R ​​entrar no modo de depuração quando apenas avisos são produzidos? Se você tivesse deixado esse detalhe de fora, todos nós não o teríamos indicado options(warn = 2). Portanto, neste caso, os detalhes são essenciais para responder à sua pergunta geral. +1 de mim.
Gavin Simpson

Respostas:

167

Eu diria que a depuração é uma forma de arte, portanto não há uma bala de prata clara. Existem boas estratégias para depuração em qualquer idioma, e elas se aplicam aqui também (por exemplo, leia este belo artigo ). Por exemplo, a primeira coisa é reproduzir o problema ... se você não puder fazer isso, precisará obter mais informações (por exemplo, com o log). Depois de reproduzi-lo, é necessário reduzi -lo até a fonte.

Em vez de um "truque", eu diria que tenho uma rotina de depuração favorita:

  1. Quando ocorre um erro, a primeira coisa que costumo fazer é examinar o rastreamento da pilha chamando traceback(): que mostra onde ocorreu o erro, o que é especialmente útil se você tiver várias funções aninhadas.
  2. Em seguida vou definir options(error=recover); isso muda imediatamente para o modo de navegador onde o erro ocorre, para que você possa navegar na área de trabalho a partir daí.
  3. Se ainda não tenho informações suficientes, normalmente uso a debug()função e passo o script linha por linha.

O melhor novo truque no R 2.10 (ao trabalhar com arquivos de script) é usar as funções findLineNum()e setBreakpoint().

Como comentário final: dependendo do erro, também é muito útil definir try()ou fazer tryCatch()declarações em torno de chamadas de funções externas (especialmente ao lidar com classes S4). Às vezes, isso fornece ainda mais informações e também oferece mais controle sobre como os erros são tratados no tempo de execução.

Essas perguntas relacionadas têm muitas sugestões:

Shane
fonte
8
Você pode adicionar debugonce () a debug () também.
Joris Meys
2
Embora não seja apenas útil na depuração, o fix (df1) abre o R Editor gráfico com o quadro de dados df1 carregado, que você pode editar rapidamente ou apenas dar uma olhada.
Dmitrii I.
depuração em R parece ser muito difícil, por exemplo, não há uma solução fácil de ver as linhas de código de avisos
TMS
browser()para quando houver erros que não acionem avisos / erros (crédito: Roman Luštrik nesta página). Alguma outra ferramenta como browser()?
PatrickT
38

A melhor explicação que eu vi até agora é:

http://www.biostat.jhsph.edu/%7Erpeng/docs/R-debug-tools.pdf

Alguém concorda / discorda?

Christopher DuBois
fonte
Guia muito completo - descreve as ferramentas essenciais incluídas no núcleo R: debug (), traceback () e recover ().
23409 Sharpie
32

Como foi apontado para mim em outra pergunta , Rprof()e summaryRprof()são agradáveis ferramentas para encontrar partes lentas do seu programa que benefício pode de acelerar ou mudar-se para a implementação de um C / C ++. Isso provavelmente se aplica mais se você estiver realizando um trabalho de simulação ou outras atividades intensivas em computação ou dados. O profrpacote pode ajudar a visualizar os resultados.

Estou em um pontapé de aprender sobre a depuração, então outra sugestão de outro segmento :

  • Defina options(warn=2)para tratar avisos como erros

Você também pode usá options-lo diretamente no calor da ação quando ocorrer um erro ou aviso, usando sua função de depuração favorita de sua escolha. Por exemplo:

  • Defina options(error=recover)para executar recover()quando ocorrer um erro, como observou Shane (e conforme documentado no guia de depuração R.) Ou qualquer outra função útil que você ache útil executar.

E outros dois métodos de um dos links de @ Shane :

  • Envolva uma chamada de função interna com try()para retornar mais informações.
  • Para funções * apply, use .inform=TRUE(do pacote plyr) como uma opção para o comando apply

O @JoshuaUlrich também apontou uma maneira elegante de usar as habilidades condicionais do browser()comando clássico para ativar / desativar a depuração:

  • Coloque dentro da função que você pode querer depurar browser(expr=isTRUE(getOption("myDebug")))
  • E defina a opção global por options(myDebug=TRUE)
  • Você pode até encerrar a chamada do navegador: myBrowse <- browser(expr=isTRUE(getOption("myDebug")))e depois ligar, myBrowse()já que ela usa globais.

Depois, há as novas funções disponíveis no R 2.10:

  • findLineNum()pega um nome de arquivo de origem e número de linha e retorna a função e o ambiente. Isso parece ser útil quando você source()cria um arquivo .R e retorna um erro na linha #n, mas você precisa saber qual função está localizada na linha #n.
  • setBreakpoint() pega um nome de arquivo de origem e número de linha e define um ponto de interrupção lá

O pacote codetools , e particularmente sua checkUsagefunção, podem ser particularmente úteis para captar rapidamente erros de sintaxe e estilo que um compilador normalmente reportaria (locais não utilizados, funções e variáveis ​​globais indefinidas, correspondência parcial de argumentos e assim por diante).

setBreakpoint()é um front-end mais fácil de usar trace(). Detalhes sobre os aspectos internos de como isso funciona estão disponíveis em um artigo recente do R Journal .

Se você estiver tentando depurar o pacote de outra pessoa, depois de localizar o problema, poderá sobrescrever suas funções com fixInNamespacee assignInNamespace, mas não use isso no código de produção.

Nada disso deve impedir as ferramentas de depuração padrão R comprovadas , algumas das quais estão acima e outras não. Em particular, as ferramentas de depuração post-mortem são úteis quando você tem um monte de código demorado que você prefere não executar novamente.

Por fim, para problemas complicados que parecem não gerar uma mensagem de erro, você pode usar options(error=dump.frames)conforme detalhado nesta pergunta: Erro sem que um erro seja gerado

Ari B. Friedman
fonte
1
+1 para todo o trabalho que você dedicou a mesclar essas perguntas em uma e mantê-las abertas!
precisa
29

Em algum momento, glm.fitestá sendo chamado. Isso significa que uma das funções que você chama ou uma das funções chamadas por essas funções está usando glm, glm.fit.

Além disso, como mencionei no meu comentário acima, isso é um aviso, não um erro , o que faz uma grande diferença. Você não pode acionar nenhuma das ferramentas de depuração de R a partir de um aviso (com opções padrão antes que alguém me diga que estou errado ;-).

Se alterarmos as opções para transformar avisos em erros, poderemos começar a usar as ferramentas de depuração de R. De ?optionsnós temos:

 ‘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.

Então, se você correr

options(warn = 2)

Em seguida, execute seu código, R lançará um erro. Nesse ponto, você pode executar

traceback()

para ver a pilha de chamadas. Aqui está um exemplo.

> 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)

Aqui você pode ignorar os quadros marcados 4:e mais altos. Vemos isso foochamado bare que bargerou o aviso. Isso deve mostrar quais funções estavam chamando glm.fit.

Se você deseja agora depurar isso, podemos mudar para outra opção para solicitar ao R que entre no depurador quando encontrar um erro e, como cometemos erros de aviso, obteremos um depurador quando o aviso original for acionado. Para isso, você deve executar:

options(error = recover)

Aqui está um exemplo:

> 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:

Você pode entrar em qualquer um desses quadros para ver o que estava acontecendo quando o aviso foi lançado.

Para redefinir as opções acima para o padrão, digite

options(error = NULL, warn = 0)

Quanto ao aviso específico que você cita, é altamente provável que você precise permitir mais iterações no código. Depois de descobrir o que está chamando glm.fit, descubra como passar o controlargumento usando glm.control- veja ?glm.control.

Gavin Simpson
fonte
4
Ótima resposta. Uma nota de pessimismo é que esses tipos de erros de convergência geralmente ocorrem com conjuntos de dados instáveis ​​/ instáveis ​​(separação completa etc.), e a janela entre 'converge muito bem' e 'não-convergente, mas não pode ser corrigida aumentando o número de iterações - precisa de alguma mudança mais drástica' é muitas vezes estreita
Ben Bolker
3
Gavin, eu venci você por 25 segundos. Exijo que você remova sua resposta excessivamente útil e pare de roubar meus votos. ;-)
Joshua Ulrich
@Ben grande ponto. Se o problema de David é a separação, o aumento do número de iterações não deve ajudar, mas ainda não consegue convergir. Nesse ponto, examinar as estimativas e os erros padrão pode sugerir que há um problema. Eu também esperaria ver o aviso sobre valores ajustados numericamente 0 ou 1 se a separação ou similar fosse um problema. Se levantar o número de iterações não ajuda, David pode postar outro Q para obter ajuda e eu posso roubar mais de @ upvotes de Josué ;-)
Gavin Simpson
1
@ Josué, não há como vencê-lo. Parei de contar os votos positivos que eu poderia ter perdido por causa dele. Mas de qualquer maneira a ajuda que ele fornece explica isso de longe. Tem que encontrar seus próprios nichos onde você o venceu. Sugiro upvotes per keystroke aqui ... :)
Matt Bannert
1
Droga, ran2, você frustrou meu plano desonesto e desonesto de dominar o mundo , Mwahahahahaha !!!!
Gavin Simpson
21

Então browser(), traceback()e entre debug()em um bar, mas trace()espera do lado de fora e mantém o motor ligado.

Ao inserir browserem algum lugar da sua função, a execução será interrompida e aguardará sua entrada. Você pode avançar usando n(ou Enter), executar o bloco inteiro (iteração) com c, terminar o loop / função atual com fou sair com Q; veja ?browser.

Com debug, você obtém o mesmo efeito do navegador, mas isso interrompe a execução de uma função no início. Os mesmos atalhos se aplicam. Esta função estará no modo "depuração" até você desativá-la usando undebug(ou seja, após a debug(foo)execução, a função fooentrará no modo "depuração" toda vez que você executar undebug(foo)).

Uma alternativa mais transitória é debugonce, que removerá o modo "depuração" da função após a próxima avaliação.

traceback fornecerá o fluxo de execução de funções até o ponto em que algo deu errado (um erro real).

Você pode inserir bits de código (ou seja, funções personalizadas) em funções usando trace, por exemplo browser. Isso é útil para funções de pacotes e você é muito preguiçoso para obter o código-fonte bem dobrado.

Roman Luštrik
fonte
18

Minha estratégia geral se parece com:

  1. Corra traceback()para ver problemas óbvios
  2. Defina options(warn=2)para tratar avisos como erros
  3. Defina options(error=recover)para entrar na pilha de chamadas por erro
Joshua Ulrich
fonte
15

Depois de passar por todos os passos sugeridos aqui eu aprendi que a criação .verbose = TRUEde foreach()também me dá toneladas de informações úteis. Em particular, foreach(.verbose=TRUE)mostra exatamente onde ocorre um erro dentro do loop foreach, enquanto traceback()não olha dentro do loop foreach.

Michael Schneider
fonte
13

O depurador de Mark Bravington, que está disponível como pacote debugno CRAN, é muito bom e bastante direto.

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

O código aparece em uma janela Tk destacada para que você possa ver o que está acontecendo e, é claro, pode chamar outro mtrace()enquanto estiver em uma função diferente.

HTH

David Lawrence Miller
fonte
11

Eu gosto da resposta de Gavin: eu não sabia sobre opções (erro = recuperar). Também gosto de usar o pacote 'debug' que fornece uma maneira visual de percorrer seu código.

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

Nesse ponto, ele abre uma janela de depuração separada mostrando sua função, com uma linha amarela mostrando onde você está no código. Na janela principal, o código entra no modo de depuração, e você pode continuar pressionando enter para percorrer o código (e também existem outros comandos) e examinar valores de variáveis ​​etc. A linha amarela na janela de depuração continua se movendo para mostrar onde você está no código. Quando terminar a depuração, você pode desativar o rastreamento com:

mtrace.off()
Prasad Chalasani
fonte
5

Com base na resposta que recebi aqui , você deve definitivamente conferir a options(error=recover)configuração. Quando isso estiver definido, ao encontrar um erro, você verá um texto no console semelhante ao seguinte ( tracebacksaída):

> 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:

Em que ponto você pode escolher em qual "quadro" entrar. Ao fazer uma seleção, você será colocado no browser()modo:

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

E você pode examinar o ambiente como era no momento do erro. Quando terminar, digite cpara retornar ao menu de seleção de quadro. Quando terminar, como ele diz, digite 0para sair.

eykanal
fonte
4

Dei essa resposta a uma pergunta mais recente, mas estou adicionando-a aqui para completar.

Pessoalmente, eu não costumo usar funções para depurar. Costumo achar que isso causa tantos problemas quanto resolve. Além disso, vindo de um background do Matlab, gosto de poder fazer isso em um ambiente de desenvolvimento integrado (IDE), em vez de fazer isso no código. O uso de um IDE mantém seu código limpo e simples.

Para o R, eu uso um IDE chamado "RStudio" ( http://www.rstudio.com ), que está disponível para Windows, Mac e Linux e é muito fácil de usar.

As versões do Rstudio desde outubro de 2013 (0.98ish?) Têm a capacidade de adicionar pontos de interrupção em scripts e funções: para isso, basta clicar na margem esquerda do arquivo para adicionar um ponto de interrupção. Você pode definir um ponto de interrupção e depois avançar a partir desse ponto. Você também tem acesso a todos os dados nesse ambiente, para poder experimentar os comandos.

Consulte http://www.rstudio.com/ide/docs/debugging/overview para obter detalhes. Se você já possui o Rstudio instalado, pode ser necessário atualizar - esse é um recurso relativamente novo (final de 2013).

Você também pode encontrar outros IDEs com funcionalidade semelhante.

É certo que, se for uma função interna, talvez você precise recorrer a algumas das sugestões feitas por outras pessoas nesta discussão. Mas, se é o seu próprio código que precisa ser corrigido, uma solução baseada em IDE pode ser exatamente o que você precisa.

Andy Clifton
fonte
1

Para depurar métodos da classe Reference sem referência à instância

ClassName$trace(methodName, browser)
Siva
fonte
0

Estou começando a pensar que não imprimir o número da linha de erro - um requisito mais básico - POR PADRÃO - é algum tipo de piada no R / Rstudio . O único método confiável que encontrei para descobrir onde ocorreu um erro é fazer um esforço adicional de chamar traceback () e ver a linha superior.

user9669128
fonte