【问题标题】:cosine similarity(patient similarity metric) between 48k patients data with predictive variables具有预测变量的 48k 患者数据之间的余弦相似度(患者相似度度量)
【发布时间】:2017-06-09 13:09:52
【问题描述】:

我必须用一些预测变量计算 48k 患者数据之间的 R 余弦相似度(患者相似度度量)。这是等式: PSM(P1,P2) = P1.P2/ ||P1|| ||P2||

其中 P1 和 P2 是对应于两个不同患者的预测向量,例如 P1 索引患者和 P2 将与索引 (P1) 进行比较,最后将计算成对患者相似度度量 PSM(P1,P2)。

此过程将针对所有 48,000 名患者进行。

我在 .csv 文件中添加了 300 名患者的样本数据集。请在此处找到示例数据集。https://1drv.ms/u/s!AhoddsPPvdj3hVTSbosv2KcPIx5a

【问题讨论】:

  • 请在 .csv 文件中找到 300 名患者的样本数据集。请在此处找到示例数据集。 1drv.ms/u/s!AhoddsPPvdj3hVTSbosv2KcPIx5a
  • 另外,您是否有一些“相似”和“不同”患者的金标准/已知病例?您请求的指标类型不需要它,但在计算完成后它是一个很好的现实检查。
  • 如果您有 10^6 位患者,您的相似度矩阵将约为 10^6 x 10^6。你有多少内存?

标签: r data-mining similarity cosine-similarity


【解决方案1】:

首先要做的是:您可以在以下任一帖子中找到更严格的余弦相似度处理方法:

现在,您的输入中显然混合了多种数据类型,至少

  • 十进制
  • 整数
  • 分类

我怀疑某些整数值是布尔值或其他分类值。通常,如果您想将它们用作相似度计算的输入,则由您将它们转换为连续的数值向量。例如,准入类型ELECTIVEEMERGENCY 之间的距离是多少?它是名义变量还是有序变量?我只会对我认为是数值因变量的列进行建模。

另外,您做了哪些工作来确保您的某些专栏与其他专栏不相关? 只需对数据科学和生物医学术语有一点了解,以下内容似乎都是相关:

diasbp_maxdiasbp_minmeanbp_maxmeanbp_minsysbp_maxsysbp_min

我建议去印刷店并订购海报尺寸的psm_pairs.pdf 打印输出。 :-) 你的眼睛更擅长检测变量之间有意义的(但非线性的)依赖关系。包括对同一基本现象的多次测量可能会在您的相似性计算中过度重视该现象。不要忘记你可以派生变量像

diasbp_rage <- diasbp_max - diasbp_min

现在,我不是特别擅长线性代数,所以我从lsa 文本分析包中导入了一个余弦相似度函数。我很乐意看到将您问题中的公式写成 R 函数。我会写它来比较一行与另一行,并使用两个嵌套的应用循环来获取所有比较。希望我们能得到同样的结果!

在计算相似度后,我尝试找到两个不同的患者,他们的遭遇最不相似。

由于您要处理大量相对较大的行,因此您需要比较各种算法方法的效率。此外,您可以在集群上使用 SparkR/其他一些 Hadoop 解决方案,或者在具有多核和 大量 RAM 的单台计算机上使用 parallel 包。我不知道我提供的解决方案是否是线程安全的。

想一想,对于一组 100 万例患者而言,仅换位(正如我实现的那样)在计算上可能会很昂贵。总的来说,(如果我没记错的话)随着输入中行数的增加,性能可能会呈指数级下降。

library(lsa)
library(reshape2)

psm_sample <- read.csv("psm_sample.csv")

row.names(psm_sample) <-
  make.names(paste0("patid.", as.character(psm_sample$subject_id)), unique = TRUE)
temp <- sapply(psm_sample, class)
temp <- cbind.data.frame(names(temp), as.character(temp))
names(temp) <- c("variable", "possible.type")

numeric.cols <- (temp$possible.type %in% c("factor", "integer") &
                   (!(grepl(
                     pattern = "_id$", x = temp$variable
                   ))) &
                   (!(
                     grepl(pattern = "_code$", x = temp$variable)
                   )) &
                   (!(
                     grepl(pattern = "_type$", x = temp$variable)
                   ))) | temp$possible.type == "numeric"

psm_numerics <- psm_sample[, numeric.cols]
row.names(psm_numerics) <- row.names(psm_sample)

psm_numerics$gender <- as.integer(psm_numerics$gender)

psm_scaled <- scale(psm_numerics)

pair.these.up <- psm_scaled
# checking for independence of variables
# if the following PDF pair plot is too big for your computer to open,
# try pair-plotting some random subset of columns
# keep.frac <- 0.5
# keep.flag <- runif(ncol(psm_scaled)) < keep.frac
# pair.these.up <- psm_scaled[, keep.flag]
# pdf device sizes are in inches
dev <-
  pdf(
    file = "psm_pairs.pdf",
    width = 50,
    height = 50,
    paper = "special"
  )
pairs(pair.these.up)
dev.off()

#transpose the dataframe to get the
#similarity between patients
cs <- lsa::cosine(t(psm_scaled))

# this is super inefficnet, 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")

extract.pat <- function(enc.col) {
  my.patients <-
    sapply(enc.col, function(one.pat) {
      temp <- (strsplit(as.character(one.pat), ".", fixed = TRUE))
      return(temp[[1]][[2]])
    })
  return(my.patients)
}
cs.melt$pat.A <- extract.pat(cs.melt$enc.A)
cs.melt$pat.B <- extract.pat(cs.melt$enc.B)

same.pat <-      cs.melt[cs.melt$pat.A == cs.melt$pat.B ,]
different.pat <- cs.melt[cs.melt$pat.A != cs.melt$pat.B ,]

most.dissimilar <-
  different.pat[which.min(different.pat$similarity),]

dissimilar.pat.frame <- rbind(psm_numerics[rownames(psm_numerics) ==
                                             as.character(most.dissimilar$enc.A) ,],
                              psm_numerics[rownames(psm_numerics) ==
                                             as.character(most.dissimilar$enc.B) ,])

print(t(dissimilar.pat.frame))

给了

                  patid.68.49   patid.9
gender                1.00000   2.00000
age                  41.85000  41.79000
sysbp_min            72.00000 106.00000
sysbp_max            95.00000 217.00000
diasbp_min           42.00000  53.00000
diasbp_max           61.00000 107.00000
meanbp_min           52.00000  67.00000
meanbp_max           72.00000 132.00000
resprate_min         20.00000  14.00000
resprate_max         35.00000  19.00000
tempc_min            36.00000  35.50000
tempc_max            37.55555  37.88889
spo2_min             90.00000  95.00000
spo2_max            100.00000 100.00000
bicarbonate_min      22.00000  26.00000
bicarbonate_max      22.00000  30.00000
creatinine_min        2.50000   1.20000
creatinine_max        2.50000   1.40000
glucose_min          82.00000 129.00000
glucose_max          82.00000 178.00000
hematocrit_min       28.10000  37.40000
hematocrit_max       28.10000  45.20000
potassium_min         5.50000   2.80000
potassium_max         5.50000   3.00000
sodium_min          138.00000 136.00000
sodium_max          138.00000 140.00000
bun_min              28.00000  16.00000
bun_max              28.00000  17.00000
wbc_min               2.50000   7.50000
wbc_max               2.50000  13.70000
mingcs               15.00000  15.00000
gcsmotor              6.00000   5.00000
gcsverbal             5.00000   0.00000
gcseyes               4.00000   1.00000
endotrachflag         0.00000   1.00000
urineoutput        1674.00000 887.00000
vasopressor           0.00000   0.00000
vent                  0.00000   1.00000
los_hospital         19.09310   4.88130
los_icu               3.53680   5.32310
sofa                  3.00000   5.00000
saps                 17.00000  18.00000
posthospmort30day     1.00000   0.00000

【讨论】:

  • 感谢您的努力!!首先,我不需要不同的患者。我是 R 的新手。如果您在我提到的问题中编写公式并将一行与另一行进行比较,请编写两个嵌套的应用循环以获取所有比较,这将是很好的。当我运行您的代码并打印相似的患者时,我只能看到正值的相似性,但它也可能是负的,而且据我所知,我认为这不是成对计算,与我像 (P1, P2) 一样的方式相比P1索引患者和P2将与索引(P1)进行比较
  • 现在关于 PSM 计算和您的查询的更多说明: 1. PSM 是角度的余弦,它在 -1(角度为 180 度时的最小相似度)和 1(最大相似度)之间进行归一化当角度为 0 度时)。 2. 对每个连续预测变量进行归一化以适应 -1 和 1 之间的范围,以便所有预测变量对 PSM 的贡献均等。 3. 对于两个向量之间的每个分类预测器产品,如果它们具有相同的类别,则应分配 1,否则为 -1,以全有或全无的方式。
  • 我已经在这个问题上投入了几个小时。如果它根本没有帮助,那么我可以理解它没有被投票。 我什至愿意花更多时间在上面,但我想先看看你的更多工作。你说你是 R 新手……你更精通什么语言?我很想看到您使用任何语言的解决方案。也许您应该先尝试使用更简单的数据集,例如 irises。
  • 我可以向你保证,这个例子确实产生了 pairwise 相似性......你看过cs 吗?我没有在后台查看lsa::cosine,但我相信它可以计算余弦相似度,正如您所描述的那样。如果你能用代码告诉我计算不正确,请这样做。我报告了 不同 名患者,即使您可能对他们不感兴趣,作为算法成功的视觉上令人信服的指标,因为您没有提供患者相似性的黄金标准。
  • 正如其他人评论的那样,数千或数百万行的成对操作从一开始就需要注意效率,并且您应该非常小心嵌套循环
【解决方案2】:

通常我不会添加第二个答案,但这可能是这里最好的解决方案。不用担心投票。

这是与我的第一个答案相同的算法,应用于 iris 数据集。每行包含来自三种不同品种鸢尾植物的花朵的四个空间测量值。

您将在下面找到 iris 分析,它以嵌套循环的形式写出,因此您可以看到等价性。但不建议将其用于具有大型数据集的生产环境。

请熟悉起始数据和所有中间数据帧:

  1. 输入iris数据
  2. psm_scaled(空间测量值,缩放为均值=0,SD=1)
  3. cs(两两相似度矩阵)
  4. 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 处以两种格式表示

记住向量 AB 表示 PatientAPatientB 的属性的有序列表,即 PSM是 AB 的点积,除以 ([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.Acs.melt$class.Bcs.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

也许你应该更关心

  • 变量的选择,包括去除高度相关的变量
  • 缩放/标准化/标准化数据
  • 大型输入数据集的性能
  • 识别已知的相似点和不同点以进行质量控制

如前所述

【讨论】:

  • 首先向你道歉再问你一次。我的要求有一些变化。现在成对的相似性就像病人(3 vs 3),(3.1 vs 3),(9 vs 9)等等。我不需要像(3 vs 3)这样的自身之间的成对计算等等......我需要逐行计算每一行,其中每一行将与其他行的其余部分进行比较,然后在下一次迭代中将其排除在外。
  • 例如:患者之间的成对计算 (3 vs 3.1), (3 vs 9), (3 vs9.1) ...。 (3 vs 数据框中的最后一个患者),然后是患者,它将被排除在迭代之外。此过程将针对所有行进行。这是输出示例——1drv.ms/w/s!AhoddsPPvdj3hVcH6uYE37uhVLvY
  • 这是一个合理的要求,但这篇文章已经太长了。我建议开始一个新问题并展示您到目前为止所做的事情,以及为什么它不符合您的要求。也许除了我之外,您还会从其他人那里得到一些输入。如果您准确地展示您的输入数据是什么样子、您想要的结果是什么样子以及您迄今为止尝试过的代码,您最有可能得到响应。这需要一些工作,但这就是您为获得有关堆栈溢出的免费专业建议所付出的代价。
猜你喜欢
  • 2021-04-30
  • 2011-05-01
  • 2014-02-25
  • 2020-08-12
  • 2017-04-04
  • 2011-01-01
  • 2011-02-08
  • 2011-03-08
相关资源
最近更新 更多