首页 > 解决方案 > 在矩阵中使用 vctrs

问题描述

我正在试验这个vctrs包。我的实际用例在相关方面类似于主页上rational有用的S3 向量文章中实现的类vctrs,因为它rcrd用于配对数据。为了清楚起见,我将使用它作为我的代表。(编辑:但是,我对理性并不特别感兴趣。)让我先粘贴相关部分:

library(vctrs)
library(zeallot)

new_rational <- function(n = integer(), d = integer()) {
  vec_assert(n, ptype = integer())
  vec_assert(d, ptype = integer())

  new_rcrd(list(n = n, d = d), class = "vctrs_rational")
}

rational <- function(n, d) {
  c(n, d) %<-% vec_cast_common(n, d, .to = integer())
  c(n, d) %<-% vec_recycle_common(n, d)

  new_rational(n, d)
}

format.vctrs_rational <- function(x, ...) {
  n <- field(x, "n")
  d <- field(x, "d")

  out <- paste0(n, "/", d)
  out[is.na(n) | is.na(d)] <- NA

  out
}

vec_ptype_abbr.vctrs_rational <- function(x, ...) "rtnl"
vec_ptype_full.vctrs_rational <- function(x, ...) "rational"

一个使用这个的例子:

(x <- rational(1, 1:15))
#> <rational[15]>
#>  [1] 1/1  1/2  1/3  1/4  1/5  1/6  1/7  1/8  1/9  1/10 1/11 1/12 1/13 1/14 1/15

尝试在 a 中使用这样的类时出现了我的问题matrix

matrix(x, ncol = 5, nrow = 3)
#> Warning in matrix(x, ncol = 5, nrow = 3): data length [2] is not a sub-multiple
#> or multiple of the number of rows [3]
#>      [,1]       [,2]       [,3]       [,4]       [,5]      
#> [1,] Integer,15 Integer,15 Integer,15 Integer,15 Integer,15
#> [2,] Integer,15 Integer,15 Integer,15 Integer,15 Integer,15
#> [3,] Integer,15 Integer,15 Integer,15 Integer,15 Integer,15

reprex 包(v0.3.0)于 2020 年 6 月 5 日创建

我希望得到一个 3×5 矩阵,其中每个单元格包含一个来自 的值,如果是“正常”向量x就会发生这种情况。x相反,我得到了一个 3×5 的列表矩阵,其中vctrs尝试使交替的行分别包含nd值。

因此,我的问题是,对于这种情况,是否有可能以vctrs“预期”的方式使用矩阵,如果可以,如何?通过实验,我感觉到这可能与实现dim.rationaland有关`dim<-.rational`,但我无法让它发挥作用。

编辑:如果所需的矩阵不清楚(如评论中所建议的那样),我想要一个类似于以下内容的矩阵对象,我已经手动编辑了它:

(m <- matrix(x, ncol = 5, nrow = 3))
#> <rational[15]>
#>      [,1] [,2] [,3] [,4]  [,5]   
#> [1,] 1/1  1/4  1/7  1/10  1/13
#> [2,] 1/2  1/5  1/8  1/11  1/14
#> [3,] 1/3  1/6  1/9  1/12  1/15

这样正常的矩阵运算将起作用m,例如

m[1,]
#> <rational[5]>
#> 1/1  1/4  1/7  1/10  1/13

标签: rmatrixvctrs

解决方案


该类的整个设计rational似乎建立在保留其类型安全性和对用户隐藏实现的基础上,我可以看到这对于让它始终如一地工作是必要的,但这意味着你不能指望它与 R 的默认设置很好地配合S3 方法。

帮助文件vctrs专门说

  • dims()、dims<-、dimnames()、dimnames<-、levels() 和 levels<- 方法会引发错误。

这表明 的作者vctrs并不认为它是构建矩阵方法的一个很好的基础。

在任何情况下,我都不会急于尝试将其放入矩阵中,因为一旦它存在,您将无法对其进行任何操作:您没有可用的算术方法:

x + 2
#> Error: <rational> + <double> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.
x * 2
#> Error: <rational> * <double> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.
x + x
#> Error: <rational> + <rational> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.

所以你需要先定义算术方法。在你这样做之前,你需要$一个用于分子和分母的访问器,一个is.rational在尝试算术之前检查类型的函数,一个找到最大公分母的函数,以及一个基于它简化你的有理数的函数。

`$.vctrs_rational` <- function(vec, symb) unclass(vec)[[as.character(symb)]]

is.rational <- function(num) class(num)[1] == "vctrs_rational"

gcd <- function(x, y) ifelse(x %% y, gcd(y, x %% y), y)

simplify <- function(num) {
  common <- gcd(num$n, num$d)
  rational(num$n / common, num$d/common)
}

所以现在你可以做

x$n
#>  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
x$d
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
is.rational(x)
#> [1] TRUE

现在编写算术函数。例如,这是一个涵盖数字和有理类型的基本算术实现:

Ops.vctrs_rational <- function(vec, num)
{
  if(!is.rational(vec)) {tmp <- vec; vec <- num; num <- tmp; }
  if(.Generic == '*'){
    if(is.rational(num)) return(simplify(rational(vec$n * num$n, vec$d * num$d)))
    else return(simplify(rational(vec$n * 2, vec$d)))
  }

  else if (.Generic == '/'){
    if(is.rational(num)) return(vec * rational(num$d, num$n))
    else return(vec * rational(1, num))
  }

  else if (.Generic == '+'){
    if(is.rational(num)){ 
      new_n <- vec$n * (vec$d * num$d)/vec$d + num$n * (vec$d * num$d)/num$d
      return(simplify(rational(new_n, vec$d * num$d)))
    }
    else return(simplify(rational(num * vec$d + vec$n, vec$d)))
  }

  else if (.Generic == '-'){
    if(is.rational(num)) return(vec + rational(-num$n, num$d)) 
    else return(vec + (-num)) 
  }

  else if (.Generic == '^'){
    if(is.rational(num) | num < 0) stop("fractional and negative powers not supported")
    return(simplify(rational(vec$n ^ num, vec$d ^ num)))
  }
}

这现在允许您执行以下操作,例如:

x * 3
#> <rational[15]>
#>  [1] 3/1  3/2  1/1  3/4  3/5  1/2  3/7  3/8  1/3  3/10 3/11 1/4  3/13 3/14 1/5

x + x
#> <rational[15]>
#> [1] 2/1  1/1  2/3  1/2  2/5  1/3  2/7  1/4  2/9  1/5  2/11 1/6  2/13 1/7  2/15

(2 + x)^2 / (3 * x + 1)
#> <rational[15]>
#>  [1] 3/1     25/8    49/15   27/8    121/35  169/48  25/7    289/80 
#>  [9] 361/99  147/40  529/143 625/168 243/65  841/224 961/255

尝试matrix()直接使用它自己可能是行不通的,因为matrix通过转换为基向量然后调用 C 代码来工作。这去除了类信息。

这意味着您需要定义一个单独的rational_matrix类,这反过来又会从支持rational_vector类中受益。然后我们可以定义具体的格式和打印方法:

as.vector.vctrs_rational <- function(x, ...) {
  n <- x$n/x$d
  attr(n, "denom") <- x$d
  attr(n, "numerator") <- x$n
  class(n) <- "rational_attr"
  n
}

rational_matrix <- function(data, nrow = 1, ncol = 1, 
                            byrow = FALSE, dimnames = NULL){
  d <- as.vector(data)
  m <- .Internal(matrix(d, nrow, ncol, byrow, dimnames, missing(nrow), 
                        missing(ncol)))
  m_dim <- dim(m)
  attributes(m) <- attributes(d)
  dim(m) <- rev(m_dim)
  class(m) <- c("rational_matrix", "matrix")
  m
}

format.rational_matrix <- function(x) {
 return(paste0(attr(x, "numerator"), "/", attr(x, "denom")))
}

print.rational_matrix <- function(x)
{
  print(matrix(format(x), nrow = dim(x)[2]), quote = FALSE)
}

最后,您需要覆盖matrix()以使其成为 S3 方法,确保首先将函数复制为matrix.default

matrix.default <- matrix
matrix <- function(data = NA, ...) UseMethod("matrix")
matrix.vctrs_rational <- function(data, ...) rational_matrix(data, ...)

所以现在你可以这样做:

matrix(x, nrow = 5)
#>      [,1] [,2] [,3] [,4] [,5]
#> [1,] 1/1  1/4  1/7  1/10 1/13
#> [2,] 1/2  1/5  1/8  1/11 1/14
#> [3,] 1/3  1/6  1/9  1/12 1/15

rational_matrix(x + 5, nrow = 3)
#>      [,1] [,2] [,3] [,4]  [,5] 
#> [1,] 6/1  21/4 36/7 51/10 66/13
#> [2,] 11/2 26/5 41/8 56/11 71/14
#> [3,] 16/3 31/6 46/9 61/12 76/15

rational_matrix(x + x, nrow = 5)
#>      [,1] [,2] [,3]
#> [1,] 2/1  1/3  2/11
#> [2,] 1/1  2/7  1/6 
#> [3,] 2/3  1/4  2/13
#> [4,] 1/2  2/9  1/7 
#> [5,] 2/5  1/5  2/15

然而,为了让它工作,无论如何我们都必须添加带有属性的额外类,所以我的感觉是,如果你想要一个与矩阵等一起工作的理性类,你应该在本机 S3 或其他面向对象方法之一中进行在 R 中可用,而不是使用vctrs包。

值得一提的是,上面的类还远未准备好生产,因为您需要添加测试相等/不等式的方法、描述矩阵运算的方法、转换为十进制的能力、绘图方法等。


推荐阅读