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

How to get column names even if it is NULL in 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);
}

有没有更优雅的方式?

几点注意事项:

  1. 矩阵并不总是设置 colnames()rownames()
    • 如果设置了一个,则对象具有dimnames的属性。
  2. 可以通过 C API 为 R 检查值是否存在。
    • 例如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 return 类型。

#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"

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

我开始这个然后分心了。 @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/Whosebug/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>