Как реализовать "is.error()" для R, чтобы идентифицировать и анализировать ошибки?
Я пытаюсь проверить, являются ли объекты результатом ошибок. Случай использования в основном возникает через цикл foreach()
, который вызывает ошибку (хотя для тестирования кажется достаточно просто назначить переменную simpleError()
), и я озадачен тем, как определить, когда это произошло: как я могу проверить, что данный объект является, по сути, ошибкой? Как только я решил, что это ошибка, что еще я могу извлечь, помимо сообщения? Возможно, мне не хватает чего-то о средствах обработки ошибок R, поскольку, как представляется, необходимо написать функцию тестирования объектов ошибок de novo.
Вот два примера: один использует foreach
, аргумент .errorhandling
установлен на pass
. Я начал использовать это как значение по умолчанию для крупномасштабной или автоматической обработки, в случае аномалии во фрагменте данных. Такие аномалии редки, и не стоит сбивать весь цикл цикла (особенно если эта аномалия происходит в конце, что по-видимому является поведением по умолчанию моего murphysListSortingAlgorithm()
;-)). Вместо этого требуется посмертное обнаружение.
library(foreach)
library(doMC)
registerDoMC(2)
results = foreach(ix = 1:10, .errorhandling = "pass") %dopar%{
if(ix == 6){
stop("Perfect")
}
if(ix == 7){
stop("LuckyPrime")
} else {
return(ix)
}
}
Для простоты здесь очень простая ошибка (по определению):
a = simpleError("SNAFU")
Пока не существует команды типа is.error()
, а команды типа typeof()
и mode()
кажутся бессмысленными, лучшее, что я нашел, это использовать class()
или attributes()
, что укажите атрибуты, указывающие на ошибку. Как я могу использовать их таким образом, чтобы гарантировать, что у меня есть ошибка и полностью обработать эту ошибку? Например, a$message
возвращает SNAFU
, но a$call
- NULL
. Должен ли я ожидать, что вы сможете извлечь что-нибудь полезное, скажем, res[[6]]$call
?
Примечание 1: Если у одного нет многоуровневой функции для воспроизведения первого примера, я должен указать, что results[[6]]
не совпадает с simpleError("Perfect")
:
> b = simpleError("Perfect")
> identical(results[[6]], b)
[1] FALSE
> results[[6]]
<simpleError in eval(expr, envir, enclos): Perfect>
> b
<simpleError: Perfect>
Это демонстрирует, почему я не могу (очень наивно) проверить, является ли элемент списка ванилом simpleError
.
Примечание 2. Я знаю try
и tryCatch
и использую их в некоторых контекстах. Однако я не совсем уверен, как я могу использовать их для постпроцесса вывода, например, цикла foreach
. Например, объект results
в первом примере: мне не кажется, что имеет смысл обрабатывать его элементы с помощью обертки tryCatch
. Для RHS операции, т.е. Цикла foreach()
, я не уверен, что tryCatch
будет делать то, что я намерен. Я могу использовать его, чтобы поймать ошибку, но я полагаю, мне нужно получить сообщение и вставить обработку в этот момент. Я вижу две проблемы: каждый цикл нужно обернуть с помощью tryCatch()
, отрицающей часть аргумента .errorhandling
, и я не могу позже обработать объект results
. Если это единственный способ сделать эту обработку, то это решение, но это означает, что ошибки не могут быть идентифицированы и обработаны аналогично многим другим объектам R, таким как матрицы, векторы, кадры данных и т.д.
Обновление 1. Я добавил дополнительный триггер остановки в цикле foreach
, чтобы дать два разных сообщения для идентификации и анализа, если это полезно.
Обновление 2. Я выбираю ответ Ричи Коттона. Кажется, это наиболее полное объяснение того, что я должен искать, хотя для полной реализации требуется несколько других битов кода (и недавняя версия R). Самое главное, он указывает, что есть два типа ошибок, которые мы должны иметь в виду, что особенно важно для тщательного изучения. См. Также комментарии и ответы других, чтобы полностью разработать собственную тестовую функцию is.error()
; ответ, который я дал, может быть полезным началом при поиске ошибок в списке результатов, а код Ричи - хорошая отправная точка для тестовых функций.
Ответы
Ответ 1
Единственными двумя типами ошибок, которые вы, вероятно, увидите в дикой природе, являются simpleError
, как вы здесь, и try-error
, которые являются результатом переноса некоторого исключения, бросающего код при вызове try
. Кто-то может создать свой собственный класс ошибок, хотя они редки и должны основываться на одном из этих двух классов. Фактически (поскольку R2.14.0) try-error
содержит a simpleError
:
e <- try(stop("throwing a try-error"))
attr(e, "condition")
Для обнаружения a simpleError
является простым.
is_simple_error <- function(x) inherits(x, "simpleError")
Эквивалент для ошибок catch catch -
is_try_error <- function(x) inherits(x, "try-error")
Итак, вы можете проверить результаты для проблем, применив это к списку результатов.
the_fails <- sapply(results, is_simple_error)
Аналогично, возвращение сообщения и вызова являются однострочными. Для удобства я преобразовал вызов в строку символов, но вы можете этого не захотеть.
get_simple_error_message <- function(e) e$message
get_simple_error_call <- function(e) deparse(e$call)
sapply(results[the_fails], get_simple_error_message)
sapply(results[the_fails], get_simple_error_call)
Ответ 2
От? simpleError:
Условия - это объекты, наследующие от абстрактного класса. Ошибки и предупреждения - это объекты, наследующие от абстрактного подклассы ошибки и предупреждения. Класс simpleError - используемый класс путем остановки и всех внутренних сигналов ошибки. Аналогичным образом, simpleWarning используется предупреждением, а simpleMessage используется сообщением. конструкторы с одинаковыми именами принимают строку, описывающую условие как аргумент и необязательный вызов. Условие conditionMessage и conditionCall - это общие функции, возвращающие сообщение и вызов условия.
Итак class(a)
возвращает:
[1] "simpleError" "error" "condition"
Итак, простая функция:
is.condition <- function(x) {
require(taRifx)
last(class(x))=="condition"
}
Как отмечает @flodel, замена тела функции на inherits(x,"condition")
более надежна.
Ответ 3
Используя предложение @flodel о inherits()
, которое попадает в наследование абстрактного класса, упомянутое @gsk3, здесь мое текущее решение:
is.error.element <- function(x){
testError <- inherits(x, "error")
if(testError == TRUE){
testSimple <- inherits(x, "simpleError")
errMsg <- x$message
} else {
testSimple <- FALSE
errMsg <- NA
}
return(data.frame(testError, testSimple, errMsg, stringsAsFactors = FALSE))
}
is.error <- function(testObject){
quickTest <- is.error.element(testObject)
if(quickTest$testError == TRUE){
return(quickTest)
} else {
return(lapply(testObject, is.error.element))
}
}
Вот результаты, сделанные довольно с помощью ldply
для списка results
:
> ldply(is.error(results))
testError testSimple errMsg
1 FALSE FALSE <NA>
2 FALSE FALSE <NA>
3 FALSE FALSE <NA>
4 FALSE FALSE <NA>
5 FALSE FALSE <NA>
6 TRUE TRUE Perfect
7 TRUE TRUE LuckyPrime
8 FALSE FALSE <NA>
9 FALSE FALSE <NA>
10 FALSE FALSE <NA>
> is.error(a)
testError testSimple errMsg
1 TRUE TRUE SNAFU
Это все еще кажется мне грубым, не в последнюю очередь потому, что я не извлек значимого значения call
, а внешняя функция isError()
может не сильно повлиять на другие структуры. Я подозреваю, что это можно улучшить с помощью sapply
или другого члена семейств *apply
или *ply
(plyr
).
Ответ 4
Я использую try и catch, как описано в этом вопросе:
Как сохранить предупреждения и ошибки как выходные данные из функции?
Идея состоит в том, что каждый элемент цикла возвращает список с тремя элементами: возвращаемое значение, любые предупреждения и любые ошибки. Результатом является список списков, которые затем могут быть запрошены, чтобы узнать не только значения из каждого элемента цикла, но какие элементы в цикле имели предупреждения или ошибки.
В этом примере я бы сделал что-то вроде этого:
library(foreach)
library(doMC)
registerDoMC(2)
results = foreach(ix = 1:10, .errorhandling = "pass") %dopar%{
catchToList({
if(ix == 6){
stop("Perfect")
}
if(ix == 7){
stop("LuckyPrime")
} else {
ix
}
})
}
Затем я обработал бы результаты, подобные этому
> ok <- sapply(results, function(x) is.null(x$error))
> which(!ok)
[1] 6 7
> sapply(results[!ok], function(x) x$error)
[1] "Perfect" "LuckyPrime"
> sapply(results[ok], function(x) x$value)
[1] 1 2 3 4 5 8 9 10
Было бы довольно просто дать результат класса catchToList
и перегрузить некоторые функции доступа, чтобы упростить синтаксис выше, но пока я не нашел реальной потребности.