Menu

Типичные ошибки R и как их отлаживать

Расшифровка классических сообщений об ошибках R — object not found, could not find function, non-numeric argument и родственных — а также tryCatch, traceback() и честная отладка печатью.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Как читать сообщение об ошибке R

У ошибки в R две части, и полезны обе:

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

После Error in идёт вызов — тот самый кусок кода, который упал ("10" + 5). После двоеточия идёт условие — что пошло не так (non-numeric argument to binary operator). Сначала читайте вызов: он говорит, где, и очень часто проблему уже видно прямо в процитированном коде. Затем читайте условие ради почему.

Две привычки отделяют тех, кто отлаживает быстро, от тех, кто страдает. Во-первых, действительно читайте сообщение — сообщения R обычно точны, просто лаконично сформулированы. Во-вторых, отлаживайте первую ошибку, а не последнюю: один сбой в начале скрипта каскадом порождает гору ошибок «object not found» ниже по коду, и все они исчезнут, когда вы почините исходную. Остальная часть страницы — расшифровка сообщений, которые встретятся чаще всего, а затем инструменты для случаев, когда чтения недостаточно.

Ошибки имён: not found

Error: object 'total' not found — R обыскал все известные ему окружения, и ни одна переменная не носит такого имени. Три причины покрывают почти все случаи:

  • Опечатка, в том числе в регистре. R чувствителен к регистру: Total, total и TOTAL — три разных имени, и R не станет гадать, что вы имели в виду.
  • Определяющая строка не выполнялась. Вы написали total <- sum(x) в скрипте, но так и не выполнили её в этой сессии — частое дело после перезапуска R, когда файл скрипта всё ещё показывает строку, а сессия её никогда не видела. Запустите скрипт с самого верха.
  • Не то окружение. Переменные, созданные внутри функции, живут и умирают внутри этого вызова. Использовать такую переменную снаружи — значит просить то, чего больше не существует; вместо этого возвращайте значение.

Error: could not find function "read_excel" — та же идея, но для имени функции. В девяти случаях из десяти функция живёт в пакете, который вы установили, но не загрузили в этой сессии:

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

Если сама library(readxl) даёт ошибку, пакет не установлен — сначала install.packages("readxl"). А если функция из базового R, значит, вы сделали опечатку (lenght() — обряд посвящения для каждого).

Синтаксические ошибки: unexpected symbol

Error: unexpected symbol in "..." (и родственные unexpected ')', unexpected string constant) означает, что R не смог даже разобрать код. Сообщение указывает туда, где R заметил, а это часто дальше настоящей ошибки. Обычные подозреваемые:

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

Когда помеченная строка выглядит невинно, ошибка почти всегда выше неё: незакрытая кавычка, круглая или фигурная скобка ранее в файле. Редактор кода, подсвечивающий парные символы, находит такое за секунды.

Ошибки типов и индексации, расшифрованные

non-numeric argument to binary operator — вы сделали арифметику над чем-то, что не является числом, обычно над числом, пришедшим текстом (классический источник — импорт: столбец с одним затесавшимся словом приходит как character, как разобрано в типах данных). Сломанная версия:

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

И решение — преобразовать, потом считать:

subscript out of bounds — вы запросили позицию n в чём-то, где элементов меньше n, через [[ ]]:

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

Проверяйте length() перед индексацией или, ещё лучше, спрашивайте по имени (scores[["grace"]]), чтобы перестановка не могла вас сломать. Обратите внимание на асимметрию: одинарные скобки снисходительнее — выход за диапазон через [ ] на векторе тихо возвращает NA вместо ошибки, меняя громкий баг на тихий.

$ operator is invalid for atomic vectors$ принадлежит спискам и датафреймам. На именованном векторе используйте скобки:

([[ ]] даёт голое значение; [ ] сохраняет прикреплённое имя.) Эта ошибка часто означает, что что-то выше по коду вернуло вектор там, где вы ждали датафрейм, — идите проверять это предположение, а не просто меняйте оператор.

argument is of length zeroif () получил условие, в котором ничего нет, почти всегда NULL, просочившийся из отсутствующего элемента списка или из функции, ничего не вернувшей:

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

Защитите проверку — и заметьте, что && прекращает вычисление, как только ответ известен, поэтому сравнение никогда не выполнится над NULL:

(Родственная ошибка missing value where TRUE/FALSE needed — тот же сбой, но с NA вместо NULL; защита там это is.na(), разобранная в пропущенных значениях.)

replacement has length zero — версия той же болезни для присваивания: x[2] <- numeric(0) пытается заполнить один слот нулём значений. То, что произвело правую часть, вернулось пустым; отлаживайте это, а не присваивание.

Предупреждения — не ошибки, и в этом опасность

Ошибка останавливает выполнение; предупреждение — нет. R доводит вычисление до конца, отдаёт вам результат и лишь потом упоминает свои оговорки. Этот результат иногда нормален, а иногда тихо неверен:

Обе строки выполняются. Первая перерабатывает более короткий вектор и предупреждает longer object length is not a multiple of shorter object length — а переработка вектора длины 2 против длины 3 почти никогда не является чьим-либо замыслом. Вторая предупреждает NAs introduced by coercion и выдаёт вектор с дырой, из-за которой каждый последующий mean() вернёт NA. Относитесь к обоим предупреждениям как к багам для расследования, а не как к шуму для прокрутки. В скриптах эту позицию можно закрепить через options(warn = 2), что повышает каждое предупреждение до ошибки, и ничто не проскользнёт.

Обработка сбоев через tryCatch()

Иногда ошибка ожидаема — один битый файл в папке из сотен, одна плохая строка, — и вы хотите её обработать и двигаться дальше, а не умереть. tryCatch() оборачивает рискованное выражение обработчиками:

Механика такова: если основной блок отработал, его значение является результатом. Если он упал, вместо него выполняется обработчик error =, и его возвращаемое значение (здесь NA) становится результатом — скрипт продолжает работу. conditionMessage(e) восстанавливает исходное сообщение для журнала. finally = выполняется в любом случае, при успехе и при сбое, и именно туда относится уборка вроде закрытия соединений — выше видно, как он печатается перед каждым результатом.

Есть и обработчик warning =tryCatch(as.numeric(x), warning = function(w) NA) ловит предупреждение о приведении типов из предыдущего раздела вместо того, чтобы пропустить его. Одно предостережение: обработчик, возвращающий запасное значение без всякого журналирования, является способом прятать сбои, а не обрабатывать их. Всегда записывайте conditionMessage() — вам в будущем это понадобится.

Локализация сбоя: traceback(), browser() и честная печать

Когда ошибка приходит из глубины вложенных вызовов функций, само сообщение не говорит, какая цепочка вызовов вас туда привела. Запустите traceback() сразу после ошибки:

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

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

Она печатает стек вызовов на момент сбоя: ваш вызов на одном конце, упавший вызов на другом. Она должна быть следующим, что вы запускаете; стек отбрасывается, как только произойдёт другая ошибка.

Для живого взгляда browser() приостанавливает выполнение там, где вы его посадите, и опускает вас в интерактивную подсказку внутри функции: осматривайте переменные, шагайте через n, продолжайте через c, выходите через Q. debug(f) делает то же самое без правки кода: он помечает f, и следующий её вызов откроется в браузере (отменить через undebug(f)).

А ещё есть техника, которую никто не выносит на слайды конференций, но которой пользуются все, — печать. Рассыпьте print() или cat() по контрольным точкам, запустите и посмотрите, где реальность перестаёт совпадать с вашими ожиданиями. Это законно, это быстро, и в скриптах это часто самый практичный инструмент. Его лучший друг — str(), отвечающая на вопрос, стоящий, пожалуй, за половиной всех ошибок в R: «а что это вообще за объект?»:

Одна компактная выжимка: это список из двух элементов, один целочисленный вектор, другой датафрейм с такими-то столбцами и типами. Когда $ падает или арифметика чудит, примените str() к объекту до всякого теоретизирования — ответ обычно прямо там («...а, это же список длины 1, содержащий мой датафрейм»).

Что вы уносите с собой

  • Читайте сообщение: часть после Error in говорит где, часть после двоеточия говорит почему. Чините первую ошибку, а не самую громкую.
  • object not found = опечатка, невыполненный код или переменная, существовавшая только внутри функции. could not find function = почти всегда пропущенный вызов library().
  • unexpected symbol означает неразбираемую грамматику, а настоящая ошибка часто является незакрытой кавычкой или скобкой перед помеченным местом.
  • Классика типов и индексов — non-numeric argument, subscript out of bounds, $ на атомарных векторах, argument is of length zero — каждая указывает на неверное предположение о том, чем является объект; str() проверяет это предположение одним вызовом.
  • Предупреждения не останавливают выполнение, и именно поэтому они заслуживают внимания: результат может быть тихо неверным.
  • tryCatch(error =, warning =, finally =) обрабатывает ожидаемые сбои без гибели (всегда журналируйте conditionMessage()); traceback() сразу после ошибки показывает цепочку вызовов; browser()/debug() останавливают внутри неё; отладка через print() — честная работа.

Дальше: многие «ошибки», которые вовсе не ошибки, сводятся к одному двухбуквенному значению — NA, и к тому, как пропущенные значения текут через всё, что вычисляет R.

Часто задаваемые вопросы

Что означает «object 'x' not found» в R?

R искал переменную с именем x, и такого имени нет ни в одном просмотренном окружении. Причины в порядке вероятности: опечатка в имени (R чувствителен к регистру — Total это не total), строка, создающая x, ещё не выполнялась в этой сессии, или x был создан внутри функции, а вы пытаетесь использовать его снаружи.

Что означает «could not find function» в R?

Функция существует в пакете, который вы не загрузили в этой сессии. Установка пакета делается один раз на машину; library() — один раз за сессию, и забытый вызов library() является обычной причиной. Если сама library() падает, пакет не установлен. Опечатка в имени функции даёт ту же ошибку.

Как обрабатывать ошибки в R через tryCatch?

Оберните рискованное выражение: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). Если expr падает, вместо гибели скрипта выполняется подходящий обработчик, и то, что он возвращает, становится результатом. conditionMessage(e) внутри обработчика даёт исходное сообщение для журналирования.

Что делает traceback() в R?

Запущенная сразу после ошибки, traceback() печатает цепочку вызовов функций, активную в момент срабатывания ошибки: ваш вызов на одном конце, упавший вызов на другом. Она ничего не чинит, но говорит, куда смотреть, а это большая часть дела, когда ошибка пришла из глубины вложенных функций.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ