在这种情况下,它可能会像鸭子一样走路,甚至会像鸭子一样嘎嘎叫,但是f来自:
f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
dim(f) <- c(4,5)
确实不是一个矩阵,尽管is.matrix() 声称它严格来说是一个。就is.matrix()而言,要成为矩阵,f只需要是一个向量并具有dim属性即可。通过将属性添加到f,您通过了测试。然而,正如您所见,一旦您开始使用 f 作为矩阵,它很快就会失去使其成为一个因素的特征(您最终会使用级别或维度丢失)。
原子向量类型实际上只有矩阵和数组:
- 合乎逻辑,
- 整数,
- 真实的,
- 复杂,
- 字符串(或字符),和
- 原始
另外,正如@hadley 提醒我的那样,您还可以拥有列表矩阵和数组(通过在列表对象上设置 dim 属性。例如,参见 Hadley 的书的 Matrices & Arrays 部分,Advanced R。)
这些类型之外的任何内容都将通过as.vector() 强制转换为一些较低的类型。这发生在matrix(f, nrow = 3) 不是因为f 是原子的根据 is.atomic()(它为f 返回TRUE,因为它在内部存储为整数,typeof(f) 返回@987654338 @),但因为它有一个class 属性。这会在f 的内部表示上设置OBJECT 位,并且任何具有类的东西都应该通过as.vector() 强制转换为一种原子类型:
matrix <- function(data = NA, nrow = 1, ncol = 1, byrow = FALSE,
dimnames = NULL) {
if (is.object(data) || !is.atomic(data))
data <- as.vector(data)
....
通过dim<-() 添加维度是一种无需复制对象即可创建数组的快速方法,但这绕过了 R 在通过其他方法将f 强制转换为矩阵时会执行的一些检查和平衡
matrix(f, nrow = 3) # or
as.matrix(f)
当您尝试使用适用于矩阵的基本函数或使用方法分派时,就会发现这一点。请注意,在将维度分配给f 之后,f 仍然属于 "factor" 类:
> class(f)
[1] "factor"
这解释了head() 的行为;您没有得到 head.matrix 行为,因为 f 不是矩阵,至少就 S3 机制而言:
> debug(head.matrix)
> head(f) # we don't enter the debugger
[1] d c a d b d
Levels: a b c d e
> undebug(head.matrix)
而head.default 方法调用[ 有一个factor 方法,因此观察到的行为:
> debugonce(`[.factor`)
> head(f)
debugging in: `[.factor`(x, seq_len(n))
debug: {
y <- NextMethod("[")
attr(y, "contrasts") <- attr(x, "contrasts")
attr(y, "levels") <- attr(x, "levels")
class(y) <- oldClass(x)
lev <- levels(x)
if (drop)
factor(y, exclude = if (anyNA(levels(x)))
NULL
else NA)
else y
}
....
cbind() 行为可以从记录的行为中解释(来自?cbind,强调我的):
函数cbind 和rbind 是S3 通用,...
....
在默认方法中,所有向量/矩阵必须是原子的
(见vector)或列表。不允许表达。语言
对象(例如公式和调用)和配对列表将被强制
到列表:其他对象(例如名称和外部指针)将
作为元素包含在列表结果中。 输入的任何类
可能已经被丢弃(特别是,因子被替换为
他们的内部代码)。
同样,f 属于 "factor" 类这一事实让您失望,因为默认的 cbind 方法将被调用,它会剥离级别信息并返回您观察到的内部整数代码。
在许多方面,您必须忽略或至少不完全相信 is.foo 函数告诉您的内容,因为它们只是使用简单的测试来判断某物是否是 foo 对象。 is.matrix() 和 is.atomic() 显然是错误的 f(带有尺寸)从特定的角度来看。他们在实现方面也是正确的,或者至少可以从实现中理解他们的行为;我认为is.atomic(f) 是不正确的,但是如果 "if is an atomic type" R Core 的意思是“类型”是typeof(f) 返回的东西,那么is.atomic() 是正确的。更严格的测试是is.vector(),其中f 失败:
> is.vector(f)
[1] FALSE
因为它具有超出names 属性的属性:
> attributes(f)
$levels
[1] "a" "b" "c" "d" "e"
$class
[1] "factor"
$dim
[1] 4 5
至于你应该如何得到一个因子矩阵,你不能,至少如果你希望它保留因子信息(级别的标签)。一种解决方案是使用字符矩阵,它会保留标签:
> fl <- levels(f)
> fm <- matrix(f, ncol = 5)
> fm
[,1] [,2] [,3] [,4] [,5]
[1,] "c" "a" "a" "c" "b"
[2,] "d" "b" "d" "b" "a"
[3,] "e" "e" "e" "c" "e"
[4,] "a" "b" "b" "a" "e"
我们存储f 的级别以备将来使用,以防我们在此过程中丢失矩阵的一些元素。
或者使用内部整数表示:
> (fm2 <- matrix(unclass(f), ncol = 5))
[,1] [,2] [,3] [,4] [,5]
[1,] 3 1 1 3 2
[2,] 4 2 4 2 1
[3,] 5 5 5 3 5
[4,] 1 2 2 1 5
您可以随时通过以下方式再次回到关卡/标签:
> fm2[] <- fl[fm2]
> fm2
[,1] [,2] [,3] [,4] [,5]
[1,] "c" "a" "a" "c" "b"
[2,] "d" "b" "d" "b" "a"
[3,] "e" "e" "e" "c" "e"
[4,] "a" "b" "b" "a" "e"
使用数据框似乎并不理想,因为数据框的每个组件都将被视为一个单独的因素,而您似乎希望将数组视为具有一组级别的单个因素。
如果你真的想做你想做的事,那就是有一个因子矩阵,你很可能需要创建自己的 S3 类来执行此操作,以及与之配套的所有方法。例如,您可以将因子矩阵存储为字符矩阵,但使用 "factorMatrix" 类,您将级别存储在因子矩阵旁边作为额外属性说。然后你需要编写[.factorMatrix,它会获取级别,然后在矩阵上使用默认的[ 方法,然后再次添加级别属性。您也可以编写 cbind 和 head 方法。然而,所需方法的列表会很快增长,但一个简单的实现可能会适合,如果你让你的对象有类c("factorMatrix", "matrix")(即从"matrix"类继承),你会拿起所有的属性/方法"matrix" 类(这将删除级别和其他属性),因此您至少可以使用对象并查看需要在哪里添加新方法来填充类的行为。