Как читать сообщение об ошибке 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 zero — if () получил условие, в котором ничего нет, почти всегда 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() печатает цепочку вызовов функций, активную в момент срабатывания ошибки: ваш вызов на одном конце, упавший вызов на другом. Она ничего не чинит, но говорит, куда смотреть, а это большая часть дела, когда ошибка пришла из глубины вложенных функций.