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

yhic 发布于 2019-03-09 r 最后更新 2019-03-09 14:35 5 浏览

我会编写一个函数(使用Rcpp),它从R向量中删除所有NA值。 在这之前,我通过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
为什么相同的功能(显然)行为不同?这里发生了什么?
已邀请:

xnam

赞同来自:

按照你的回答,我会使用这样的模板作为模板:

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> = 4308)的开发版本,它适用于所有类型的我,然后我们可以使用我们的RCPP_RETURN_VECTOR调度宏而不是编写switch
// [[Rcpp::export]]
SEXP na_omit( SEXP x ){
     RCPP_RETURN_VECTOR( na_omit_template, x ) ;   
}
na_omit已包含在Rcpp(svn revision> = 4309)中,只有一些修改,即它可以处理命名向量和任意糖表达式。

rsaepe

赞同来自:

我继续研究有关模板问题的解决方案(即参见@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向量?
我清楚地想到了case STRSXP: return na_omit_template<STRSXP, ?>(x);,但是这个陈述不起作用,将std::stringRcpp:String替换为?

pquo

赞同来自:

模板机制似乎工作正常。

> 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>()));
}

kquis

赞同来自:

一些实现:

// 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);
}