Как реализовать "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 и перегрузить некоторые функции доступа, чтобы упростить синтаксис выше, но пока я не нашел реальной потребности.