通常我不会添加第二个答案,但这可能是这里最好的解决方案。不用担心投票。
这是与我的第一个答案相同的算法,应用于 iris 数据集。每行包含来自三种不同品种鸢尾植物的花朵的四个空间测量值。
您将在下面找到 iris 分析,它以嵌套循环的形式写出,因此您可以看到等价性。但不建议将其用于具有大型数据集的生产环境。
请熟悉起始数据和所有中间数据帧:
- 输入
iris数据
-
psm_scaled(空间测量值,缩放为均值=0,SD=1)
-
cs(两两相似度矩阵)
-
cs.melt(长格式的成对相似性)
最后,我汇总了一个品种与另一个品种之间所有比较的平均相似度。您会看到,同一品种个体之间的比较平均相似度接近 1,而同一品种个体之间的比较平均相似度接近 负1。
library(lsa)
library(reshape2)
temp <- iris[, 1:4]
iris.names <- paste0(iris$Species, '.', rownames(iris))
psm_scaled <- scale(temp)
rownames(psm_scaled) <- iris.names
cs <- lsa::cosine(t(psm_scaled))
# this is super inefficient, because cs contains
# two identical triangular matrices
cs.melt <- melt(cs)
cs.melt <- as.data.frame(cs.melt)
names(cs.melt) <- c("enc.A", "enc.B", "similarity")
names(cs.melt) <- c("flower.A", "flower.B", "similarity")
class.A <-
strsplit(as.character(cs.melt$flower.A), '.', fixed = TRUE)
cs.melt$class.A <- sapply(class.A, function(one.split) {
return(one.split[1])
})
class.B <-
strsplit(as.character(cs.melt$flower.B), '.', fixed = TRUE)
cs.melt$class.B <- sapply(class.B, function(one.split) {
return(one.split[1])
})
cs.melt$comparison <-
paste0(cs.melt$class.A , '_vs_', cs.melt$class.B)
cs.agg <-
aggregate(cs.melt$similarity, by = list(cs.melt$comparison), mean)
print(cs.agg[order(cs.agg$x),])
给了
# Group.1 x
# 3 setosa_vs_virginica -0.7945321
# 7 virginica_vs_setosa -0.7945321
# 2 setosa_vs_versicolor -0.4868352
# 4 versicolor_vs_setosa -0.4868352
# 6 versicolor_vs_virginica 0.3774612
# 8 virginica_vs_versicolor 0.3774612
# 5 versicolor_vs_versicolor 0.4134413
# 9 virginica_vs_virginica 0.7622797
# 1 setosa_vs_setosa 0.8698189
如果您仍然不习惯在缩放的数值数据帧上执行 lsa::cosine(),我们当然可以进行显式的成对计算。
您为 PSM 或患者的余弦相似度提供的公式在 Wikipedia 处以两种格式表示
记住向量 A 和 B 表示 PatientA 和 PatientB 的属性的有序列表,即 PSM是 A 和 B 的点积,除以 ([A 的量级] 和 [A 的量级的标量积>B])
在 R 中的简洁表达方式是
cosine.sim <- function(A, B) { A %*% B / sqrt(A %*% A * B %*% B) }
但我们可以将其重写为与您的帖子更相似
cosine.sim <- function(A, B) { A %*% B / (sqrt(A %*% A) * sqrt(B %*% B)) }
我猜你甚至可以将它(一对个体之间的相似性计算)重写为一堆嵌套循环,但在数据量可控的情况下,请不要。 R 针对向量和矩阵的运算进行了高度优化。如果您是 R 新手,请不要再猜测了。顺便说一句,你的数百万行发生了什么?既然您已经减少到数万人,这肯定会减轻压力。
不管怎样,假设每个个体只有两个元素。
individual.1 <- c(1, 0)
individual.2 <- c(1, 1)
因此,您可以将 individual.1 视为在原点 (0,0) 和 (0, 1) 之间穿过的线,将 individual.2 视为在原点和 (1, 1) 之间穿过的线。
some.data <- rbind.data.frame(individual.1, individual.2)
names(some.data) <- c('element.i', 'element.j')
rownames(some.data) <- c('individual.1', 'individual.2')
plot(some.data, xlim = c(-0.5, 2), ylim = c(-0.5, 2))
text(
some.data,
rownames(some.data),
xlim = c(-0.5, 2),
ylim = c(-0.5, 2),
adj = c(0, 0)
)
segments(0, 0, x1 = some.data[1, 1], y1 = some.data[1, 2])
segments(0, 0, x1 = some.data[2, 1], y1 = some.data[2, 2])
那么向量 individual.1 和向量 individual.2 的夹角是多少?你猜对了,0.785 弧度,也就是 45 度。
cosine.sim <- function(A, B) { A %*% B / (sqrt(A %*% A) * sqrt(B %*% B)) }
cos.sim.result <- cosine.sim(individual.1, individual.2)
angle.radians <- acos(cos.sim.result)
angle.degrees <- angle.radians * 180 / pi
print(angle.degrees)
# [,1]
# [1,] 45
现在我们可以使用我之前在两个嵌套循环中定义的cosine.sim函数来显式计算每个鸢尾花之间的成对相似度。请记住,psm_scaled 已被定义为来自iris 数据集的缩放数值。
cs.melt <- lapply(rownames(psm_scaled), function(name.A) {
inner.loop.result <-
lapply(rownames(psm_scaled), function(name.B) {
individual.A <- psm_scaled[rownames(psm_scaled) == name.A, ]
individual.B <- psm_scaled[rownames(psm_scaled) == name.B, ]
similarity <- cosine.sim(individual.A, individual.B)
return(list(name.A, name.B, similarity))
})
inner.loop.result <-
do.call(rbind.data.frame, inner.loop.result)
names(inner.loop.result) <-
c('flower.A', 'flower.B', 'similarity')
return(inner.loop.result)
})
cs.melt <- do.call(rbind.data.frame, cs.melt)
现在我们重复上述cs.melt$class.A、cs.melt$class.B和cs.melt$comparison的计算,并计算cs.agg.from.loops作为各类比较之间的平均相似度:
cs.agg.from.loops <-
aggregate(cs.agg.from.loops$similarity, by = list(cs.agg.from.loops $comparison), mean)
print(cs.agg.from.loops[order(cs.agg.from.loops$x),])
# Group.1 x
# 3 setosa_vs_virginica -0.7945321
# 7 virginica_vs_setosa -0.7945321
# 2 setosa_vs_versicolor -0.4868352
# 4 versicolor_vs_setosa -0.4868352
# 6 versicolor_vs_virginica 0.3774612
# 8 virginica_vs_versicolor 0.3774612
# 5 versicolor_vs_versicolor 0.4134413
# 9 virginica_vs_virginica 0.7622797
# 1 setosa_vs_setosa 0.8698189
我相信这与我们使用lsa::cosine 得到的结果相同。
所以我想说的是……你为什么不使用lsa::cosine?
也许你应该更关心
- 变量的选择,包括去除高度相关的变量
- 缩放/标准化/标准化数据
- 大型输入数据集的性能
- 识别已知的相似点和不同点以进行质量控制
如前所述