Sugerencias generales para la depuración en R

120

Recibo un error al usar una función R que escribí:

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

Que he hecho:

  1. Paso a través de la función
  2. Agregar impresión para averiguar en qué línea se produce el error sugiere dos funciones que no deberían usarse glm.fit. Son window()y save().

Mis enfoques generales incluyen agregar printy stopcomandos, y recorrer una función línea por línea hasta que pueda localizar la excepción.

Sin embargo, no está claro para mí usar esas técnicas de dónde viene este error en el código. Ni siquiera estoy seguro de qué funciones dependen del código glm.fit. ¿Cómo hago para diagnosticar este problema?

David LeBauer
fuente
55
Echa un vistazo a la página de Duncan Murdoch de depuración en I
Rob Hyndman
10
Ok, voy a decir lo obvio: eso es una advertencia, no un error .
Gavin Simpson,
10
@ gavin-simpson No me di cuenta de que había una diferencia técnica, gracias por señalarlo. Pero al final, indica que mi función funcional anterior es disfuncional.
David LeBauer
11
@David +1 para "... mi función funcional anterior es disfuncional".
Joshua Ulrich
55
@David: re tu ps. Esto agrega una dimensión a la pregunta que se hubiera perdido sin el ejemplo; a saber, ¿cómo hacer que R pase al modo de depuración cuando solo se producen advertencias? Si hubiera dejado fuera este detalle, no todos lo hubiéramos señalado options(warn = 2). Entonces, en este caso, los detalles son esenciales para responder a su pregunta general. +1 de mi parte
Gavin Simpson

Respuestas:

167

Yo diría que la depuración es una forma de arte, por lo que no hay una bala de plata clara. Existen buenas estrategias para depurar en cualquier idioma, y ​​también se aplican aquí (por ejemplo, lea este bonito artículo ). Por ejemplo, lo primero es reproducir el problema ... si no puede hacer eso, entonces necesita obtener más información (por ejemplo, con el registro). Una vez que pueda reproducirlo, debe reducirlo a la fuente.

En lugar de un "truco", diría que tengo una rutina de depuración favorita:

  1. Cuando ocurre un error, lo primero que suelo hacer es mirar el seguimiento de la pila llamando traceback(): eso muestra dónde ocurrió el error, lo cual es especialmente útil si tiene varias funciones anidadas.
  2. A continuación lo pondré options(error=recover); esto cambia inmediatamente al modo de navegador donde se produce el error, por lo que puede explorar el espacio de trabajo desde allí.
  3. Si todavía no tengo suficiente información, generalmente uso la debug()función y paso por el script línea por línea.

El mejor truco nuevo en R 2.10 (cuando se trabaja con archivos de script) es usar las funciones findLineNum()y setBreakpoint().

Como comentario final: dependiendo del error, también es muy útil establecer try()o hacer tryCatch()declaraciones en torno a llamadas a funciones externas (especialmente cuando se trata de clases S4). Eso a veces proporcionará aún más información, y también le da más control sobre cómo se manejan los errores en tiempo de ejecución.

Estas preguntas relacionadas tienen muchas sugerencias:

Shane
fuente
8
También puede agregar debugonce () a debug ().
Joris Meys
2
Aunque no solo es útil al depurar, el arreglo (df1) abre el editor gráfico R con el marco de datos df1 cargado que puede editar sobre la marcha o simplemente echar un vistazo.
Dmitrii I.
la depuración en R parece ser muy difícil, por ejemplo, no hay una solución fácil para ver las líneas de código de advertencias
TMS
browser()para cuando hay errores que no activan advertencias / errores (crédito: Roman Luštrik en esta página). ¿Alguna otra herramienta como browser()?
PatrickT
32

Como se me señaló en otra pregunta , Rprof()y summaryRprof()son buenas herramientas para encontrar partes lentas de su programa que podrían beneficiarse de acelerar o pasar a una implementación de C / C ++. Esto probablemente se aplica más si está haciendo un trabajo de simulación u otras actividades intensivas en cómputo o datos. El profrpaquete puede ayudar a visualizar los resultados.

Estoy en una patada para aprender a depurar, así que otra sugerencia de otro hilo :

  • Configurado options(warn=2)para tratar las advertencias como errores

También puede usarlo optionspara dejarlo caer en el calor de la acción cuando ocurre un error o advertencia, utilizando su función de depuración favorita. Por ejemplo:

  • Configurado options(error=recover)para ejecutarse recover()cuando ocurre un error, como lo observó Shane (y como está documentado en la guía de depuración de R.) O cualquier otra función útil que le resulte útil ejecutar.

Y otros dos métodos de uno de los enlaces de @ Shane :

  • Envuelva una llamada de función interna con try()para devolver más información sobre ella.
  • Para las funciones * apply, use .inform=TRUE(del paquete plyr) como una opción para el comando apply

@JoshuaUlrich también señaló una forma ordenada de usar las habilidades condicionales del browser()comando clásico para activar / desactivar la depuración:

  • Poner dentro de la función que tal vez quieras depurar browser(expr=isTRUE(getOption("myDebug")))
  • Y establezca la opción global por options(myDebug=TRUE)
  • Incluso podría ajustar la llamada del navegador: myBrowse <- browser(expr=isTRUE(getOption("myDebug")))y luego llamar con, myBrowse()ya que usa globals.

Luego están las nuevas funciones disponibles en R 2.10:

  • findLineNum()toma un nombre de archivo de origen y un número de línea y devuelve la función y el entorno. Esto parece ser útil cuando source()utiliza un archivo .R y devuelve un error en la línea #n, pero necesita saber qué función se encuentra en la línea #n.
  • setBreakpoint() toma un nombre de archivo de origen y un número de línea y establece un punto de interrupción allí

El paquete codetools , y en particular su checkUsagefunción, puede ser particularmente útil para detectar rápidamente la sintaxis y los errores estilísticos que un compilador generalmente informaría (locales no utilizados, funciones y variables globales no definidas, coincidencia parcial de argumentos, etc.).

setBreakpoint()es un front-end más fácil de usar para trace(). Los detalles sobre los aspectos internos de cómo funciona esto están disponibles en un artículo reciente de R Journal .

Si está intentando depurar el paquete de otra persona, una vez que haya localizado el problema, puede sobrescribir sus funciones con fixInNamespacey assignInNamespace, pero no lo use en el código de producción.

Nada de esto debería impedir las herramientas de depuración R estándar probadas y verdaderas , algunas de las cuales están arriba y otras no. En particular, las herramientas de depuración post-mortem son útiles cuando tiene un montón de código que consume mucho tiempo y que prefiere no volver a ejecutar.

Finalmente, para problemas difíciles que no parecen arrojar un mensaje de error, puede usar options(error=dump.frames)como se detalla en esta pregunta: Error sin arrojar un error

Ari B. Friedman
fuente
1
¡+1 por todo el trabajo que has puesto en fusionar estas preguntas en una sola y luego mantenerlo abierto!
GSee
29

En algún momento, glm.fitse está llamando. Eso significa que una de las funciones que llamas o una de las funciones llamadas por esas funciones están usando o bien glm, glm.fit.

Además, como mencioné en mi comentario anterior, es una advertencia, no un error , lo que hace una gran diferencia. No puede activar ninguna de las herramientas de depuración de R desde una advertencia (con opciones predeterminadas antes de que alguien me diga que estoy equivocado ;-).

Si cambiamos las opciones para convertir las advertencias en errores, entonces podemos comenzar a usar las herramientas de depuración de R. De ?optionstenemos:

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

Entonces si corres

options(warn = 2)

luego ejecute su código, R arrojará un error. En ese punto, podrías correr

traceback()

para ver la pila de llamadas. Aquí hay un ejemplo.

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

Aquí puede ignorar los marcos marcados 4:y superiores. Vemos que foollamó bary que bargeneró la advertencia. Eso debería mostrarte qué funciones estaban llamando glm.fit.

Si ahora desea depurar esto, podemos recurrir a otra opción para decirle a R que ingrese al depurador cuando encuentre un error, y como hemos cometido errores de advertencia, obtendremos un depurador cuando se active la advertencia original. Para eso debes ejecutar:

options(error = recover)

Aquí hay un ejemplo:

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

Luego puede ingresar a cualquiera de esos marcos para ver qué estaba sucediendo cuando se lanzó la advertencia.

Para restablecer las opciones anteriores a sus valores predeterminados, ingrese

options(error = NULL, warn = 0)

En cuanto a la advertencia específica que cita, es muy probable que necesite permitir más iteraciones en el código. Una vez que hayas descubierto lo que está llamando glm.fit, averigua cómo pasar el controlargumento usando glm.control- mira ?glm.control.

Gavin Simpson
fuente
44
gran respuesta. Una nota de pesimismo es que este tipo de errores de convergencia a menudo ocurren con conjuntos de datos inestables / inestables (separación completa, etc.), y la ventana entre 'converge muy bien' y 'no convergente, pero no se puede solucionar aumentando el número de iteraciones - necesita un cambio más drástico 'a menudo es limitado
Ben Bolker
3
Gavin, te gané por 25 segundos. Exijo que elimine su respuesta demasiado útil y que deje de robar mis votos a favor. ;-)
Joshua Ulrich
@Ben gran punto. Si el problema de David es la separación, entonces aumentar el número de iteraciones no debería ayudar, aún así no podría converger. En ese momento, mirar las estimaciones y los errores estándar pueden sugerir que hay un problema. También esperaría ver la advertencia sobre los valores ajustados numéricamente 0 o 1 si la separación o similar fuera un problema. Si aumentar el número de iteraciones no ayuda, David puede publicar otra Q para obtener ayuda y puedo robar más votos positivos de @ Joshua ;-)
Gavin Simpson,
1
@Joshua, no hay forma de vencerlo. Dejé de contar los votos a favor que podría haber perdido a causa de él. Pero de todos modos, la ayuda que proporciona explica eso con diferencia. Tengo que encontrar tus propios nichos donde lo golpeaste. Sugiero votos a favor por pulsación de tecla aquí ... :)
Matt Bannert
1
Maldita sea @ ran2, has frustrado mi plan cruel y tortuoso para conquistar el mundo , ¡¡Mwahahahahaha !!!!
Gavin Simpson
21

Por lo tanto browser(), traceback()y debug()entrar en un bar, pero trace()espera afuera y mantiene el motor en marcha.

Al insertar browseren algún lugar de su función, la ejecución se detendrá y esperará su entrada. Puede avanzar usando n(o Enter), ejecutar el fragmento completo (iteración) con c, finalizar el bucle / función actual con f, o salir con Q; ver ?browser.

Con debug, obtienes el mismo efecto que con el navegador, pero esto detiene la ejecución de una función al principio. Se aplican los mismos atajos. Esta función estará en modo "depuración" hasta que la desactive usando undebug(es decir, después de debug(foo)ejecutar la función fooentrará en modo "depuración" cada vez hasta que la ejecute undebug(foo)).

Una alternativa más transitoria es debugonce, que eliminará el modo "depuración" de la función después de la próxima vez que se evalúe.

traceback le dará el flujo de ejecución de funciones hasta donde algo salió mal (un error real).

Puede insertar bits de código (es decir, funciones personalizadas) en funciones utilizando trace, por ejemplo browser. Esto es útil para funciones de paquetes y eres demasiado vago para obtener el código fuente bien plegado.

Roman Luštrik
fuente
18

Mi estrategia general se ve así:

  1. Corre traceback()para ver buscar problemas obvios
  2. Configurado options(warn=2)para tratar las advertencias como errores
  3. Establecer options(error=recover)para entrar en la pila de llamadas por error
Joshua Ulrich
fuente
15

Después de pasar por todos los pasos sugeridos aquí me acabo de enterar que el establecimiento .verbose = TRUEen foreach()también me da un montón de información útil. En particular, foreach(.verbose=TRUE)muestra exactamente dónde ocurre un error dentro del bucle foreach, mientras traceback()que no mira dentro del bucle foreach.

Michael Schneider
fuente
13

El depurador de Mark Bravington que está disponible como paquete debugen CRAN es muy bueno y bastante sencillo.

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

El código aparece en una ventana Tk resaltada para que pueda ver lo que está sucediendo y, por supuesto, puede llamar a otro mtrace() mientras está en una función diferente.

HTH

David Lawrence Miller
fuente
11

Me gusta la respuesta de Gavin: no sabía acerca de las opciones (error = recuperar). También me gusta usar el paquete 'debug' que brinda una forma visual de recorrer su código.

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

En este punto, se abre una ventana de depuración separada que muestra su función, con una línea amarilla que muestra dónde se encuentra en el código. En la ventana principal, el código ingresa al modo de depuración, y puede seguir presionando Intro para avanzar por el código (y también hay otros comandos), y examinar valores variables, etc. La línea amarilla en la ventana de depuración sigue moviéndose para mostrar dónde Estás en el código. Cuando haya terminado con la depuración, puede desactivar el seguimiento con:

mtrace.off()
Prasad Chalasani
fuente
5

Según la respuesta que recibí aquí , definitivamente deberías revisar la options(error=recover)configuración. Cuando esto está configurado, al encontrar un error, verá un texto en la consola similar al siguiente ( tracebacksalida):

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

En ese momento puede elegir qué "marco" para ingresar. Cuando realice una selección, se le pondrá en browser()modo:

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

Y puede examinar el entorno tal como estaba en el momento del error. Cuando haya terminado, escriba cpara volver al menú de selección de cuadros. Cuando haya terminado, como le indica, escriba 0para salir.

eykanal
fuente
4

Di esta respuesta a una pregunta más reciente, pero la agrego aquí para completarla.

Personalmente, tiendo a no utilizar funciones para depurar. A menudo encuentro que esto causa tantos problemas como resuelve. Además, viniendo de un fondo de Matlab, me gusta poder hacer esto en un entorno de desarrollo integrado (IDE) en lugar de hacerlo en el código. Usar un IDE mantiene su código limpio y simple.

Para R, utilizo un IDE llamado "RStudio" ( http://www.rstudio.com ), que está disponible para Windows, Mac y Linux y es bastante fácil de usar.

Las versiones de Rstudio desde aproximadamente octubre de 2013 (0.98ish?) Tienen la capacidad de agregar puntos de interrupción en scripts y funciones: para hacer esto, simplemente haga clic en el margen izquierdo del archivo para agregar un punto de interrupción. Puede establecer un punto de interrupción y luego avanzar a partir de ese punto. También tiene acceso a todos los datos en ese entorno, por lo que puede probar comandos.

Consulte http://www.rstudio.com/ide/docs/debugging/overview para más detalles. Si ya tiene instalado Rstudio, es posible que deba actualizar; esta es una característica relativamente nueva (finales de 2013).

También puede encontrar otros IDE que tengan una funcionalidad similar.

Es cierto que si se trata de una función incorporada, es posible que tenga que recurrir a algunas de las sugerencias hechas por otras personas en esta discusión. Pero, si es su propio código el que necesita ser reparado, una solución basada en IDE podría ser justo lo que necesita.

Andy Clifton
fuente
1

Para depurar métodos de clase de referencia sin referencia de instancia

ClassName$trace(methodName, browser)
Siva
fuente
0

Estoy empezando a pensar que no imprimir el número de línea de error, un requisito más básico, POR DEFECTO, es una especie de broma en R / Rstudio . El único método confiable que he encontrado para encontrar dónde se produjo un error es hacer el esfuerzo adicional de inmovilizar traceback () y ver la línea superior.

usuario9669128
fuente