Menu

Errores comunes de R y cómo depurarlos

Un descifrador de los mensajes de error clásicos de R - object not found, could not find function, non-numeric argument y compañía - más tryCatch, traceback() y la honesta depuración con print.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Cómo leer un mensaje de error de R

Un error de R tiene dos partes, y ambas son útiles:

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

Después de Error in viene la llamada - el fragmento exacto de código que falló ("10" + 5). Después de los dos puntos viene la condición - qué salió mal (non-numeric argument to binary operator). Lee primero la llamada: te dice dónde, y muy a menudo ya puedes ver el problema ahí mismo, en el código citado. Luego lee la condición para saber el porqué.

Dos hábitos separan a quienes depuran rápido de quienes sufren. Primero, lee de verdad el mensaje - los mensajes de R suelen ser precisos, solo que redactados con parquedad. Segundo, depura el primer error, no el último: un fallo temprano en un script se propaga en cascada a un montón de errores "object not found" posteriores que desaparecen todos cuando arreglas el original. El resto de esta página es un descifrador de los mensajes que más vas a encontrar, y después las herramientas para cuando leer no basta.

Los errores de nombres: not found

Error: object 'total' not found - R buscó en todos los entornos que conoce y ninguna variable tiene ese nombre. Tres causas cubren casi todos los casos:

  • Una errata, incluidas las mayúsculas. R distingue mayúsculas y minúsculas: Total, total y TOTAL son tres nombres distintos, y R no va a adivinar cuál querías decir.
  • La línea que la define no se ha ejecutado. Escribiste total <- sum(x) en el script pero nunca lo ejecutaste en esta sesión - habitual tras reiniciar R, donde el archivo del script sigue mostrando la línea pero la sesión nunca la ha visto. Ejecuta el script desde el principio.
  • Entorno equivocado. Las variables creadas dentro de una función viven y mueren dentro de esa llamada. Usar una fuera de la función es pedir algo que ya no existe - devuelve el valor en su lugar.

Error: could not find function "read_excel" - la misma idea, pero para el nombre de una función. Nueve de cada diez veces la función vive en un paquete que instalaste pero no cargaste en esta sesión:

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

Si library(readxl) da error también, el paquete no está instalado - primero install.packages("readxl"). Y si la función es de R base, la has escrito mal (lenght() es el rito de iniciación de todo el mundo).

Errores de sintaxis: unexpected symbol

Error: unexpected symbol in "..." (y sus primos unexpected ')', unexpected string constant) significa que R ni siquiera pudo interpretar el código. El mensaje señala dónde R se dio cuenta, que a menudo está más allá de donde está el error real. Los sospechosos habituales:

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

Cuando la línea señalada parece inocente, el error casi siempre está encima de ella: una comilla, un paréntesis o una llave sin cerrar más arriba en el archivo. Un editor de código que resalte los pares emparejados los encuentra en segundos.

Errores de tipos e indexación, descifrados

non-numeric argument to binary operator - hiciste matemáticas sobre algo que no es un número, normalmente un número que llegó como texto (las importaciones son la fuente clásica: una columna con una sola palabra extraviada entra como character, como se explica en tipos de datos). La versión rota:

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

Y el arreglo - convierte, y luego calcula:

subscript out of bounds - pediste la posición n en algo con menos de n elementos, con [[ ]]:

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

Comprueba length() antes de indexar o, mejor, pide por nombre (scores[["grace"]]) para que reordenar no pueda romperte nada. Fíjate en la asimetría: los corchetes simples son más indulgentes - un [ ] fuera de rango sobre un vector devuelve NA en silencio en lugar de dar error, lo que cambia un bug ruidoso por uno silencioso.

$ operator is invalid for atomic vectors - $ pertenece a las listas y los data frames. Sobre un vector con nombres, usa corchetes:

([[ ]] da el valor a secas; [ ] conserva el nombre adjunto.) Este error suele significar que algo aguas arriba devolvió un vector cuando esperabas un data frame - ve a comprobar esa suposición en lugar de limitarte a cambiar el operador.

argument is of length zero - un if () recibió una condición sin nada dentro, casi siempre un NULL que se coló desde un elemento de lista ausente o una función que no devolvió nada:

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

Protege la comprobación - y observa que && deja de evaluar en cuanto conoce la respuesta, así que la comparación nunca se ejecuta sobre un NULL:

(Un error relacionado, missing value where TRUE/FALSE needed, es el mismo fallo con NA en lugar de NULL - la protección ahí es is.na(), que se cubre en valores faltantes.)

replacement has length zero - la versión en asignación de la misma enfermedad: x[2] <- numeric(0) intenta rellenar una posición con cero valores. Lo que sea que produjo el lado derecho volvió vacío; depura eso, no la asignación.

Los warnings no son errores - y ahí está el peligro

Un error detiene la ejecución; un warning no. R termina el cálculo, te entrega un resultado y menciona sus reservas después. Ese resultado a veces está bien y a veces está silenciosamente mal:

Ambas líneas se completan. La primera recicla el vector más corto y avisa con longer object length is not a multiple of shorter object length - y reciclar un vector de longitud 2 contra uno de longitud 3 casi nunca es lo que nadie quería. La segunda avisa con NAs introduced by coercion y entrega un vector con un agujero que hará que cada mean() posterior devuelva NA. Trata ambos warnings como bugs que investigar, no como ruido que dejar pasar. En los scripts puedes imponer esa postura con options(warn = 2), que asciende todo warning a error para que nada se cuele.

Manejar fallos con tryCatch()

A veces un error es esperado - un archivo corrupto en una carpeta de cientos, una fila mala - y quieres manejarlo y seguir adelante en lugar de morir. tryCatch() envuelve una expresión arriesgada con manejadores:

La mecánica: si el bloque principal tiene éxito, su valor es el resultado. Si da error, se ejecuta en su lugar el manejador error = y el valor que este devuelva (aquí NA) se convierte en el resultado - el script sigue adelante. conditionMessage(e) recupera el mensaje original para registrarlo. finally = se ejecuta en todos los casos, éxito o fallo, y ahí es donde va la limpieza como cerrar conexiones - puedes verlo imprimirse antes de cada resultado arriba.

También hay un manejador warning = - tryCatch(as.numeric(x), warning = function(w) NA) captura el warning de coerción de la sección anterior en lugar de dejarlo pasar. Una advertencia: un manejador que devuelve un valor de reserva sin registrar nada es una forma de ocultar fallos, no de manejarlos. Registra siempre conditionMessage() - tu yo del futuro lo necesita.

Localizar el fallo: traceback(), browser() y la impresión honesta

Cuando el error viene de lo profundo de llamadas a funciones anidadas, el mensaje por sí solo no dice qué cadena de llamadas te llevó allí. Ejecuta traceback() inmediatamente después del error:

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

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

Imprime la pila de llamadas en el momento del fallo - tu llamada en un extremo, la llamada que falló en el otro. Tiene que ser lo siguiente que ejecutes; la pila se descarta en cuanto ocurre otro error.

Para una mirada en vivo, browser() pausa la ejecución allí donde lo plantes y te deja en un prompt interactivo dentro de la función - inspecciona variables, avanza con n, continúa con c, sal con Q. debug(f) hace lo mismo sin editar código: marca f para que su siguiente llamada se abra en el browser (deshazlo con undebug(f)).

Y luego está la técnica que nadie pone en las diapositivas de las conferencias pero que todo el mundo usa: imprimir. Salpica print() o cat() en puntos de control, ejecuta, y observa dónde la realidad deja de coincidir con tus expectativas. Es legítima, es rápida, y en scripts suele ser la herramienta más práctica. Su mejor amiga es str(), que responde la pregunta detrás de quizá la mitad de todos los errores de R - "¿qué es realmente este objeto?":

Una lectura compacta: es una lista, dos elementos, uno un vector de enteros, otro un data frame con estas columnas y tipos. Cuando un $ falla o las matemáticas se portan mal, aplica str() al objeto antes de teorizar - la respuesta suele estar ahí mismo ("...ah, es una lista de longitud 1 que contiene mi data frame").

Lo que te llevas

  • Lee el mensaje: la parte después de Error in dice dónde, la parte después de los dos puntos dice por qué. Arregla el primer error, no el más ruidoso.
  • object not found = errata, código aún sin ejecutar, o una variable que solo existió dentro de una función. could not find function = falta la llamada a library(), casi siempre.
  • unexpected symbol significa gramática imposible de interpretar - y el error real suele ser una comilla o un paréntesis sin cerrar antes del punto señalado.
  • Los clásicos de tipos e índices - non-numeric argument, subscript out of bounds, $ on atomic vectors, argument is of length zero - señalan cada uno una suposición equivocada sobre qué es un objeto; str() comprueba la suposición en una llamada.
  • Los warnings no detienen la ejecución, y exactamente por eso merecen atención - el resultado puede estar silenciosamente mal.
  • tryCatch(error =, warning =, finally =) maneja fallos esperados sin morir (registra siempre conditionMessage()); traceback() justo después de un error muestra la cadena de llamadas; browser()/debug() pausan dentro de ella; la depuración con print() es trabajo honesto.

A continuación: muchos "errores" que no son errores en absoluto se remontan a un valor de tres caracteres - NA, y cómo los valores faltantes fluyen por todo lo que R calcula.

Preguntas frecuentes

¿Qué significa "object 'x' not found" en R?

R buscó una variable llamada x y ese nombre no existe en ningún entorno que haya revisado. Las causas, por orden de probabilidad: una errata en el nombre (R distingue mayúsculas y minúsculas - Total no es total), la línea que crea x aún no se ha ejecutado en esta sesión, o x se creó dentro de una función y estás intentando usarla fuera.

¿Qué significa "could not find function" en R?

La función existe en un paquete que no has cargado en esta sesión. Instalar un paquete es una vez por máquina; library() es una vez por sesión, y olvidar la llamada a library() es la causa habitual. Si library() falla también, el paquete no está instalado. Una errata en el nombre de la función produce el mismo error.

¿Cómo se manejan los errores en R con tryCatch?

Envuelve la expresión arriesgada: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). Si expr falla, se ejecuta el manejador correspondiente en lugar de que muera el script, y lo que devuelva el manejador se convierte en el resultado. conditionMessage(e) dentro de un manejador te da el mensaje original para registrarlo.

¿Qué hace traceback() en R?

Ejecutado justo después de un error, traceback() imprime la cadena de llamadas a funciones que estaba activa cuando saltó el error - tu llamada en un extremo, la llamada que falló en el otro. No arregla nada; te dice dónde mirar, que es la mayor parte de la batalla cuando el error vino de lo profundo de funciones anidadas.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR