Подтвердить что ты не робот

Как заставить ошибку, если встречаются не конечные значения (NA, NaN или Inf)

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

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

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

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

Первый вариант - это то, что я делаю в настоящее время. Это утомительно, потому что я не могу гарантировать, что все проверил. Второй вариант будет проверять все, даже если объект не обновлен. Это огромная трата времени. Третий вариант включает модификацию присвоений NA, NaN и бесконечных значений (+/- Inf), так что возникает ошибка. Кажется, что лучше оставить R Core. Четвертый вариант похож на второй - мне нужен вызов отдельной функции, перечисляющей все ячейки памяти, только для идентификатора тех, которые изменились, и затем проверьте значения; Я даже не уверен, что это будет работать для всех объектов, так как программа может выполнять модификацию на месте, которая, похоже, не будет вызывать функцию 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 (то есть на основе памяти на основе дисков). Кажется, что они имеют несколько разные типы памяти, чем стандартные операции с матрицей и data.frame.

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

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

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

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

4b9b3361

Ответ 1

Я боюсь, что такого ярлыка нет. В теории на unix существует SIGFPE, что вы можете ловить ловушку, но на практике

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

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

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

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

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


Это пример кода, который можно использовать для рекурсивного перемещения объекта 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)

Ответ 2

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

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

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

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

  • Когда он выдает ошибку, stopOnNaNs() завершается, прежде чем он сможет вернуть TRUE. Как следствие, он удаляется из списка задач, и вам понадобится reset с 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.