模板化的Rcpp函数用于擦除NA值

时间:2013-04-11 16:12:10

标签: r templates rcpp

我会编写一个函数(使用Rcpp),删除NA向量中的所有R值。

在此之前,我通过Rcpp::cppFunction函数执行了一些测试功能。

library(inline)
cppFunction('
  Vector<INTSXP> na_test(const Vector<INTSXP>& x) {
    return setdiff(x, Vector<INTSXP>::create(::traits::get_na<INTSXP>()));
  }
')

这是这样的:

na_test(c(1, NA, NA, 1, 2, NA))
# [1] 1 2

之后,我尝试通过C++ 模板机制来概括此功能。

因此,在外部的 .cpp 文件中(通过sourceCpp函数提供),我写道:

template <int RTYPE>
Vector<RTYPE> na_test_template(const Vector<RTYPE>& x) {
    return setdiff(x, Vector<RTYPE>::create(::traits::get_na<RTYPE>()));
}

// [[Rcpp::export(na_test_cpp)]]
SEXP na_test(SEXP x) {
    switch(TYPEOF(x)) {
        case INTSXP:
            return na_test_template<INTSXP>(x);
        case REALSXP:
            return na_test_template<REALSXP>(x);
    }
    return R_NilValue;
}

此代码编译但行为不同,我无法解释原因。

逸岸:

na_test_cpp(c(1, NA, NA, 1, 2, NA))
# [1]  2 NA NA NA  1

为什么相同的功能(显然)表现不同?这里发生了什么?

4 个答案:

答案 0 :(得分:6)

跟进你的回答,我会用这样的模板作为模板:

template <int RTYPE>
Vector<RTYPE> na_omit_template(const Vector<RTYPE>& x) {
    int n = x.size() ;
    int n_out = n - sum( is_na(x) ) ;

    Vector<RTYPE> out(n_out) ;
    for( int i=0, j=0; i<n; i++){
        if( Vector<RTYPE>::is_na( x[i] ) ) continue ;
        out[j++] = x[i];
    }
    return out ;
}

所以我们的想法是首先计算结果的长度,然后只使用Rcpp向量类而不是std::vector。这将导致更少的数据副本。


使用Rcpp的开发版本(svn revision&gt; = 4308),它适用于所有类型的我,然后我们可以使用我们的RCPP_RETURN_VECTOR调度宏而不是编写{{1 }}:

switch

// [[Rcpp::export]] SEXP na_omit( SEXP x ){ RCPP_RETURN_VECTOR( na_omit_template, x ) ; } 已包含在na_omit(svn revision&gt; = 4309)中,只需进行一些修改,即它可以处理命名向量和任意糖表达式。

答案 1 :(得分:1)

我继续研究有关模板问题的解决方案(即参见@Sameer回复)。

所以我写了另一个函数,现在模板机制正常工作。

在外部.cpp文件中:

#include <Rcpp.h>
template <int RTYPE, class T>
Vector<RTYPE> na_omit_template(const Vector<RTYPE>& x) {
    typedef typename Vector<RTYPE>::iterator rvector_it;
    if (x.size() == 0) {
        return x;
    }
    std::vector<T> out;
    rvector_it it = x.begin();
    for (; it != x.end(); ++it) {
        if (!Vector<RTYPE>::is_na(*it)) {
            out.push_back(*it);
        }
    }
    return wrap(out);
}

// [[Rcpp::export(na_omit_cpp)]]
SEXP na_omit(SEXP x) {
    switch(TYPEOF(x)) {
        case INTSXP:
            return na_omit_template<INTSXP, int>(x);
        case REALSXP:                   
            return na_omit_template<REALSXP, double>(x);
        case LGLSXP:
            return na_omit_template<LGLSXP, bool>(x);
        case CPLXSXP:
            return na_omit_template<CPLXSXP, Rcomplex>(x);
        case RAWSXP:
            return na_omit_template<RAWSXP, Rbyte>(x);
        default:
            stop("unsupported data type");
    }
}

此功能会删除 NA ,这是我最初的目的。

不幸的是,目前它并不适用于所有类型的矢量,如下面的R示例所示。

library(Rcpp)
sourceCpp('file.cpp')
na_omit_cpp(as.integer(c(1, NA, NA, 1, 2, NA))) # OK
# [1] 1 1 2
na_omit_cpp(as.numeric(c(1, NA, NA, 1, 2, NA)))
# [1] 1 1 2
na_omit_cpp(c(NA, 1L, NA, 3L, NA)) # OK
# [1] 1 3
na_omit_cpp(c(NA, 2L, 1, NA)) # OK
# [1] 2 1
na_omit_cpp(c(1.0, 1.1, 2.2, NA, 3, NA, 4)) # OK
# [1] 1.0 1.1 2.2 3.0 4.0
na_omit_cpp(c(1L, NaN, NaN, 0, NA)) # OK
# [1]   1 NaN NaN   0
na_omit_cpp(c(NA, NaN, 1.0, 0.0, 2.2, NA, 3.3, NA, 4.4)) # OK
# [1] NaN 1.0 0.0 2.2 3.3 4.4
na_omit_cpp(as.logical(c(1, 0, 1, NA))) # OK
# [1]  TRUE FALSE  TRUE
na_omit_cpp(as.logical(c(TRUE, FALSE, NA, TRUE, NA))) # OK
# [1]  TRUE FALSE  TRUE
# empty vectors ?
na_omit_cpp(c(NA)) # OK
# logical(0)
na_omit_cpp(numeric(0)) # OK
# numeric(0)
na_omit_cpp(logical(0)) # OK
# logical(0)
na_omit_cpp(raw(0)) # OK
# raw(0)
na_omit_cpp(as.raw(c(40,16,NA,0,2))) # NO! (R converts it to 00)
# [1] 28 10 00 00 02
# Warning message ...
na_omit_cpp(as.complex(c(-1, 2, 1, NA, 0, NA, -1))) # NO! 
# [1] -1+0i  2+0i  1+0i    NA  0+0i    NA -1+0i

因此,除raw向量和complex向量外,此函数几乎适用于所有情况。

当前未解决的问题是:

  1. 我不知道为什么会出现这个错误,我想知道原因。有什么想法吗?
  2. 如@Sameer所示,之前的模板化函数有一种奇怪的行为。
  3. 如何接受character向量?
  4. 我清楚地想到了case STRSXP: return na_omit_template<STRSXP, ?>(x);,但这句话无效,将std::stringRcpp:String替换为?

答案 2 :(得分:0)

模板机制似乎工作正常。

> na_test_cpp(as.numeric(c(1, NA, NA, 1, 2, NA)))
[1]  2 NA NA NA  1

> na_test_cpp(as.integer(c(1, NA, NA, 1, 2, NA)))
[1] 1 2

这段代码适用于INTSXP,但不适用于REALSXP

Vector<REALSXP> na_test_real(const Vector<REALSXP>& x) {
  return setdiff(x, Vector<REALSXP>::create(::traits::get_na<REALSXP>()));
}

答案 3 :(得分:0)

一些实施:

// naive
template <int RTYPE>
Vector<RTYPE> na_omit_impl(const Vector<RTYPE>& x) {
    std::size_t n = x.size();
    // Estimate out length
    std::size_t n_out = 0;
    for(std::size_t i = 0; i < n; ++i) {
        if (Vector<RTYPE>::is_na(x[i])) continue;
        ++n_out;
    }
    // exit if no NAs
    if (n_out == n) return x;
    // allocate vector without filling
    Vector<RTYPE> res = no_init(n_out);
    // fill result vector
    for(std::size_t i = 0, j = 0; i < n; ++i) {
        if (Vector<RTYPE>::is_na(x[i])) continue;
        res[j] = x[i];
        ++j;
    }
    return res;
}

// STL algorithms
template <int RTYPE>
struct not_na {
    typedef typename Vector<RTYPE>::stored_type type;
    bool operator() (const type& i) {
        return !Vector<RTYPE>::is_na(i);
    }
};

template <int RTYPE>
Vector<RTYPE> na_omit_impl(const Vector<RTYPE>& x) {
    // Estimate out length
    std::size_t n_out = std::count_if(x.begin(), x.end(), not_na<RTYPE>());
    // exit if no NAs
    if (n_out == x.size()) return x;
    // allocate vector without filling
    Vector<RTYPE> res = no_init(n_out);
    // fill result vector
    std::copy_if(x.begin(), x.end(), res.begin(), not_na<RTYPE>());
    return res;
}

// Rcpp sugar
template <class T>
T na_omit_impl(const T& x) {
    return x[!is_na(x)];
}

// Rcpp sugar
template <class T>
T na_omit_impl(const T& x) {
    return Rcpp::na_omit(x);
}

所有实现都适用于RCPP_RETURN_VECTOR宏:

// [[Rcpp::export]]
RObject na_omit(RObject x){
    RCPP_RETURN_VECTOR(na_omit_impl, x);
}