问题 如果遇到非有限值(NA,NaN或Inf),如何强制错误


我从Matlab那里得到了一个条件调试标志: dbstop if infnan  这里描述。如果设置,此条件将停止代码执行时 Inf 要么 NaN 遇到了(IIRC,Matlab没有NAs)。

如何在R中以比在每次赋值操作后测试所有对象更有效的方式实现此目的?

目前,我看到这样做的唯一方法是通过如下的黑客攻击:

  1. 在可能遇到这些值的所有位置之后手动插入测试(例如,可能发生除0的除法)。测试将是使用 is.finite()在本问答中描述,关于每个元素。
  2. 使用 body() 在每次操作之后修改代码以调用单独的函数,或者可能只是每次赋值,它们测试所有对象(可能还有所有环境中的所有对象)。
  3. 修改R的源代码(?!?)
  4. 尝试使用 tracemem 识别那些已更改的变量,并仅检查这些变量是否为坏值。
  5. (新增 - 请参阅注释2)使用某种调用处理程序/回调来调用测试函数。

第一种选择是我目前正在做的事情。这很乏味,因为我不能保证我已经检查了一切。第二个选项将测试所有内容,即使对象尚未更新。这是浪费大量时间。第3个选项将涉及修改NA,NaN和无限值(+/- Inf)的赋值,以便产生错误。这似乎最好留给R Core。第四个选项就像第二个选项 - 我需要调用一个单独的函数列出所有内存位置,只需要ID那些已更改的内容,然后检查值;我甚至不确定它是否适用于所有对象,因为程序可能会进行就地修改,这似乎不会调用 duplicate 功能。

我错过了更好的方法吗?也许马克·布拉文顿(Mark Bravington),卢克·蒂尔尼(Luke Tierney)的一些聪明的工具,或者相对基本的东西 - 类似于 options() 编译R时的参数或标志?

示例代码 这是一些非常简单的示例代码,用于测试,包含 addTaskCallback Josh O'Brien提出的功能。代码不会中断,但在第一种情况下确实发生错误,而在第二种情况下没有发生错误(即 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.我不知道是否有一些简单的方法来测试非有限值的存在,例如关于索引NAs,Infs等存储在对象中的对象的元信息,或者这些是否存储在适当位置的元信息。到目前为止,我已经尝试了Simon Urbanek的 inspect包,并没有找到一种方法来判断非数字值的存在。

跟进: Simon Urbanek在评论中指出,这些信息不能作为对象的元信息。

注4.我仍在测试所提出的想法。另外,正如Simon所建议的,在C / C ++中测试非有限值的存在应该是最快的;这应该超过编译的R代码,但我对任何事情都持开放态度。对于大型数据集,例如大约10-50GB,这应该比复制数据节省大量资金。通过使用多个内核可以获得进一步的改进,但这有点先进。


6160
2018-02-06 21:15


起源

一些原始函数具有内置的此功能,即,如果提供或返回非有限结果,则它们返回错误或警告。采取, sin(Inf) 例如。也许这是你可以探索的东西。 - Brandon Bertelsen
那么,Inf或NaN的情况并非总是如此 应该 停止你的功能/代码(NA是一个单独的案例,因为它故意一直用作'填充'或'标记')。我经常运行一些产生一些Inf值的操作,比如在一些矩阵的低信号区域。我怀疑你会通过使用获得更好的信息 is.infinite 和/或 is.nan 无论如何,在可疑变量上。 - Carl Witthoft
@CarlWitthoft在我目前正在研究的代码+数据场景中,问题值恰恰是那三个 - NA,NaN和Infs。在其他情况下,我肯定需要NAs,但今天不是。 :)一旦这些发生,我需要代码中止(它的计算成本相当高,数据量的b / c)。因此,我真的想触发错误(或至少是一个警告)。 - Iterator
@BrandonBertelsen感谢您的建议。我看了一下基本的算术运算符,没有从R里面弹出任何东西。我怀疑这个特殊的处理是在C代码中用于某些原始函数,但是总有可能它只是R中已知的那些东西之一向导。 :) - Iterator
@Iterator - 感谢发人深思的问题。它给了我一种令人兴奋的锻炼,让我回到了原点。如果我的建议最终有助于您的具体情况,请告诉我们! - Josh O'Brien


答案:


下面概述的概念(及其实现)非常不完美。我甚至犹豫不决,但是:(a)我觉得这很有意思,即使在它的丑陋中也是如此; (b)我可以想到它会有用的情况。鉴于你现在听起来像是在每次计算后手动插入支票,我希望你的情况就是其中之一。

我的是一个两步黑客。首先,我定义一个函数 nanDetector() 它旨在检测 NaN在您的计算可能返回的几种对象类型中。然后,它使用 addTaskCallback() 调用该函数 nanDetector() 上 .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
2018-02-06 23:37



热的diggity狗,这有几个有趣的想法,我刚开始考虑,即使用回调。无论它是否适用于我的代码,我都必须看到,但它仍然是指导性的。谢谢! - Iterator
FWIW在R代码中执行检查是非常低效的 - 在C中这样做是微不足道的,因为它基本上只是 if (TYPEOF(x) == REALSXP) { double *d = REAL(x); int n = LENGTH(x); for(int i = 0; i < n; i++) if (!R_finite(d[i])) return ScalarLogical(0); } return ScalarLogical(1); 并且您可以更快速地(并且无需复制)递归到递归对象中。 - Simon Urbanek
@SimonUrbanek感谢C指导 - 这当然应该很快。这种方法是否易于开发,比如通过 inline 包?或者我可以希望它可能出现在R中吗? :)(我猜这是一个R-devel问题。;-)) - Iterator
@SimonUrbanek - 这听起来很有趣,但我不知道我是如何在实践中实现它的。我从来没有使用C编程,但我不害怕学习如何...所以,你的评论中的基本思想是将上述代码与适当的R相关头文件放在一个文本文件中,用gcc编译,然后加载编译后的代码,调用它 .C(myCnanDetector) 代替对R功能的调用 nanDetector()? - Josh O'Brien
@JoshO'Brien我还在测试这个功能。它通常有效,但并不总是被调用。似乎代码被包装了 system.time({}) 然后不执行回调。这并不重要(我会放弃 system.time),但值得注意的是其他人尝试这一点。 - Iterator


我担心没有这样的捷径。理论上unix有 SIGFPE 你可以陷阱,但在实践中

  1. 没有标准的方法来使FP操作能够捕获它(即使C99没有包含它的规定) - 它是高度系统特定的(例如, feenableexcept 在Linux上, fp_enable_all 在AIX等上)或者需要为目标CPU使用汇编程序
  2. FP操作现在经常在像SSE这样的矢量单元中完成,所以你甚至不能确定FPU是否参与其中
  3. R拦截了一些类似的操作 NaNS, NAs并单独处理它们,因此它们不会进入FP代码

也就是说,如果你足够努力(禁用SSE等),你可以破解自己的R会为你的平台和CPU捕获一些例外。这不是我们考虑建立R的东西,但出于特殊目的,它可能是可行的。

但是,它仍然无法捕获 NaN/NA 操作,除非您更改R内部代码。此外,您必须检查您正在使用的每个程序包,因为它们可能在其C代码中使用FP操作,也可能会处理 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)

7
2018-02-06 23:10



亲爱的,我正在等待云分开,天使唱歌,并为你发布。一旦我读到“我害怕......”,我想“哦,是的,等到西蒙出现,这家伙是错的......让我们看看这是谁......”:)至于为什么我不发布在R-Devel上 - R-Core是可怕的。 :) - Iterator
但更严重的是,我一直在研究回调,条件处理程序和你的 inspect 包。 1:对于FPs,对象的内部结构是否会显示是否存在Infs或NA?即有没有关于非有限值的存在/位置的元信息? 2:如果有这样的信息,我可以使用调用处理程序在执行每个语句后调用检查错误值的调用吗? - Iterator
我看起来很可怕吗? ;)抱歉让你失望 - 恕我直言 SIGFPE 真的是要走的路(我怀疑这是Matlab使用的),但缺乏标准真的很令人沮丧(Matlab不需要特殊情况的NA)。 - Simon Urbanek
但是,不,除了查看其内容之外,没有办法告诉矢量包含Inf / NaN / NA。部分原因是 REALSXP 作为一个传递 double* 指向任何指针 C 代码,所以没有访问器宏,因此无法控制在哪里写入值(不像 STRSXP 例如) - 所以你必须每次检查整个向量来实现这样的位。 BTW inspect 现在是R的一部分,你称之为 .Internal(inspect(x))。 - Simon Urbanek
关于 SIGFPE - 你也许是对的。不幸的是,我无法进入Matlab的内部。 ;-)我希望它是一个.M文件,所以我可以检查,但是哦,不透明度是我避免使用Matlab的众多原因之一。关于 inspect()  - 约书亚乌尔里希建议使用单独的包装 这个答案。这是否意味着该软件包已被弃用? - Iterator