在矩阵中使用 vctrs
Using vctrs in matrices
我正在试验 vctrs
包。我的实际用例在相关方面类似于 vctrs
主页上有用的 S3 vectors 文章中实施的 rational
class,因为它使用 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
我的问题出现在尝试在 matrix
中使用这样的 class 时:
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 package (v0.3.0)
于 2020 年 6 月 5 日创建
我希望得到一个 3×5 矩阵,每个单元格包含一个来自 x
的值,如果 x
是一个 "normal" 向量就会发生这种情况。相反,我得到一个 3×5 列表矩阵,其中 vctrs
尝试使交替行分别包含 n
和 d
值。
因此,我的问题是 是否有可能让 vctrs
在这种情况下以 "expected" 的方式处理矩阵,如果可以,怎么做? 通过试验,我觉得这可能与实施 dim.rational
和 `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
rational
class 的整个设计似乎建立在保持其类型安全和对用户隐藏实现的基础上,我认为这是使其始终如一地工作所必需的,但是意味着您不能指望它能很好地与 R 的默认 S3 方法配合使用。
vctrs
的帮助文件具体说
- dims(), dims<-, dimnames(), dimnames<-, levels(), and levels<- methods throw errors.
这表明 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 代码来工作。这会删除 class 信息。
这意味着您需要定义一个单独的 rational_matrix
class,这反过来又会受益于支持 rational_vector
class。然后我们可以定义具体的格式和打印方式:
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
然而,为了让它工作,我们不得不添加额外的 classes 属性,所以我的感觉是,如果你想要一个与矩阵等一起工作的有理 class,你应该在本机 S3 或 R 中可用的其他面向对象方法之一中执行此操作,而不是使用 vctrs
包。
同样值得一提的是,上面的 class 远未准备好生产,因为您需要添加测试相等/不等的方法、描述矩阵运算的方法、转换为的能力小数点、绘图方法等
我正在试验 vctrs
包。我的实际用例在相关方面类似于 vctrs
主页上有用的 S3 vectors 文章中实施的 rational
class,因为它使用 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
我的问题出现在尝试在 matrix
中使用这样的 class 时:
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 package (v0.3.0)
于 2020 年 6 月 5 日创建我希望得到一个 3×5 矩阵,每个单元格包含一个来自 x
的值,如果 x
是一个 "normal" 向量就会发生这种情况。相反,我得到一个 3×5 列表矩阵,其中 vctrs
尝试使交替行分别包含 n
和 d
值。
因此,我的问题是 是否有可能让 vctrs
在这种情况下以 "expected" 的方式处理矩阵,如果可以,怎么做? 通过试验,我觉得这可能与实施 dim.rational
和 `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
rational
class 的整个设计似乎建立在保持其类型安全和对用户隐藏实现的基础上,我认为这是使其始终如一地工作所必需的,但是意味着您不能指望它能很好地与 R 的默认 S3 方法配合使用。
vctrs
的帮助文件具体说
- dims(), dims<-, dimnames(), dimnames<-, levels(), and levels<- methods throw errors.
这表明 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 代码来工作。这会删除 class 信息。
这意味着您需要定义一个单独的 rational_matrix
class,这反过来又会受益于支持 rational_vector
class。然后我们可以定义具体的格式和打印方式:
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
然而,为了让它工作,我们不得不添加额外的 classes 属性,所以我的感觉是,如果你想要一个与矩阵等一起工作的有理 class,你应该在本机 S3 或 R 中可用的其他面向对象方法之一中执行此操作,而不是使用 vctrs
包。
同样值得一提的是,上面的 class 远未准备好生产,因为您需要添加测试相等/不等的方法、描述矩阵运算的方法、转换为的能力小数点、绘图方法等