【问题标题】:Can we get factor matrices in R?我们可以在 R 中获得因子矩阵吗?
【发布时间】:2015-04-27 16:26:49
【问题描述】:

似乎不可能在 R 中获得因子矩阵。是真的吗?如果是,为什么?如果没有,我该怎么办?

f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
m <- matrix(f,4,5)
is.factor(m) # fail.

m <- factor(m,letters[1:5])
is.factor(m) # oh, yes? 
is.matrix(m) # nope. fail. 

dim(f) <- c(4,5) # aha?
is.factor(f) # yes.. 
is.matrix(f) # yes!

# but then I get a strange behavior
cbind(f,f) # is not a factor anymore
head(f,2) # doesn't give the first 2 rows but the first 2 elements of f
# should I worry about it?

【问题讨论】:

  • 为什么需要因子矩阵?您是否考虑过使用data.frame(它支持因子更好)而不是矩阵?
  • @nicola 我确实考虑过,是的。但是data.frame 对我的需要来说实在是太多了。需要一个由可比较的同质元素组成的矩阵不是很自然吗?

标签: r matrix vector integer r-factor


【解决方案1】:

在这种情况下,它可能会像鸭子一样走路,甚至会像鸭子一样嘎嘎叫,但是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 作为矩阵,它很快就会失去使其成为一个因素的特征(您最终会使用级别或维度丢失)。

原子向量类型实际上只有矩阵和数组:

  1. 合乎逻辑,
  2. 整数,
  3. 真实的,
  4. 复杂,
  5. 字符串(或字符),和
  6. 原始

另外,正如@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&lt;-() 添加维度是一种无需复制对象即可创建数组的快速方法,但这绕过了 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" 类(这将删除级别和其他属性),因此您至少可以使用对象并查看需要在哪里添加新方法来填充类的行为。

【讨论】:

  • 很好的解释+1,但不同意“因子”矩阵不是矩阵。相反,OP 的失败是 S3 调度中对隐式类的不一致处理的产物。如果 R 将我们的因子矩阵视为 c("factor", "matrix", "integer") 类,那么大多数函数都可以正常工作。我认为不这样做是 R 中的一个(小)设计缺陷。
  • 尽管您关于属性丢弃函数(rbind 等)的观点仍然成立。
  • 列表也可以是矩阵
  • 我认为使用字符矩阵会更好,因为意外处理数字等值的可能性较小。由于全局字符串池,使用字符串而不是整数几乎没有内存开销。
  • @BrodieG 要存在因子矩阵之类的东西,S3 需要支持多重继承,但它不支持。 (并不是说“矩阵”在 S3 系统中定义得很好,主要是一个隐式类)
【解决方案2】:

不幸的是,因子支持在 R 中并不完全通用,因此许多 R 函数默认将因子视为其内部存储类型,即integer:

> typeof(factor(letters[1:3]))
[1] "integer  

matrix、cbind 就是这样。他们不知道如何处理因数,但他们确实知道如何处理整数,因此他们将您的因数视为整数。 head 实际上是相反的。它确实知道如何处理因子,但它从不费心检查您的因子是否也是矩阵,因此只需将其视为正常的无量纲因子向量。

如果你的矩阵有因子,你最好的选择是强制它成为字符。完成操作后,您可以将其恢复为因子形式。你也可以用整数形式来做这件事,但是你会冒着奇怪的风险(例如,你可以在整数矩阵上做矩阵乘法,但这对因子没有意义)。

请注意,如果您将“矩阵”类添加到您的因子中,一些(但不是全部)事情就会开始工作:

f <- factor(letters[1:9])
dim(f) <- c(3, 3)
class(f) <- c("factor", "matrix")
head(f, 2)

生产:

     [,1] [,2] [,3]
[1,] a    d    g   
[2,] b    e    h   
Levels: a b c d e f g h i

这不能解决rbind等问题。

【讨论】:

  • 那么让我们使用整数矩阵吧,因为当我只需要 5 或 10 个级别时,我觉得处理字符很重......并且尽量不要进行虚拟操作用我的矩阵;)
  • @lago-lito,根据 Hadley 的评论,字符实际上根本不会占用太多空间,因为实际的字符串只在内存中存储一​​次。另外,请注意我的答案的更新
  • 哦,真的吗?我应该在哪里进一步了解矩阵在内存中的存储方式?我必须承认,将整数矩阵与字符串矩阵一样轻的想法确实感觉很奇怪..
  • @lago-lito,有关字符向量的成本,请参阅?factor 中的注释。矩阵只是存储为具有dim 属性的向量,因此唯一真正重要的是底层向量的存储方式。
  • 太好了,谢谢!我还刚刚找到了函数utils::object.size,它可以帮助我测量我的矩阵的实际重量,`很有趣;)
猜你喜欢
  • 2010-10-02
  • 2015-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-01
  • 2013-11-23
  • 1970-01-01
  • 2013-01-01
相关资源
最近更新 更多