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

时间:2012-02-06 21:15:44

标签: r debugging conditional nan

我错过了Matlab的条件调试标记:dbstop if infnan described here。如果设置,当遇到InfNaN时,此条件将停止代码执行(IIRC,Matlab没有NA)。

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

目前,我认为这样做的唯一方法是通过以下方式进行黑客攻击:

  1. 在可能遇到这些值的所有位置之后手动插入测试(例如,可能发生除0的除法)。测试将是对每个元素使用is.finite()described in this Q & A
  2. 使用body()修改代码以在每次操作之后调用单独的函数,或者可能只调用每个赋值,它们测试所有对象(可能还有所有环境中的所有对象)。
  3. 修改R的源代码(?!?)
  4. 尝试使用tracemem标识已更改的变量,并仅检查这些变量是否为错误值。
  5. (新增 - 请参阅注释2)使用某种调用处理程序/回调来调用测试函数。
  6. 第一个选项是我目前正在做的事情。这很乏味,因为我无法保证我已经检查了所有内容。第二个选项将测试所有内容,即使对象尚未更新。这是浪费大量时间。第3个选项将涉及修改NA,NaN和无限值(+/- Inf)的赋值,以便产生错误。这似乎比R Core更好。第四个选项就像第二个选项 - 我需要调用一个单独的函数列出所有内存位置,只需要ID那些已更改的内容,然后检查值;我甚至不确定它是否适用于所有对象,因为程序可能会进行就地修改,这似乎不会调用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.我不知道是否有一些简单的方法来测试非有限值的存在,例如:有关索引NAs,Infs等存储在对象中的对象的元信息,或者这些对象是否存储到位。到目前为止,我已经尝试过Simon Urbanek的inspect软件包,但还没有找到一种方法来判断是否存在非数字值。

    后续行动:Simon Urbanek在评论中指出,此类信息无法作为对象的元信息。

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

2 个答案:

答案 0 :(得分:7)

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

  1. 没有标准的方法可以让FP操作捕获它(即使C99没有包含它的规定) - 它是高度系统特定的(例如Linux上的feenableexceptfp_enable_all on AIX等)或者需要为目标CPU使用汇编程序
  2. FP操作现在经常在像SSE这样的矢量单元中完成,所以你甚至不能确定FPU是否参与其中
  3. R拦截NaN s,NA等内容的某些操作并单独处理它们,以便它们不会进入FP代码
  4. 那就是说,如果你足够努力(禁用SSE等),你可以破解自己的R会为你的平台和CPU捕获一些例外。我们不会考虑将其构建到R中,但出于特殊目的,它可能是可行的。

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

答案 1 :(得分:7)

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

我的是一个两步黑客。首先,我定义了一个函数nanDetector(),它用于检测计算可能返回的几种对象类型中的NaN。然后,在每个顶级任务/计算完成后,使用addTaskCallback()nanDetector()上调用函数.Last.value。当它在其中一个返回值中找到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.