历史遗留:R matrix
不是 array
在最近对 R-devel
邮件列表的讨论中,在 7 月 8 日开始的一个主题中,head.matrix 可以返回 1000 列 - 限制为 n 或添加新参数?迈克尔·奇里科和加布·贝克尔提出将 head()
和 tail()
实用程序函数泛化,加布指出当前(R-4.x.y 之前的)head()
不会特殊处理 array
。我回复道,指出R 目前通常需要 matrix
和 array
方法
但请注意以下历史怪癖
sapply(setNames(,1:5),
function(K) inherits(array(7, dim=1:K), "array"))
((我希望这会改变,我明确放入了当前 R 3.x.y 结果,而不是评估上述 R 块:))
1 2 3 4 5
TRUE FALSE TRUE TRUE TRUE
请注意,matrix
对象在(继承)意义上不是 array
,尽管 - 许多用户可能不知道 -
identical(
matrix(47, 2,3), # NB " n, n+1 " is slightly special
array (47, 2:3))
## [1] TRUE
所有矩阵都可以通过 array(.)
等效地构造,尽管在 matrix(*, byrow=TRUE)
的情况下略显笨拙。
请注意,正因为如此,基本 R 本身有三个函数,其中 matrix
和 array
方法是相同的,正如我在帖子中所写:因此,目前,“通常”foo.matrix
只是 foo.array
的副本,如果后者存在,其中 foo 的base
示例为 {unique, duplicated, anyDuplicated}。
for(e in expression(unique, duplicated, anyDuplicated)) { # `e` is a `symbol`
f.m <- get(paste(e, "matrix", sep="."))
f.a <- get(paste(e, "array", sep="."))
stopifnot(is.function(f.m),
identical(f.m, f.a))
}
在 R 4.0.0 中,matrix()
会是 "array"
吗?
在同一篇文章中,我还问过
这是我们应该考虑在 R 4.0.0 中更改的内容 - 让它对 2d 数组也为 TRUE,即矩阵对象?
与此同时,我试探性地回答了我自己的问题“是”,并开始调查一些后果。从我发现的内容来看,在过于热心的(单元)测试中,甚至是我自己编写的,我被提醒我想教更多的人了解一个潜在的相关问题,在这个问题中,我们看到许多不安全的使用者不安全地使用 R
如果您认为 class(.) == *
,请三思: 相反,inherits(., *)
…. 或 is(., *)
大多数非初学者 R 用户都了解类之间的继承,更普遍地说,R 对象在概念上不止一种“类型”。例如,pi
既是 "numeric"
又是 "double"
,或者 1:2
既是 integer
又是 numeric
。他们可能知道时间日期对象有两种形式:?DateTimeClasses
(或 ?POSIXt
) 帮助页面描述了 POSIXct
和 POSIXlt
,并说
"POSIXct"
更方便地包含在数据框中,而"POSIXlt"
更接近于人类可读形式。存在一个虚拟类"POSIXt"
,两个类都继承自该类...
例如
class(tm <- Sys.time())
## [1] "POSIXct" "POSIXt"
显示 class(.)
在此处的长度为 2,这会中断 if(class(x) == "....") ..
调用。
正式类:S4
R 的正式类系统称为S4
(主要在标准 R 软件包 methods
中实现),提供功能和工具来实现丰富的类继承结构,在 软件包 Matrix
或 Bioconductor 项目 中得到大量使用,该项目有 1800 多个 R “软件”软件包。Bioconductor 甚至建立在提供大量使用的 S4 类的核心软件包之上,例如 Biostrings、S4Vectors、XVector、IRanges 和 GenomicRanges。另请参见 常见的 Bioconductor 方法和类。
在正式的 S4 类系统中,扩展和继承非常重要且经常被广泛使用,因此这样的表达式
if (class(obj) == "matrix") { ..... } # *bad* - do not copy !
特别无用,因为 obj
很可能是扩展矩阵的类,而使用 S4 的程序员很早就学会了使用
if (is(obj, "matrix")) { ..... } # *good* !!!
请注意,Bioconductor 软件包开发人员指南警告不要滥用 class(.) == *
,请参见 R 代码和最佳实践 一节
非正式“经典”类:S3
R 被创建为 S 的方言或实现,请参见 维基百科的 R 历史,对于 S,“白皮书”(Chambers & Hastie,1992)引入了一种方便且相对简单的面向对象 (OO),后来被称为S3
,因为白皮书引入了S 版本 3(蓝皮书描述了S 版本 2,绿皮书描述了S 版本 4,即 S4
)。
白皮书还介绍了公式、数据框等,在某些情况下还介绍了某些 S 对象可以是给定类的特定情况,并且从这个意义上扩展该类。例如,在 R 中,多元时间序列 ("mts"
) 扩展了(简单)时间序列 ("ts"
),或者多元或广义线性模型 ("mlm"
或 "glm"
) 扩展了正态线性模型 "lm"
。
“解决方法”:class(.)[1]
因此,一些经验更丰富、更谨慎的程序员已在这样的比较中用 class(x)[1]
(或 class(x)[1L]
)替换 class(x)
,例如,在 2018 年广受赞誉的 useR!演讲中。
在某些情况下,这已经足够好,这也是 R 的 data.class(.)
函数(除其他函数外)或(用户隐藏的)methods:::.class1(.)
所做的。
但是,程序员应意识到,这只是一个解决方法,并且在使用典型的 S3 继承的情况下会导致其工作不正确:在某些情况下,对结果为 "fitme"
类的函数 fitme()
进行轻微修改或扩展非常自然,通常通过编写 fitmeMore()
(例如)来实现,其值将为 c("fMore", "fitme")
类,以便几乎所有“fitme”方法都将继续工作,但是 fitmeMore()
的作者将另外提供一个 print()
方法,即提供方法函数 print.fMore()
。
但是,如果其他用户使用 class(.)[1]
并为 class(.)[1] == "fitme"
的情况提供了代码,则该代码将错误地不适用于新的 "fMore"
对象。
唯一正确的解决方案是使用 inherits(., "fitme")
,因为它适用于所有它应该适用的对象。
在很大程度上依赖 CRAN 包中,以下行(略有混淆)应有效确定特定类的列表条目
isC <- vapply(args, class, "") == "__my_class__"
发现(并通知包维护者)需要更正为
isC <- vapply(args, inherits, TRUE, what = "__my_class__")
摘要
您应该使用 inherits(x, "foo")
而不是 class(x) == "foo"
或者可能也可以使用 is(x, "foo")
推论
switch(class(x)[1],
"class_1" = { ..... },
"class_2" = { ..... },
.......,
.......,
"class_10" = { ..... },
stop(" ... invalid class:", class(x)))
可能看起来很简洁,但几乎总是不够好,因为它是(通常)错误的,例如,当 class(x)
为 c("class_7", "class_2")
时。
参考
R 核心团队 (2019)。R 帮助页面
对于 S3,
class
或inherits
对于 S4,例如,S4 方法和类的基本用法,以及
is
。
Becker, R. A., Chambers, J. M. 和 Wilks, A. R. (1988) The New S Language(蓝皮书,介绍 S 版本 2 (
S2
));Wadsworth & Brooks/Cole。Chambers, J. M. 和 Hastie, T. J. 编辑 (1992) Statistical Models in S(白皮书,介绍 S 版本 3 (
S3
);Chapman & Hall,伦敦)。钱伯斯,约翰·M.(1998)使用数据编程(绿皮书,
S4
原版);施普林格。钱伯斯,约翰·M.(2008)数据分析软件:使用 R 编程(
S4
等 R);施普林格。