首页 > 解决方案 > Rcpp:这是使用 Rcpp 修改数据框某些列的最佳方法

问题描述

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

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

  2. 为什么 updateDF1 和 updateDF2 的性能差异不显着?使用或不使用引用 (&) 传递参数有什么区别?

  3. 函数编码是否池化,还有另一种方式,比如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

标签: rrcpp

解决方案


根据@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

推荐阅读