Rcpp:这是使用 Rcpp 修改数据框某些列的最佳方法

Rcpp: Which is the best way to modify some columns of a dataframe with Rcpp

通常我必须处理大空间数据,期望高速和内存效率。 假设我想在 Rcpp 中使用自定义函数修改数据帧的某些数字列,我对 C++ 和 Rcpp 的引用和复制机制感到困惑。使用下面的三个最小示例代码,请您帮助我澄清以下问题:

  1. updateDF3 是以最高速度和最低内存要求执行此类任务的最佳函数吗? 这个函数是从一个类似的问题 here 修改而来的,但我不明白作者给出的警告,“这种方法存在问题。你的原始数据框和你创建的数据框共享相同的向量等等坏事可能会发生。”如果我仅将此函数用于 updateDF3 的子函数并从 R 调用,它安全吗?

  2. 为什么updateDF1和updateDF2的性能差别不大? 传参有无引用(&)有什么区别?

  3. 函数编码是不是pooly还有其他方式,比如DataFrame out=clone(df), tmpstr=asstd::string(colnames[v])?

提前致谢。

#include <Rcpp.h>
#include <iostream>
using namespace Rcpp;
using namespace std; 

// [[Rcpp::export]]
bool contains(CharacterVector x, std::string y) { 
  return std::find(x.begin(), x.end(), y)!=x.end(); 
}


// [[Rcpp::export]]
DataFrame updateDF1(DataFrame df, Nullable<Rcpp::CharacterVector> vars=R_NilValue) {
  DataFrame out=clone(df);
  string tmpstr;
  NumericVector tmpv;
  if(vars.isNotNull()){
    CharacterVector selvars(vars);
    for(int v=0;v<selvars.size();v++){
      tmpstr=as<std::string>(selvars[v]);
      tmpv=df[tmpstr];
      tmpv=tmpv+1.0;
      out[tmpstr]=tmpv;
    }
  }
  return out;
}

// [[Rcpp::export]]
DataFrame updateDF2(DataFrame& df, Nullable<Rcpp::CharacterVector> vars=R_NilValue) {
  DataFrame out=clone(df);
  string tmpstr;
  NumericVector tmpv;
  if(vars.isNotNull()){
    CharacterVector selvars(vars);
    for(int v=0;v<selvars.size();v++){
      tmpstr=as<std::string>(selvars[v]);
      tmpv=df[tmpstr];
      tmpv=tmpv+1.0;
      out[tmpstr]=tmpv;
    }
  }
  return out;
}

// [[Rcpp::export]]
List updateDF3(DataFrame& df, Nullable<Rcpp::CharacterVector> vars=R_NilValue) {
  List out(df.size());
  CharacterVector colnames=df.attr("names");
  string tmpstr;
  NumericVector tmpv;
  
  for(int v=0;v<df.size();v++){
    if(vars.isNotNull()){  
      CharacterVector selvars(vars); 
      tmpstr=as<std::string>(colnames[v]);
      if(contains(selvars,tmpstr)){
        tmpv=df[tmpstr];
        tmpv=tmpv+1.0;
        out[v]=tmpv;
      }else{
        out[v]=df[tmpstr];
      }
    }else{
      out[v]=df[tmpstr];
    }
  }
  out.attr("class") = df.attr("class") ;
  out.attr("row.names") = df.attr("row.names") ;
  out.attr("names") = df.attr("names") ;
  return out;
}


/*** R

df=as.data.frame(matrix(1:120000000,nrow=10000000))
names(df)=paste("band",1:ncol(df),sep="_")
df=cbind(x="charcol",df)
microbenchmark::microbenchmark(
  x1<<-updateDF1(df,vars=names(df)[-1]),
  x2<<-updateDF2(df,vars=names(df)[-1]),
  x3<<-updateDF3(df,vars=names(df)[-1]),
  times=10
)
identical(x1,x2)
identical(x1,x3)
*/
##performance
#Unit: milliseconds
#                                       expr      min       lq     mean   median
# x1 <<- updateDF1(df, vars = names(df)[-1]) 587.6023 604.9242 711.8981 651.1242
# x2 <<- updateDF2(df, vars = names(df)[-1]) 581.7129 641.2876 882.9999 766.9354
# x3 <<- updateDF3(df, vars = names(df)[-1]) 406.1824 417.5892 542.2559 420.8485

根据@Roland的建议,最好的方法是通过修改updateDF2使用参考方法,代码如下:

// [[Rcpp::export]]
DataFrame updateDF(DataFrame& df, Nullable<Rcpp::CharacterVector> vars=R_NilValue) {
  string tmpstr;
  NumericVector tmpv;
  if(vars.isNotNull()){
    CharacterVector selvars(vars);
    for(int v=0;v<selvars.size();v++){
      tmpstr=selvars[v];
      tmpv=df[tmpstr];
      tmpv=tmpv+1.0;
      df[tmpstr]=tmpv;
    }
  }
  return df;
}

表现:

Unit: milliseconds
                                       expr      min       lq     mean   median
 x1 <<- updateDF1(df, vars = names(df)[-1]) 573.8246 728.4211 990.8680 951.3108
 x2 <<- updateDF2(df, vars = names(df)[-1]) 595.7339 694.0645 935.4226 941.7450
 x3 <<- updateDF3(df, vars = names(df)[-1]) 197.7855 206.4767 377.4378 225.0290
 x4 <<- updateDF(df, vars = names(df)[-1])  148.5119 149.7321 247.1329 152.3744