Как вызвать ошибку, если встречаются не конечные значения (NA, NaN или Inf) - PullRequest
11 голосов
/ 07 февраля 2012

Есть флаг условной отладки, который я пропускаю из Matlab: dbstop if infnan , описанный здесь .Если установлено, это условие остановит выполнение кода при обнаружении Inf или NaN (IIRC, Matlab не имеет NA).

Как я могу добиться этого в R более эффективным способом, чемтестировать все объекты после каждой операции присваивания?

В настоящий момент я вижу только один способ сделать это с помощью хаков, подобных следующему:

  1. Вставить тест вручную после всех мест, гдеэти значения могут встречаться (например, деление, где может произойти деление на 0).Тестирование должно было бы использовать is.finite(), , описанные в этом разделе вопросов и ответов , на каждом элементе.
  2. Использование body() для изменения кода для вызова отдельной функции после каждой операцииили, возможно, только каждое назначение, которое проверяет все объекты (и, возможно, все объекты во всех средах).
  3. Изменение исходного кода R (?!?)
  4. Попытка использовать tracemem дляидентифицируйте те переменные, которые изменились, и проверяйте только их на наличие неправильных значений.
  5. (Новое - см. примечание 2). Для вызова тестовой функции используйте какой-либо обработчик вызовов / обратные вызовы.

Первый вариант - это то, что я делаю в настоящее времяЭто утомительно, потому что я не могу гарантировать, что проверил все.Второй вариант будет проверять все, даже если объект не был обновлен.Это огромная трата времени.Третий вариант будет включать в себя изменение назначений NA, NaN и бесконечных значений (+/- Inf), так что возникает ошибка.Кажется, что лучше оставить R Core.4-й вариант похож на 2-й - мне нужно вызвать отдельную функцию, перечисляющую все области памяти, просто чтобы идентифицировать те, которые изменились, и затем проверить значения;Я даже не уверен, что это будет работать для всех объектов, так как программа может выполнить модификацию на месте, которая, кажется, не вызовет функцию duplicate.

Есть ли лучший подход, который япропал?Может быть, какой-нибудь умный инструмент Марка Брэвингтона, Люка Тирни или что-то относительно простое - что-то вроде параметра options() или флага при компиляции R?

Пример кода Вот несколько очень простыхпример кода для тестирования, включающий функцию addTaskCallback, предложенную Джошом О'Брайеном.Код не прерывается, но ошибка возникает в первом сценарии, в то время как во втором случае ошибки не возникает (т. Е. badDiv(0,0,FALSE) не прерывается).Я все еще изучаю обратные вызовы, поскольку это выглядит многообещающе.

badDiv  <- function(x, y, flag){
    z = x / y
    if(flag == TRUE){
        return(z)
    } else {
        return(FALSE)
    }
}

addTaskCallback(stopOnNaNs)
badDiv(0, 0, TRUE)

addTaskCallback(stopOnNaNs)
badDiv(0, 0, FALSE)

Примечание 1. Я был бы удовлетворен решением для стандартных операций R, хотя во многих моих вычислениях используются объекты, используемые черезdata.table или bigmemory (т. Е. Матрицы на основе дисковой памяти).Похоже, что у них поведение памяти несколько отличается от стандартных операций matrix и data.frame.

Примечание 2. Идея обратных вызовов кажется несколько более многообещающей, поскольку для этого не требуется писать функции, которые изменяют код R,например, с помощью идеи body().

Примечание 3. Я не знаю, существует ли какой-либо простой способ проверить наличие неконечных значений, например, метаинформацию об объектах, индексирующих где NA,Инфы и т. Д. Хранятся в объекте, или если они хранятся на месте.До сих пор я пробовал пакет inspect Саймона Урбанека и не нашел способа предугадать присутствие нечисловых значений.

Продолжение: Саймон Урбанек указалв комментарии, что такая информация недоступна как метаинформация для объектов.

Примечание 4. Я все еще проверяю представленные идеи.Также, как предполагает Саймон, тестирование на наличие неконечных значений должно быть самым быстрым в C / C ++;это должно превосходить даже скомпилированный код R, но я открыт для всего.Для больших наборов данных, например порядка 10-50 ГБ, это должно быть значительной экономией по сравнению с копированием данных.Можно добиться дальнейших улучшений за счет использования нескольких ядер, но это немного сложнее.

Ответы [ 2 ]

7 голосов
/ 07 февраля 2012

Идея, изложенная ниже (и ее реализация), очень несовершенна. Я не решаюсь даже предложить это, но: (а) я думаю, что это довольно интересно, даже при всей своей безобразности; и (б) я могу думать о ситуациях, когда это было бы полезно. Учитывая, что, похоже, вы прямо сейчас вставляете чек после каждого вычисления, я надеюсь, что ваша ситуация одна из них.

Мой двухшаговый хак. Сначала я определяю функцию nanDetector(), которая предназначена для обнаружения NaN s в нескольких типах объектов, которые могут быть возвращены вашими вычислениями. Затем с помощью addTaskCallback() вызывается функция nanDetector() on .Last.value после завершения каждой задачи / вычисления верхнего уровня. Когда он находит NaN в одном из этих возвращаемых значений, он выдает ошибку, которую вы можете использовать, чтобы избежать дальнейших вычислений.

Среди его недостатков:

  • Если вы делаете что-то вроде установки stop(error = recover), трудно сказать, где была вызвана ошибка, поскольку ошибка всегда выдается изнутри stopOnNaNs().

  • Когда выдается ошибка, stopOnNaNs() завершается, прежде чем он может вернуть TRUE. Как следствие, он удаляется из списка задач, и вам нужно сбросить с addTaskCallback(stopOnNaNs), если вы хотите использовать его снова. (Подробнее см. Раздел «Аргументы» в? AddTaskCallback .)

Без лишних слов, вот оно:


# Sketch of a function that tests for NaNs in several types of objects
nanDetector <- function(X) {
   # To examine data frames
   if(is.data.frame(X)) { 
       return(any(unlist(sapply(X, is.nan))))
   }
   # To examine vectors, matrices, or arrays
   if(is.numeric(X)) {
       return(any(is.nan(X)))
   }
   # To examine lists, including nested lists
   if(is.list(X)) {
       return(any(rapply(X, is.nan)))
   }
   return(FALSE)
}

# Set up the taskCallback
stopOnNaNs <- function(...) {
    if(nanDetector(.Last.value)) {stop("NaNs detected!\n")}
    return(TRUE)
}
addTaskCallback(stopOnNaNs)


# Try it out
j <- 1:00
y <- rnorm(99)
l <- list(a=1:4, b=list(j=1:4, k=NaN))
# Error in function (...)  : NaNs detected!

# Subsequent time consuming code that could be avoided if the
# error thrown above is used to stop its evaluation.
7 голосов
/ 07 февраля 2012

Боюсь, что нет такого ярлыка. Теоретически в Unix есть SIGFPE, на который вы можете поймать ловушку, но на практике

  1. Не существует стандартного способа, позволяющего операциям FP перехватывать его (даже C99 не включает в себя условия для этого) - он сильно зависит от системы (например, feenableexcept в Linux, fp_enable_all в AIX и т. Д.) или требует использования ассемблера для вашего целевого процессора
  2. Операции FP в настоящее время часто выполняются в векторных единицах, таких как SSE, поэтому вы даже не можете быть уверены, что FPU задействован и
  3. R перехватывает некоторые операции с такими вещами, как NaN s, NA s, и обрабатывает их отдельно, чтобы они не попали в код FP

Тем не менее, вы можете взломать себе R, который поймает некоторые исключения для вашей платформы и процессора, если вы попытаетесь достаточно сильно (отключить SSE и т. Д.). Это не то, что мы могли бы рассмотреть встраивание в R, но для специальных целей это может быть выполнимо.

Однако он все равно не будет перехватывать операции NaN / NA, если вы не измените внутренний код R. Кроме того, вам придется проверять каждый пакет, который вы используете, поскольку они могут использовать операции FP в своем C-коде, а также могут обрабатывать NA / NaN отдельно.

Если вас беспокоят только такие вещи, как деление на ноль или переполнение / переполнение, вышеприведенное сработает и, вероятно, ближе всего к решению, подобному решению.

Простая проверка результатов может быть не очень надежной, поскольку вы не знаете, основан ли результат на каком-то промежуточном NaN вычислении, которое изменило агрегированное значение, которое, возможно, также не должно быть NaN. Если вы готовы отказаться от такого случая, вы можете просто рекурсивно пройтись по объектам результатов или рабочему пространству. Это не должно быть крайне неэффективно, потому что вам нужно беспокоиться только о REALSXP, а не о чем-либо еще (если вам не нравятся NA s - тогда у вас будет больше работы).


Это пример кода, который можно использовать для рекурсивного обхода объекта R:

static int do_isFinite(SEXP x) {
    /* recurse into generic vectors (lists) */
    if (TYPEOF(x) == VECSXP) {
        int n = LENGTH(x);
        for (int i = 0; i < n; i++)
            if (!do_isFinite(VECTOR_ELT(x, i))) return 0;
    }
    /* recurse into pairlists */ 
    if (TYPEOF(x) == LISTSXP) {
         while (x != R_NilValue) {
             if (!do_isFinite(CAR(x))) return 0;
             x = CDR(x);
         }
         return 1;
    }
    /* I wouldn't bother with attributes except for S4
       where attributes are slots */
    if (IS_S4_OBJECT(x) && !do_isFinite(ATTRIB(x))) return 0;
    /* check reals */
    if (TYPEOF(x) == REALSXP) {
        int n = LENGTH(x);
        double *d = REAL(x);
        for (int i = 0; i < n; i++) if (!R_finite(d[i])) return 0;
    }
    return 1; 
}

SEXP isFinite(SEXP x) { return ScalarLogical(do_isFinite(x)); }

# in R: .Call("isFinite", x)
...