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