在这种情况下,它可能像鸭子一样行走,甚至像鸭子一样嘎嘎叫,但是
f
来自:
f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
dim(f) <- c(4,5)
虽然is.matrix()
声称f
是矩阵,但实际上它并不是矩阵。在is.matrix()
的考虑中,只要f
是向量并且具有dim
属性,它就被视为矩阵。通过将属性添加到f
,您可以通过测试。但是,如您所见,一旦您开始将f
用作矩阵,它将很快失去使它成为因子的特征(您最终会使用级别或尺寸丢失)。
对于原子向量类型,实际上只有矩阵和数组:
- 逻辑型,
- 整数型,
- 实数,
- 复数,
- 字符串(或字符),
- 原始的
还有,正如@hadley提醒我的那样,您也可以有列表矩阵和数组(通过在列表对象上设置dim
属性)。例如,请参见Hadley的书籍Advanced R的Matrices & Arrays部分。
除了这些类型之外的任何内容都将通过as.vector()
强制转换为某种较低类型。在matrix(f, nrow = 3)
中发生这种情况,不是因为f
按照is.atomic()
的定义是原子的(它返回TRUE
,因为它在内部存储为整数,并且typeof(f)
返回"integer"
),而是因为它具有一个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)
as.matrix(f)
当您尝试使用在矩阵上工作的基本函数或使用方法分派时,会发现这一点。请注意,在将维度分配给f
之后,f
仍然是"factor"
类:
> class(f)
[1] "factor"
这个问题可以通过head()
函数来解释;你没有得到head.matrix
的结果是因为在S3机制中,f
不被视为矩阵:
> debug(head.matrix)
> head(f)
[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
),或者是列表。不允许使用表达式。语言对象(例如公式和调用)和pairlist将被强制转换为列表:其他对象(例如名称和外部指针)将包含在列表结果的元素中。输入可能具有的任何类都将被丢弃(特别是因子将被替换为其内部代码)。
再次说明,
f
是
"factor"
类会使您失败,因为默认的
cbind
方法将被调用,并且它将剥离级别信息并返回内部整数代码,就像您观察到的那样。
在许多方面,您必须忽略或至少不完全信任
is.foo
函数告诉您的内容,因为它们只是使用简单的测试来判断某个东西是否是
foo
对象。当涉及到
f
(带有维度)时,
is.matrix()
和
is.atomic()
在某种程度上显然是错误的。从特定的角度来看,它们也是正确的。从实现的角度来看,它们的行为可以被理解;我认为
is.atomic(f)
不正确,但是如果R Core通过
"if is of an atomic type"来指代"类型"是由
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"
类的所有属性/方法(这将删除水平和其他属性),因此您至少可以使用对象并查看您需要添加新方法以填充类行为的位置。
data.frame
(支持因子更好)而不是矩阵? - nicoladata.frame
对于我所需的东西来说太多了。需要一个由可比较、同质元素组成的矩阵不是很自然吗? - iago-lito