在Rcpp中按列对数据框进行排序

9

在RCpp中,有没有一种简单的方法可以按照DataFrame的两个(或多个或一个)列进行排序?

网络上提供了许多排序算法,或者我可以使用std::sort来包装DataFrame,但我想知道是否在RCpp或RCppArmadillo中已经有现成的方法可用?

我需要将此排序/排序作为另一个函数的一部分执行。

DataFrame myFunc(DataFrame myDF, NumericVector x) {
  //// some code here
  DataFrame myDFsorted = sort (myDF, someColName1, someColName2) // how to sort??
  //// some code here
}

我希望在 RCpp 中避免访问 R 的 order 函数(以保持 RCpp 代码的速度)。
非常感谢。
2个回答

12

困难在于数据框是一组向量,可能是不同类型的; 我们需要一种方法来独立于这些类型(整数、字符等)对它们进行排序。在dplyr中,我们开发了所谓的向量访问器。对于这个特定的问题,我们需要一组OrderVisitor,它们展示以下接口:

class OrderVisitor {
public:
    virtual ~OrderVisitor(){}

    /** are the elements at indices i and j equal */
    virtual bool equal(int i, int j) const  = 0 ;

    /** is the i element less than the j element */
    virtual bool before( int i, int j) const = 0 ;

    virtual SEXP get() = 0 ;

} ;

dplyr提供了OrderVisitor的实现,适用于我们在此文件中支持的所有类型,并且我们有一个调度函数order_visitor,它可以从向量创建一个OrderVisitor*

有了这个,我们可以将一组向量访问者存储到std::vector<OrderVisitor*>中;OrderVisitors有一个构造函数,它接受一个DataFrame和一个CharacterVector,表示我们想要用于排序的向量名称。

OrderVisitors o(data, names ) ;

然后我们可以使用OrderVisitors.apply方法,该方法基本上执行词典排序:

IntegerVector index = o.apply() ;

apply方法的实现是通过用0..n初始化一个IntegerVector,然后根据访问者使用std::sort进行排序。

inline Rcpp::IntegerVector OrderVisitors::apply() const {
    IntegerVector x = seq(0, nrows -1 ) ;
    std::sort( x.begin(), x.end(), OrderVisitors_Compare(*this) ) ;
    return x ;
}

这里相关的事情是 OrderVisitors_Compare 类如何实现 operator()(int,int)

inline bool operator()(int i, int j) const {
    if( i == j ) return false ;
    for( int k=0; k<n; k++)
        if( ! obj.visitors[k]->equal(i,j) )
            return obj.visitors[k]->before(i, j ) ; 
    return i < j ;
}

现在,index 给出了已排序数据的整数索引,我们只需通过使用这些索引来对 data 进行子集化,从而创建一个新的 DataFrame。为此,我们有另一种访问者,它包含在 DataFrameVisitors 类中。我们首先创建一个DataFrameVisitors

DataFrameVisitors visitors( data ) ;

这个类封装了一个std::vector<VectorVisitor*>。每个VectorVisitor*都知道如何使用整数向量索引来对自身进行子集化。这是从DataFrameVisitors.subset使用的:

template <typename Container>
DataFrame subset( const Container& index, const CharacterVector& classes ) const {
    List out(nvisitors);
    for( int k=0; k<nvisitors; k++){
       out[k] = get(k)->subset(index) ;    
    }
    structure( out, Rf_length(out[0]) , classes) ;
    return (SEXP)out ;
}
为了总结一下,这里有一个使用在dplyr中开发的工具的简单函数:
#include <dplyr.h>
// [[Rcpp::depends(dplyr)]]

using namespace Rcpp ;
using namespace dplyr ;

// [[Rcpp::export]]
DataFrame myFunc(DataFrame data, CharacterVector names) {
  OrderVisitors o(data, names ) ;
  IntegerVector index = o.apply() ;

  DataFrameVisitors visitors( data ) ;
  DataFrame res = visitors.subset(index, "data.frame" ) ;
  return res ;  
}

你能逐行解释一下 myFunc 吗? - G. Grothendieck
1
@uday 我认为这个问题已经被 这个提交 解决了。Gabor,我会进一步解释这个答案。 - Romain Francois
@G.Grothendieck,希望这个提供了足够的细节。 - Romain Francois
@RomainFrancois,dplyr 的新版本发布了吗? - uday
@uday 你可以从 GitHub 上获取开发版本。install_github( "hadley/dplyr" ) - Romain Francois
显示剩余2条评论

3
由于 data.frame 实际上是一个 C++ 中的列列表,因此您需要根据新的排序索引重新单独排序所有列。这与在 R 中为 data.frame 进行 [.., ..] 索引的方式不同。
例如,请参见 这篇关于向量排序的 Rcpp Gallery 文章 以获得一些指针。您可能需要提供要使用的新排序索引,之后这只是一个索引问题—— Gallery 上也有一些帖子。 这篇 SO 帖子 可以让您开始创建索引;这篇 bytes.com 帖子 讨论了相同的想法。 编辑:Armadillo 有函数 sort_index()stable_sort_index() 来创建所需的索引以重新排列列。这仅涵盖了单列情况,并且仅限于数值列,但这是一个开始。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接