即使在Rcpp中为NULL,如何获取列名?

时间:2019-04-25 13:32:21

标签: r rcpp

我想获取一个矩阵的列名以设置另一个列,但是如果矩阵没有列名(或设置为NULL),则以下代码会使我的R会话崩溃。

CharacterVector cn = colnames(x);

以下代码是即使没有矩阵也能获得矩阵的列名的方法。

#include <Rcpp.h>
using namespace Rcpp;

// Get column names or empty
// [[Rcpp::export]]
CharacterVector get_colnames(const NumericMatrix &x) {
   CharacterVector cn;

   SEXP cnm = colnames(x);
   if (!Rf_isNull(cnm)) cn = cnm;

   return(cn);
}

还有更优雅的方式吗?

2 个答案:

答案 0 :(得分:3)

一些注意事项:

  1. 矩阵不一定总是设置colnames()rownames()
    • 如果设置了一个,则该对象的属性为dimnames
  2. 可以通过R的C API检查值是否存在。
    • 例如Rf_isNull()
  3. 另一种存在性检查将是验证dimnames是否是对象属性的一部分。
    • 从那里,检查dimnames中的条目是否为空。

让我们首先通过创建一个没有名字的矩阵,然后再创建一个名字为 的矩阵来验证这些点。最后,我们将介绍您的函数的更详细的版本,该版本尝试解析没有列名的矩阵。

矩阵构造

因此,传统的矩阵构造应为:

x_no_names = matrix(1:4, nrow = 2)

x_no_names
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4
colnames(x_no_names)
#> NULL
rownames(x_no_names)
#> NULL
attributes(x_no_names)
#> $dim
#> [1] 2 2

因此,对于没有列或行名的 创建的矩阵,没有dimnames

如果我们为属性分配列名或行名会怎样?

# Create a matrix with names
x_named = x_no_names
colnames(x_named) = c("Col 1", "Col 2")
rownames(x_named) = c("Row 1", "Row 2")

# View attributes
attributes(x_named)
#> $dim
#> [1] 2 2
#> 
#> $dimnames
#> $dimnames[[1]]
#> [1] "Row 1" "Row 2"
#> 
#> $dimnames[[2]]
#> [1] "Col 1" "Col 2"

# View matrix object
x_named
#>       Col 1 Col 2
#> Row 1     1     3
#> Row 2     2     4

注意:matrix对象现在具有dimnames属性。

在C ++中实现检查

通过了解matrix结构,我们可以检查:

  1. dimnames是否作为属性存在于矩阵上?
  2. dimnames中的第二个条目不是NULL吗?

注意:这种方法会使原始功能更加冗长。需要权衡的是该函数将避免使用SEXP返回类型。

#include <Rcpp.h>

// Get column names or empty
// [[Rcpp::export]]
Rcpp::CharacterVector get_colnames(const Rcpp::NumericMatrix &x) {

  // Construct a character vector
  Rcpp::CharacterVector cn;

  // Create a numerical index for each column
  Rcpp::IntegerVector a = Rcpp::seq_len(x.ncol());
  // Coerce it to a character
  Rcpp::CharacterVector b = Rcpp::as<Rcpp::CharacterVector>(a);

  // Assign to character vector
  cn  = b;

  if(x.hasAttribute("dimnames")) {
    Rcpp::List dimnames = x.attr( "dimnames" ) ;

    if(dimnames.size() != 2) {
      Rcpp::stop("`dimnames` attribute must have a size of 2 instead of %s.", dimnames.size());
    }

    // Verify column names exist by checking for NULL
    if(!Rf_isNull(dimnames[1]) ) {
      // Retrieve colnames and assign to cn.
      cn = dimnames[1];
    } else {
     // Assign to the matrix
     colnames(x) = cn;
    }
  } 

  return(cn);
}

测试 C ++ 变体

现在调用该函数将给出:

get_colnames(x_no_names)
#> [1] "1" "2"

get_colnames(x_named)
#> [1] "Col 1" "Col 2"

第一个指示我们正在使用生成的索引,而第二个指示正在检索值。

答案 1 :(得分:2)

我从头开始,然后分心。 @coatless遮住了它,这只是短了。

代码

#include <Rcpp.h>

// [[Rcpp::plugins(cpp11)]]
using namespace Rcpp;

// [[Rcpp::export]]
CharacterVector getColnames(const NumericMatrix &x) {
  size_t nc = x.cols();
  SEXP s = x.attr("dimnames");  // could be nil or list
  if (Rf_isNull(s)) {           // no dimnames, need to construct names
    CharacterVector res(nc);
    for (size_t i=0; i<nc; i++) {
      res[i] = std::string("V") + std::to_string(i);
    }
    return(res);
  } else {                      // have names, return colnames part
    List dn(s);
    return(dn[1]);
  }

}

/*** R
m <- matrix(1:9,3,3)
getColnames(m)
colnames(m) <- c("tic", "tac", "toe")
getColnames(m)
*/

输出

R> Rcpp::sourceCpp("~/git/stackoverflow/55850510/answer.cpp")

R> m <- matrix(1:9,3,3)

R> getColnames(m)
[1] "V0" "V1" "V2"

R> colnames(m) <- c("tic", "tac", "toe")

R> getColnames(m)
[1] "tic" "tac" "toe"
R>