使用 "NA" 将 R 数组传递给 C 函数

passing in R array to C function with "NA"

我使用 C 库在 R 中工作。我需要向 C 函数传递一个数字在 1 到 10 之间的数组,但也可以是 "NA"。然后在 C 中,根据我需要设置输出的值。 这是一个简化的代码

heredyn.load("ranking.so")

fun <- function(ranking) {
  nrak <- length(ranking)
  out <- .C("ranking", as.integer(nrak), as.character(ranking), rr = as.integer(vector("integer",nrak)))
  out$rr
}

ranking <- sample(c(NA,seq(1,10)),10,replace=TRUE)
rr <- fun(ranking)

C 函数可以简单地是这样

#include <R.h>

void ranking(int *nrak, char *ranking, int *rr) {
  int i ;

  for (i=0;i<*nrak;i++) {
    if (ranking[i] == 'NA')
      rr[i] = 1 ;
    else
      rr[i] = (int) strtol(&ranking[i],(char **)NULL,10) ;
  }
}

由于 "NA" 值,我将排名设置为字符,但也许还有另一种方法可以做到这一点,使用整数并且在调用函数之前不将 "NA" 替换为 0?

(像这样的代码,总是给我一个零数组...)

您正试图解决几个微妙且重要的问题,尤其是如何使用 R 编译代码以及测试非有限值。

您寻求有关 C 的帮助。我建议您使用 C++——您不需要以复杂的方式使用它。考虑这个包含一个函数的短文件,该函数可以按照您建议的方式处理向量(我只是测试 NA 然后将 42 指定为 simplicit 的标记)或者对值进行平方:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector foo(NumericVector x) {
    unsigned int n = x.size();
    for (unsigned int i=0; i<n; i++)
        if (NumericVector::is_na(x[i]))
            x[i] = 42.0;
        else 
            x[i] = pow(x[i], 2);
    return x;
}


/*** R
foo( c(1, 3, NA, NaN, 6) )
*/

如果我将其保存在我的盒子上作为 /tmp/foo.cpp,以便编译、link、加载甚至 运行 嵌入式 R 使用示例,我只需要一行来调用sourceCpp():

R> Rcpp::sourceCpp("/tmp/foo.cpp")

R> foo( c(1, 3, NA, NaN, 6))
[1]  1  9 42 42 36
R> 

我们可以对整数做同样的事情:

// [[Rcpp::export]]
IntegerVector bar(IntegerVector x) {
    unsigned int n = x.size();
    for (unsigned int i=0; i<n; i++)
        if (IntegerVector::is_na(x[i]))
            x[i] = 42;
        else 
            x[i] = pow(x[i], 2);
        return x;
}

使用 R_NaInt 测试该值是否为 NA,如

#include <R.h>

void ranking_c(int *nrak, int *ranking, int *rr) {
  for (int i=0; i < *nrak; i++)
      rr[i] = R_NaInt == ranking[i] ? -1 : ranking[i];
}

通过明确允许 NA 从 R 调用

> x = c(1:2, NA_integer_)
> .C("ranking_c", length(x), as.integer(x), integer(length(x)), NAOK=TRUE)[[3]]
[1]  1  2 -1

或者,使用 R 的 .Call() 接口。每个 R 对象都表示为一个 S 表达式。有一些 C 级函数可以操作 S 表达式,例如长度 Rf_length()、数据访问 INTEGER() 和分配 Rf_allocVector() 不同类型的 S 表达式,例如用于整数向量的 INTSXP。

R 内存管理使用垃圾收集器,可以 运行 在任何分配内存的调用上。因此,最佳做法是 PROTECT() 在范围内的任何 R 分配。

您的函数将接受 0 个或多个 S 表达式作为输入,并且 return 单个 S 表达式;它可能被实现为

#include <Rinternals.h>
#include <R_ext/Arith.h>

SEXP ranking_call(SEXP ranking)
{
    /* allocate space for result, PROTECTing from garbage collection */
    SEXP result = PROTECT(Rf_allocVector(INTSXP, Rf_length(ranking)));

    /* assign result */
    for (int i = 0; i < Rf_length(ranking); ++i)
        INTEGER(result)[i] =
            R_NaInt == INTEGER(ranking)[i] ? -1 : INTEGER(ranking)[i];

    UNPROTECT(1);               /* no more need to protect */
    return result;
}

并使用 .Call("ranking_call", as.integer(ranking)) 从 R 中调用。

在速度和内存分配方面,使用 .Call.C 更有效(.C 可能会在途中复制原子向量),但使用它的主要原因是它在直接使用 R 的数据结构方面提供了灵活性。当 return 值比原子向量更复杂时,这一点尤为重要。