我想通过计算每对中所有(多维)点集之间的距离平均值来量化组相似度。
我可以很容易地手动为每对组手动完成此操作,就像这样:
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多个行。
谁能提出一个更有效的解决方案,或者根本上不同的方式来解决我所缺少的问题?
答案 0 :(得分:2)
我不确定这是否能解决问题,但这是另一种方法。您使用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
答案 1 :(得分:1)
您可以将每对组连接起来,然后只计算该组中的相异矩阵。显然,这意味着您正在将一个组与其自身进行某种程度的比较,但是它可能仍适用于您的用例,并且使用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)
}
}
}
答案 2 :(得分:1)
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。
重要:请注意,所得交叉距离矩阵的对角线不包含零。
如果您要像dist
那样呼叫proxy::dist(df_list, method = "ngower")
,
proxy
将假定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::pr_DB$delete_entry("ngower")
中重新定义函数,请先使用proxy
。
如果您更喜欢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 here部分和tsclustFamily
的文档。