如何使用核心R操作/访问“dist”类实例的元素?

28
R中一个基本/常见的类被称为"dist",它是对称距离矩阵的相对高效表示。然而,与"matrix"对象不同,似乎没有支持使用"["运算符通过索引对操作"dist"实例的功能。
例如,以下代码返回空值、NULL或错误:
# First, create an example dist object from a matrix
mat1  <- matrix(1:100, 10, 10)
rownames(mat1) <- 1:10
colnames(mat1) <- 1:10
dist1 <- as.dist(mat1)
# Now try to access index features, or index values
names(dist1)
rownames(dist1)
row.names(dist1)
colnames(dist1)
col.names(dist1)
dist1[1, 2]

同时,以下命令在某种程度上确实有效,但并不能更轻松地访问/操作特定的索引对数值:

dist1[1] # R thinks of it as a vector, not a matrix?
attributes(dist1)
attributes(dist1)$Diag <- FALSE
mat2 <- as(dist1, "matrix")
mat2[1, 2] <- 0

一个解决方法——我想要避免——是先将"dist"对象转换为"matrix",对该矩阵进行操作,然后再将其转换回"dist"。也就是说,这不是有关如何转换"dist"实例为已定义常见矩阵索引工具的"matrix"或其他类的问题;因为这在另一个SO问题中已经得到了多种回答。
stats包(或其他核心R包)中是否有专门用于索引/访问"dist"实例元素的工具?

1
好问题。我没有答案,但请注意,在R中,矩阵只是一个带有维度的向量。因此,dist1[1:20]dist1[5] <- 100等操作正常工作并不令人惊讶。经过一些努力,您可能可以编写一个二维版本,尽管我的原子知识有限。 - Ari B. Friedman
13个回答

10

很不幸地,目前没有标准的方法来实现这个功能。下面有两个函数将一维的索引转换为二维矩阵坐标。它们不是很简洁,但是它们能够工作,并且至少你可以使用这些代码来创建更好的实现。我之所以发布这篇文章,只是因为这些公式并不显然。

distdex<-function(i,j,n) #given row, column, and n, return index
    n*(i-1) - i*(i-1)/2 + j-i

rowcol<-function(ix,n) { #given index, return row and column
    nr=ceiling(n-(1+sqrt(1+4*(n^2-n-2*ix)))/2)
    nc=n-(2*n-nr+1)*nr/2+ix+nr
    cbind(nr,nc)
}
一个小的测试工具来展示它的工作原理:
dist(rnorm(20))->testd
as.matrix(testd)[7,13]   #row<col
distdex(7,13,20) # =105
testd[105]   #same as above

testd[c(42,119)]
rowcol(c(42,119),20)  # = (3,8) and (8,15)
as.matrix(testd)[3,8]
as.matrix(testd)[8,15]

这是一个非常有用的答案,但鉴于预期的应用,它需要一些澄清。只有当i < j时才有效。对于i = j和i > j,它会返回错误的答案。将distdex函数修改为在i == j时返回0,并在i > j时转置i和j可以解决问题。我把代码放在下面的回答中,这样其他人就可以复制和粘贴了。明确地说,这仅涉及到距离矩阵的非标准查询问题,因此不是对Christian A答案的指责,而只是澄清。 - csfowler
以下是一份更好的实现方式:R - 如何从距离矩阵中获取匹配元素的行列下标 - Zheyuan Li

8

as.matrix(d)会将dist对象d转换为矩阵,而as.dist(m)会将矩阵m转换回dist对象。请注意,后者实际上并不检查m是否是有效的距离矩阵;它只提取下三角部分。


这个答案已经在我的问题结尾提到了(最后一段和示例代码中的as(dist1, "matrix"))。我想知道是否有一个“原地”解决方案——与此解决方法分开——由R中的“dist”类支持。感谢您对as.dist未检查dist实例有效性的评论。 - Paul 'Joey' McMurdie
我没有看出区别。矩阵式索引的“原地”解决方案就是在dist对象上使用as.matrixas.matrix通用函数调用stats::: as.matrix.dist,这是dist类的方法。 - Hong Ooi
我试图澄清问题的结尾。我认为混淆源于“原地”这个短语的歧义性,我想这可能指的是(1)已经在R中存在的任何工具,包括某些原因不理想的方法,比如我想避免的类翻转;或者(2)专门用于此目的和类别的工具,不会改变我的对象的类别来完成任务,即使是暂时的。 - Paul 'Joey' McMurdie

7
我无法直接回答你的问题,但如果你正在使用欧几里得距离,请查看fields包中的rdist函数。它的实现(用Fortran编写)比dist更快,并且输出是matrix类。至少可以证明一些开发人员已经选择远离这个dist类,可能正是你提到的原因。如果你担心使用完整的matrix来存储对称矩阵会浪费内存,你可以将其转换为三角形矩阵。
library("fields")
points <- matrix(runif(1000*100), nrow=1000, ncol=100)

system.time(dist1 <- dist(points))
#    user  system elapsed 
#   7.277   0.000   7.338 

system.time(dist2 <- rdist(points))
#   user  system elapsed 
#  2.756   0.060   2.851 

class(dist2)
# [1] "matrix"
dim(dist2)
# [1] 1000 1000
dist2[1:3, 1:3]
#              [,1]         [,2]         [,3]
# [1,] 0.0000000001 3.9529674733 3.8051198575
# [2,] 3.9529674733 0.0000000001 3.6552146293
# [3,] 3.8051198575 3.6552146293 0.0000000001

谢谢!这是有用的知识。而且,了解到 R 中基本的“dist”处理工具相当简陋也是很有帮助的。 - Paul 'Joey' McMurdie

4

您可以使用str()访问任何对象的属性。

对于我的一些数据的“dist”对象(dist1),它看起来像这样:

> str(dist1)
Class 'dist'  atomic [1:4560] 7.3 7.43 7.97 7.74 7.55 ...
  ..- attr(*, "Size")= int 96
  ..- attr(*, "Labels")= chr [1:96] "1" "2" "3" "4" ...
  ..- attr(*, "Diag")= logi FALSE
  ..- attr(*, "Upper")= logi FALSE
  ..- attr(*, "method")= chr "euclidean"
  ..- attr(*, "call")= language dist(x = dist1) 

您可以看到,在这个特定的数据集中,“标签”属性是一个长度为96的字符字符串,其字符是从1到96的数字。
您可以直接更改该字符字符串,方法如下:
> attr(dist1,"Labels") <- your.labels

"

“your.labels” 应该是一些 ID 或因子向量,可能来自创建“dist”对象的原始数据。

"

1
这个回答只是对Christian A之前的回答的扩展。它是必要的,因为一些问题的读者(包括我自己)可能会像对待对称对象一样查询dist对象(不仅仅是下面的(7,13),还有(13,7))。我没有编辑权限,早先的答案在用户将dist对象视为dist对象而不是稀疏矩阵时是正确的,这就是为什么我有一个单独的响应而不是编辑。如果这个答案有用,请给Christian A点赞。
distdex<-function(i,j,n) #given row, column, and n, return index
    n*(i-1) - i*(i-1)/2 + j-i

rowcol<-function(ix,n) { #given index, return row and column
    nr=ceiling(n-(1+sqrt(1+4*(n^2-n-2*ix)))/2)
    nc=n-(2*n-nr+1)*nr/2+ix+nr
    cbind(nr,nc)
}
#A little test harness to show it works:

dist(rnorm(20))->testd
as.matrix(testd)[7,13]   #row<col
distdex(7,13,20) # =105
testd[105]   #same as above

但是...
distdex(13,7,20) # =156
testd[156]   #the wrong answer

Christian A的函数只在i < j时有效。当i = j或i > j时,它会返回错误的答案。将distdex函数修改为在i == j时返回0,并在i > j时转置i和j可以解决这个问题,因此:

distdex2<-function(i,j,n){ #given row, column, and n, return index
  if(i==j){0
  }else if(i > j){
    n*(j-1) - j*(j-1)/2 + i-j
  }else{
    n*(i-1) - i*(i-1)/2 + j-i  
  }
}

as.matrix(testd)[7,13]   #row<col
distdex2(7,13,20) # =105
testd[105]   #same as above
distdex2(13,7,20) # =105
testd[105]   #the same answer

这里有一个更好的实现方式:R - 如何从距离矩阵中获取匹配元素的行和列下标 - Zheyuan Li

1

您可能会发现这个有用[来自??分布]:

距离矩阵的下三角通过向量以列的形式存储,称为“do”。如果“n”是观察值的数量,即“ n < - attr(do,“Size”)”,则对于i < j <= n,(行)i和j之间的不相似性为“do [n *(i-1) - i *(i-1)/ 2 + j-i]”。向量的长度为n *(n-1)/ 2,即n ^ 2的顺序。


我的部分答案已经包含了那个公式很长时间了。在发布这个问题到SO之前,检查?dist文档是我做的第一件事情。 - Paul 'Joey' McMurdie

1
似乎dist对象与简单的向量对象处理方式基本相同。就我所见,它是带有属性的向量。因此,要获取值,可以这样做:
x = as.vector(distobject)

看到了吗?dist是一个公式,用于提取使用它们的索引计算特定对象对之间的距离。


这种强制转换已经在其他答案中进行了描述,并且在问题中被指出是我试图避免的。我更喜欢一些尝试使用矩阵样式“[”符号进行提取或赋值的代码作为有价值的答案。 - Paul 'Joey' McMurdie
as.vector 对我来说比 as.matrix 快大约 30 倍。 - Jeff Bezos

0
你可以这样做:
d <- function(distance, selection){
  eval(parse(text = paste("as.matrix(distance)[",
               selection, "]")))
}

`d<-` <- function(distance, selection, value){
  eval(parse(text = paste("as.matrix(distance)[",
               selection, "] <- value")))
  as.dist(distance)
}

这将允许您这样做:

 mat <- matrix(1:12, nrow=4)
 mat.d <- dist(mat)
 mat.d
        1   2   3
    2 1.7        
    3 3.5 1.7    
    4 5.2 3.5 1.7

 d(mat.d, "3, 2")
    [1] 1.7
 d(mat.d, "3, 2") <- 200
 mat.d
          1     2     3
    2   1.7            
    3   3.5 200.0      
    4   5.2   3.5   1.7

然而,对于对角线或上三角的任何更改都将被忽略。这可能是正确的做法,也可能不是。如果不是,您需要添加某种合理性检查或适当处理这些情况。还有可能有其他情况。

谢谢Tyler。这似乎仍然是一个类翻转(虽然聪明而有用),这也可能会消除您原始“dist”实例中一些潜在有用的属性,如$call。我很好奇你对我刚才下面发表的答案有何看法,其中包括一个工作访问器不修改类,以及一个我还没有解决的非工作替换函数。 - Paul 'Joey' McMurdie
@Paul,你的解决方案看起来不错,尽管由于某些原因它返回了错误的对角线值。我不知道为什么替换函数不起作用。 - Tyler

0

stats包中似乎没有这样的工具。感谢@flodel在非核心包中提供了另一种实现。

我深入研究了核心R源代码中"dist"类的定义,它是老派的S3,dist.R源文件中没有像我在这个问题中所问的那样的工具。

dist()函数的文档确实指出了有用的信息,即(我引用):

距离矩阵的下三角按列存储在向量do中。如果n是观测值的数量,即n <- attr(do, "Size"),则对于i < j ≤ n,(行)ij之间的不相似度为:

do[n*(i-1) - i*(i-1)/2 + j-i]

向量的长度为n*(n-1)/2,即n^2的阶数。

(引用结束)

我在下面的示例代码中利用了这一点,为定义自己的"dist"访问器。请注意,此示例每次只能返回一个值。
################################################################################
# Define dist accessor
################################################################################
setOldClass("dist")
getDistIndex <- function(x, i, j){
    n <- attr(x, "Size")
    if( class(i) == "character"){ i <- which(i[1] == attr(x, "Labels")) }
    if( class(j) == "character"){ j <- which(j[1] == attr(x, "Labels")) }
    # switch indices (symmetric) if i is bigger than j
    if( i > j ){
        i0 <- i
        i  <- j
        j  <- i0
    }
    # for i < j <= n
    return( n*(i-1) - i*(i-1)/2 + j-i )
}
# Define the accessor
"[.dist" <- function(x, i, j, ...){
    x[[getDistIndex(x, i, j)]]
}
################################################################################

这似乎很好地运作了,如预期的那样。然而,我在让替换函数工作方面遇到了麻烦。

################################################################################
# Define the replacement function
################################################################################
"[.dist<-" <- function(x, i, j, value){
    x[[get.dist.index(x, i, j)]] <- value
    return(x)
}
################################################################################

这个新赋值运算符的测试运行

dist1["5", "3"] <- 7000

返回:

“R> 错误:在矩阵上的下标数量不正确

按要求,我认为@flodel回答得更好,但仍然认为这个“答案”也可能有用。

我还在Matrix包中找到了一些漂亮的S4方括号访问器和替换定义的例子,可以从当前的例子中很容易地进行调整。”


0
将其转换为矩阵对我来说也不可行,因为生成的矩阵将会是35K乘以35K,所以我将其保留为向量(距离的结果),并编写了一个函数来查找距离应该存在的向量中的位置:
distXY <- function(X,Y,n){
  A=min(X,Y)
  B=max(X,Y)

  d=eval(parse(text=
               paste0("(A-1)*n  -",paste0((1:(A-1)),collapse="-"),"+ B-A")))

  return(d)

}

在你提供X和Y的情况下,它们是矩阵中元素的原始行,从中计算出dist,n是该矩阵中元素的总数。结果是距离所在的dist向量位置。希望这有意义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接