48k 患者数据与预测变量之间的余弦相似性(患者相似性度量)

cosine similarity(patient similarity metric) between 48k patients data with predictive variables

我必须计算 R 中 48k 患者数据与一些预测变量之间的余弦相似性(患者相似性度量)。这是等式: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

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

  • Find cosine similarity between two arrays
  • Creating co-occurrence matrix

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

  • 十进制
  • 整数
  • 分类

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

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

diasbp_max, diasbp_min, meanbp_max, meanbp_min, sysbp_max and sysbp_min

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

这样的变量
diasbp_rage <- diasbp_max - diasbp_min

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

计算相似度后,我尝试找到两个最不相似的遭遇。

由于您处理的行数相对较大,因此您需要比较各种算法方法以提高效率。此外,您可以在具有多核和 lots RAM 的单台计算机上使用 SparkR/some other Hadoop solution on a cluster, or the 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

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

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

下面是虹膜分析,写成嵌套循环,因此您可以看到等效性。但不建议将其用于大型数据集的生产。

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

  1. 输入iris数据
  2. psm_scaled(空间测量值,缩放为均值=0,SD=1)
  3. cs(成对相似度矩阵)
  4. cs.melt(长格式的成对相似度)

最后,我汇总了一个品种与另一个品种之间所有比较的平均相似度。你会看到,同一品种个体之间的比较具有接近 1 的平均相似度,而同一品种个体之间的比较具有接近 negative1.

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 表示 PatientA 的有序属性列表和PatientB,PSM 是 AB 的点积,除以(的标量积[A的大小]和[B的大小])

在 R 中的简洁表达方式是

cosine.sim <- function(A, B) { A %*% B / sqrt(A %*% A * B %*% B) }  

但我们可以重写它,使其看起来更像您的 post

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

也许你更应该关心

  • 变量的选择,包括删除高度相关的变量
  • scaling/normalizing/standardizing数据
  • 大型输入数据集的性能
  • 识别已知的相似点和不同点以进行质量控制

如前所述