许多矩阵对之间的相似性/距离

Similarity / distance between many pairs of matrices

我想通过计算每对中所有(多维)点集之间距离的平均值来量化组相似性。

我可以很容易地手动为每对组手动完成此操作,如下所示:

library(dplyr)
library(tibble)
library(proxy)

# dummy data
set.seed(123)
df1 <- data.frame(x = rnorm(100,0,4), 
                  y = rnorm(100,1,5), 
                  z = rbinom(100, 1, 0.1))
df2 <- data.frame(x = rnorm(100,-1,3), 
                  y = rnorm(100,0,6), 
                  z = rbinom(100, 1, 0.1))
df3 <- data.frame(x = rnorm(100,-30,4), 
                  y = rnorm(100,10,2), 
                  z = rbinom(100, 1, 0.9))

# compute distance (unscaled, uncentred data)
dist(df1, df2, method = "gower") %>% mean
dist(df1, df3, method = "gower") %>% mean
dist(df2, df3, method = "gower") %>% mean

但我想以某种方式对其进行矢量化处理,因为我的实际数据有 30 多个组。一个简单的 for 循环可以像这样实现:

# combine data and scale, centre
df <- rbind(df1, df2, df3) %>% 
  mutate(id = rep(1:3, each = 100))
df <- df %>% 
  select(-id) %>%
  transmute_all(scale) %>% 
  add_column(id = df$id)

# create empty matrix for comparisons
n <- df$id %>% unique %>% length
m <- matrix(nrow = n, ncol = n)

# loop through each pair once
for(i in 1:n) {
  for(j in 1:i) { #omit top right corner
    if(i == j) {
      m[i,j] <- NA #omit diagonal
    } else {
      m[i,j] <- dist(df[df$id == i,1:3], df[df$id == j,1:3], method = "gower") %>% mean
    }
  }
}

m
          [,1]      [,2] [,3]
[1,]        NA        NA   NA
[2,] 0.2217443        NA   NA
[3,] 0.8446070 0.8233932   NA

但是,这种方法的扩展性可想而知地很差;一个快速基准表明这将花费 90 多个小时,我的实际数据有 30 多个组,每组 1000 多行。

任何人都可以提出更有效的解决方案,或者提出一种根本不同的方法来解决我遗漏的问题吗?

我不确定这是否会奏效,但这是另一种方法。您使用 ls 获取矩阵名称,使用 combn 生成两个对,然后使用 get 获取用于计算 dist

的矩阵
do.call(rbind,
        combn(ls(pattern = "df\d+"), 2, FUN = function(x)
            data.frame(pair = toString(x),
                       dist = mean(dist(get(x[1]), get(x[2]), method = "gower")),
                       stringsAsFactors = FALSE),
            simplify = FALSE
        ))
#      pair      dist
#1 df1, df2 0.2139304
#2 df1, df3 0.8315169
#3 df2, df3 0.8320911

您可以取每一对组,将它们连接起来,然后只计算该组内的差异矩阵。显然,这意味着您在某种程度上将一个组与其自身进行比较,但它可能仍适用于您的用例,并且对于 daisy 来说,它对于您的数据大小来说相当快。

library(cluster)

n <- 30
groups <- vector("list", 30)

# dummy data
set.seed(123)
for(i in 1:30) {
  groups[[i]] = data.frame(x = rnorm(1000,ceiling(runif(1, -10, 10)),ceiling(runif(1, 2, 4))), 
                           y = rnorm(1000,ceiling(runif(1, -10, 10)),ceiling(runif(1, 2, 4))), 
                           z = rbinom(1000,1,runif(1,0.1,0.9)))
}

m <- matrix(nrow = n, ncol = n)

# loop through each pair once
for(i in 1:n) {
  for(j in 1:i) { #omit top right corner
    if(i == j) {
      m[i,j] <- NA #omit diagonal
    } else {
      # concatenate groups
      dat <- rbind(df_list[[i]], df_list[[j]])

      # compute all distances (between groups and within groups), return matrix
      mm <- dat %>% 
        daisy(metric = "gower") %>%
        as.matrix

      # retain only distances between groups
      mm <- mm[(nrow(df_list[[i]])+1):nrow(dat) , 1:nrow(df_list[[i]])]

      # write mean distance to global comparison matrix
      m[i,j] <- mean(mm)
    }
  }
}

proxy 可以使用矩阵列表作为输入, 你只需要定义一个包装函数来做你想做的事:

nested_gower <- function(x, y, ...) {
  mean(proxy::dist(x, y, ..., method = "gower"))
}

proxy::pr_DB$set_entry(
  FUN = nested_gower,
  names = c("ngower"),
  distance = TRUE,
  loop = TRUE
)

df_list <- list(df1, df2, df3)
proxy::dist(df_list, df_list, method = "ngower")
     [,1]      [,2]      [,3]     
[1,] 0.1978306 0.2139304 0.8315169
[2,] 0.2139304 0.2245903 0.8320911
[3,] 0.8315169 0.8320911 0.2139049

这仍然会很慢, 但它应该比普通 R 中的 for 循环更快 (proxy 在后台使用 C)。

重要提示:注意生成的交叉距离矩阵的对角线没有零。 如果你像 proxy::dist(df_list, method = "ngower") 一样调用 distproxy 将假定 distance(x, y) = distance(y, x)(对称), distance(x, x) = 0, 在这种情况下,后者是不正确的。 将两个参数传递给 dist 可以防止这种假设。 如果你真的不关心对角线, 只传递一个参数以通过避免上三角的计算来节省一些额外的时间。 或者,如果您确实关心对角线但仍想避免计算上三角, 先用一个参数调用 dist 然后调用 proxy::dist(df_list, df_list, method = "ngower", pairwise = TRUE).

旁注:如果您想使用 gower 包模仿这种行为(如 d.b 所建议), 您可以将包装函数定义为:

nested_gower <- function(x, y, ...) {
  distmat <- sapply(seq_len(nrow(y)), function(y_row) {
      gower::gower_dist(x, y[y_row, , drop = FALSE], ...)
  })

  mean(distmat)
}

但是,返回的值似乎会根据传递给函数的记录数而变化, 所以很难说什么是最好的方法。

*如果要在 proxy 中重新定义函数,请先使用 proxy::pr_DB$delete_entry("ngower")


如果您更喜欢 proxy 版本的 Gower 交叉距离矩阵, 我突然想到你可以利用我的 dtwclust 包的一些功能来并行计算:

library(dtwclust)
library(doParallel)

custom_dist <- new("tsclustFamily", dist = "ngower", control = list(symmetric = TRUE))@dist

workers <- makeCluster(detectCores())
registerDoParallel(workers)

distmat <- custom_dist(df_list)

stopCluster(workers); registerDoSEQ()

对于您的实际用例,可能更快 (这里的小样本数据不是那么多)。 关于对角线的相同警告 (所以使用 custom_dist(df_list, df_list)custom_dist(df_list, pairwise = TRUE))。 如果您想了解更多信息,请参阅第 3.2 节 heretsclustFamily 的文档。