如果遇到非有限值(NA、NaN 或 Inf),如何强制出错

Posted

技术标签:

【中文标题】如果遇到非有限值(NA、NaN 或 Inf),如何强制出错【英文标题】:How to force an error if non-finite values (NA, NaN, or Inf) are encountered 【发布时间】:2012-02-28 08:33:45 【问题描述】:

我错过了 Matlab 中的一个条件调试标志:dbstop if infnandescribed here。如果设置,此条件将在遇到 InfNaN 时停止代码执行(IIRC,Matlab 没有 NA)。

与在每次赋值操作后测试所有对象相比,我如何在 R 中以更有效的方式实现这一点?

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

    在可能遇到这些值的所有位置之后手动插入测试(例如,一个除法,其中可能发生被 0 的除法)。测试将在每个元素上使用is.finite()、described in this Q & A。 使用body() 修改代码以调用单独的函数,在每次操作之后或可能只是在每次赋值之后,它会测试所有对象(也可能是所有环境中的所有对象)。 修改 R 的源代码 (?!?) 尝试使用tracemem 来识别已更改的变量,并仅检查这些变量是否存在错误值。 (新 - 见注 2)使用某种调用处理程序/回调来调用测试函数。

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

有没有更好的方法我错过了?也许是 Mark Bravington、Luke Tierney 的一些聪明的工具,或者一些相对基本的工具——类似于 options() 参数或编译 R 时的标志?

示例代码 这里有一些非常简单的示例代码供测试,其中包含 Josh O'Brien 提出的 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.tablebigmemory 使用的对象(即基于磁盘的内存映射矩阵)。这些似乎与标准矩阵和 data.frame 操作有一些不同的内存行为。

注意 2. 回调的想法似乎更有希望,因为这不需要我编写改变 R 代码的函数,例如通过body() 的想法。

注3。我不知道是否有一些简单的方法来测试非有限值的存在,例如关于对象的元信息,索引 NA、Infs 等存储在对象中的位置,或者这些对象是否存储在适当的位置。到目前为止,我已经尝试了 Simon Urbanek 的 inspect 包,但还没有找到一种方法来判断是否存在非数字值。

跟进:Simon Urbanek 在评论中指出,此类信息不可用作对象的元信息。

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

【问题讨论】:

一些原始函数内置了此功能,即,如果提供或提供非有限结果,它们会返回错误或警告。以sin(Inf) 为例。也许这是你可以探索的东西。 嗯,Inf 或 NaN 应该 停止你的函数/代码并不总是这样(NA 是一个单独的情况,因为它一直被故意用作“填充物” '或'标记')。我经常运行一些产生一些 Inf 值的操作,比如在某个矩阵的低信号区域。我怀疑无论如何对可疑变量使用is.infinite 和/或is.nan 会得到更好的信息。 @CarlWitthoft 在我目前正在处理的代码+数据方案中,问题值正是这三个 - NA、NaN 和 Infs。在其他情况下,我肯定需要 NA,但今天不需要。 :) 一旦这些发生,我需要代码中止(它的计算量相当大,数据量的 b/c)。因此,我真的想触发错误(或至少是警告)的原因。 @BrandonBertelsen 感谢您的建议。我查看了基本的算术运算符,并没有从 R 内部弹出任何内容。我怀疑这种特殊处理存在于某些原始函数的 C 代码中,但总有可能它是只有 R 知道的那些东西之一巫师。 :) @Iterator -- 感谢您提出发人深省的问题。它给了我一种令人费解的锻炼,让我回到 SO。请让我知道我下面的建议是否对您的特殊情况有所帮助! 【参考方案1】:

下面概述的想法(及其实现)非常不完善。我什至不愿建议它,但是: (a) 我认为它有点有趣,即使它很丑陋; (b) 我能想到它有用的情况。鉴于您现在在每次计算后手动插入检查,我希望您的情况就是其中之一。

我的是两步破解。首先,我定义了一个函数nanDetector(),该函数旨在检测您的计算可能返回的几种对象类型中的NaNs。然后,在每个***任务/计算完成后,它使用addTaskCallback().Last.value 上调用函数nanDetector()。当它在其中一个返回值中找到 NaN 时,它会抛出一个错误,您可以使用它来避免任何进一步的计算。

它的缺点:

如果您执行设置stop(error = recover) 之类的操作,则很难判断错误是在哪里触发的,因为错误总是从stopOnNaNs() 内部抛出。

当它抛出错误时,stopOnNaNs() 在返回 TRUE 之前被终止。因此,它会从任务列表中删除,您需要使用addTaskCallback(stopOnNaNs) 重置它才能再次使用它。 (详情请参阅'Arguments' section of ?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.

【讨论】:

Hot diggity dog,这有几个我刚开始考虑的有趣想法,即使用回调。它是否可以在我的代码中工作,我必须看看,但它仍然是指导性的。谢谢! FWIW 在 R 代码中执行检查将非常低效 - 在 C 中这样做很简单,因为它本质上只是 if (TYPEOF(x) == REALSXP) double *d = REAL(x); int n = LENGTH(x); for(int i = 0; i &lt; n; i++) if (!R_finite(d[i])) return ScalarLogical(0); return ScalarLogical(1); 并且您可以更快地递归(并且无需复制)到递归对象. @SimonUrbanek 感谢 C 的指导——那肯定很快。这种方法是否易于通过inline 包开发使用?或者我可以希望一颗星星出现在 R 中吗? :)(我想这是一个 R-devel 问题。;-)) @SimonUrbanek -- 这听起来很有趣,但我不知道如何在实践中实现它。我从来没有用 C 编程过,但我不怕学习如何......所以,你评论中的基本思想是将上述代码与适当的 R 相关头文件放在一个文本文件中,用 gcc 编译它,然后加载编译后的代码,用.C(myCnanDetector)代替R函数nanDetector()调用它? @JoshO'Brien 我仍在测试该功能。它通常有效,但并不总是被调用。似乎如果代码包含在system.time() 中,则不会执行回调。并不是说这最终很重要(我会放弃system.time),但对于尝试此操作的其他人来说值得注意。【参考方案2】:

恐怕没有这样的捷径。理论上,在 unix 上有 SIGFPE 你可以抓住,但在实践中

    没有标准的方法来启用 FP 操作来捕获它(甚至 C99 也不包含对此的规定) - 它是高度系统特定的(例如,Linux 上的feenableexcept,AIX 上的fp_enable_all 等)或者需要为您的目标 CPU 使用汇编器 如今,FP 操作通常在 SSE 等向量单元中完成,因此您甚至无法确定是否涉及 FPU,并且 R 会截获一些对诸如NaNs、NAs 之类的操作并单独处理它们,以免它们进入 FP 代码

也就是说,如果你足够努力(禁用 SSE 等),你可以自己破解一个 R,它会为你的平台和 CPU 捕获一些异常。我们不会考虑将其构建到 R 中,但出于特殊目的,它可能是可行的。

但是,除非您更改 R 内部代码,否则它仍然无法捕获 NaN/NA 操作。此外,您必须检查您正在使用的每个包,因为它们可能在其 C 代码中使用 FP 操作,并且还可能单独处理 NA/NaN

如果您只担心被零除或上溢/下溢之类的事情,上述方法将起作用,并且可能最接近解决方案。

仅检查您的结果可能不太可靠,因为您不知道结果是否基于某个中间的NaN 计算,该计算更改了可能也不需要为NaN 的聚合值。如果您愿意放弃这种情况,那么您可以简单地递归遍历您的结果对象或工作区。这不应该是非常低效的,因为您只需要担心REALSXP 而无需担心其他任何事情(除非您也不喜欢NAs - 那么您将有更多的工作)。


这是一个可用于递归遍历 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)

【讨论】:

该死,我在等云散开,等天使歌唱,等你发帖。当我读到“我害怕......”时,我想“哦,是的,等西蒙出现,这家伙太不对了......让我们看看这是谁......” :) 至于我为什么不发帖在 R-Devel 上 - R-Core 很可怕。 :) 不过,更严重的是,我一直在研究回调、条件处理程序和您的 inspect 包。 1:对于FP,对象的内部结构是否不会显示是否有Infs或NAs? IE。是否有关于非有限值的存在/位置的元信息? 2:如果有这样的信息,我可以在每条语句执行后使用调用处理程序调用一个调用来检查错误值吗? 我看起来很吓人吗? ;) 很抱歉让你失望 - 恕我直言 SIGFPE 确实是要走的路(我怀疑这就是 Matlab 使用的)但缺乏标准真的令人沮丧(而且 Matlab 不需要特殊情况下的 NA)。 但是,不,除了查看其内容之外,没有办法判断向量是否包含 Inf/NaN/NA。部分原因是REALSXP 作为double* 指向任何C 代码的指针传递,因此没有访问器宏,因此无法控制将哪些值写入何处(例如,与STRSXP 不同) - 所以你每次都必须检查整个向量才能实现这样的位。顺便说一句,inspect 现在是 R 的一部分,你称它为 .Internal(inspect(x)) 关于SIGFPE - 你可能是对的。不幸的是,我无法了解 Matlab 的内部结构。 ;-) 我希望它是一个 .M 文件,所以我可以检查一下,但是哦,不透明度是我避免使用 Matlab 的众多原因之一。关于inspect() - Joshua Ulrich 建议使用this answer 中的单独包。这是否意味着该软件包已被弃用?

以上是关于如果遇到非有限值(NA、NaN 或 Inf),如何强制出错的主要内容,如果未能解决你的问题,请参考以下文章

如何消除“外部函数调用中的 NA/NaN/Inf (arg 7)”使用 randomForest 运行预测

一种检测 NaN、NA、Inf、-Inf 等的功能?

TensorFlow NaN 错误?

在 R 的数据集中将 -inf、NaN 和 NA 值替换为零

线性模型函数 lm() 错误:外部函数调用中的 NA/NaN/Inf (arg 1)

r 用R中的0替换NA,NAN和Inf