首页 > 解决方案 > 具有任意类型向量输入的 Rcpp 快速统计模式函数

问题描述

我正在尝试为 R 构建一个超快速模式函数,用于聚合大型分类数据集。该函数应采用所有支持的 R 类型的向量输入并返回模式。我已阅读这篇文章此帮助页面和其他文章,但我无法让该函数接受所有 R 数据类型。我的代码现在适用于数字向量,我依赖于 Rcpp 糖包装函数:

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
int Mode(NumericVector x, bool narm = false) 
{
    if (narm) x = x[!is_na(x)];
    NumericVector ux = unique(x);
    int y = ux[which_max(table(match(x, ux)))];
    return y;
}

此外,我想知道是否可以将“ narm ”参数重命名为“ na.rm ”而不会出错,当然,如果有更快的方法在 C++ 中编写模式函数,我将不胜感激。

标签: c++rrcpp

解决方案


为了使该函数适用于任何向量输入,您可以为您想要支持的任何数据类型实现@JosephWood 算法,并从switch(TYPEOF(x)). 但这将是很多代码重复。相反,最好创建一个可以处理任何Vector<RTYPE>参数的通用函数。如果我们遵循 R 的范式,即一切都是向量,并让函数也返回 a Vector<RTYPE>,那么我们可以利用RCPP_RETURN_VECTOR. 请注意,我们需要 C++11 才能将其他参数传递给RCPP_RETURN_VECTOR. 一件棘手的事情是,您需要存储类型Vector<RTYPE>才能创建合适的std::unordered_map. 这里Rcpp::traits::storage_type<RTYPE>::type来救援。但是,std::unordered_map不知道如何处理 R 中的复数。为简单起见,我禁用了这种特殊情况。

把它们放在一起:

#include <Rcpp.h>
using namespace Rcpp ;

// [[Rcpp::plugins(cpp11)]]
#include <unordered_map>

template <int RTYPE>
Vector<RTYPE> fastModeImpl(Vector<RTYPE> x, bool narm){
  if (narm) x = x[!is_na(x)];
  int myMax = 1;
  Vector<RTYPE> myMode(1);
  // special case for factors == INTSXP with "class" and "levels" attribute
  if (x.hasAttribute("levels")){
    myMode.attr("class") = x.attr("class");
    myMode.attr("levels") = x.attr("levels");
  }
  std::unordered_map<typename Rcpp::traits::storage_type<RTYPE>::type, int> modeMap;
  modeMap.reserve(x.size());

  for (std::size_t i = 0, len = x.size(); i < len; ++i) {
    auto it = modeMap.find(x[i]);

    if (it != modeMap.end()) {
      ++(it->second);
      if (it->second > myMax) {
        myMax = it->second;
        myMode[0] = x[i];
      }
    } else {
      modeMap.insert({x[i], 1});
    }
  }

  return myMode;
}

template <>
Vector<CPLXSXP> fastModeImpl(Vector<CPLXSXP> x, bool narm) {
  stop("Not supported SEXP type!");
}

// [[Rcpp::export]]
SEXP fastMode( SEXP x, bool narm = false ){
  RCPP_RETURN_VECTOR(fastModeImpl, x, narm);
}

/*** R
set.seed(1234)
s <- sample(1e5, replace = TRUE)
fastMode(s)
fastMode(s + 0.1)
l <- sample(c(TRUE, FALSE), 11, replace = TRUE) 
fastMode(l)
c <- sample(letters, 1e5, replace = TRUE)
fastMode(c)
f <- as.factor(c)
fastMode(f) 
*/

输出:

> set.seed(1234)

> s <- sample(1e5, replace = TRUE)

> fastMode(s)
[1] 85433

> fastMode(s + 0.1)
[1] 85433.1

> l <- sample(c(TRUE, FALSE), 11, replace = TRUE) 

> fastMode(l)
[1] TRUE

> c <- sample(letters, 1e5, replace = TRUE)

> fastMode(c)
[1] "z"

> f <- as.factor(c)

> fastMode(f) 
[1] z
Levels: a b c d e f g h i j k l m n o p q r s t u v w x y z

如上所述,使用的算法来自Joseph Wood 的答案,该答案已在 CC-BY-SA 和 GPL >= 2 下明确获得双重许可。我关注 Joseph 并特此根据GPL(版本 2 )许可此答案中的代码或更高版本)除了隐式 CC-BY-SA 许可证。


推荐阅读