在Rcpp中按列排序数据帧

时间:2014-05-31 03:39:52

标签: r rcpp

有没有简单的方法可以通过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 个答案:

答案 0 :(得分: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 ;

} ;
然后,对于我们在此file中支持的所有类型,

dplyr都有OrderVisitor的实现,并且我们有一个调度函数order_visitor,它从向量中生成OrderVisitor*

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

OrderVisitors o(data, names ) ;

然后我们可以使用基本上执行词典排序的OrderVisitors.apply method

IntegerVector index = o.apply() ;

apply方法是通过简单地根据访问者初始化IntegerVector 0..n然后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为我们提供了排序数据的整数索引,我们只需要通过使用这些索引对DataFrame进行子集来从data创建一个新的data 。为此我们有另一种访问者,封装在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 ;  
}

答案 1 :(得分:3)

因为data.frame实际上是C ++中的列列表,所以在给定新的ording索引的情况下,您必须单独重新排序所有列。这与[.., ..]中的data.frame索引在R中的工作方式不同。

参见例如this Rcpp Gallery article on sorting vectors有些指示。 您可能必须提供要使用的新排序索引,之后它只是一个索引问题 - 而且在Gallery上也有一些帖子。

This SO post可以帮助您开始创建索引; this bytes.com post讨论了同样的想法。

修改:Armadillo has function sort_index()stable_sort_index()创建重新排列列所需的索引。这仅涵盖一列情况,并且仅限于数字列,但它是一个开始。