【问题标题】:Minimum dissimilarity between one record and a whole data.frame一条记录和整个data.frame之间的最小差异
【发布时间】:2018-06-29 14:46:33
【问题描述】:

我正在努力使计算海量数据集(600,000 条记录)记录中的差异变得可行。

第一个任务是使用单个记录与不包括该记录的整个数据帧之间的欧几里得距离来计算差异。

考虑以下示例:

mydf <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm(5))
one_row <- mydf[1,]

问题分两步表述:

  1. 使用向量化操作返回一个长度为 4 的向量,与 one_row 的每行相比,mydf[-1,]
  2. 从点1的向量中,提取与one_row更相似的行的索引

然后,我可以为 mydf 中的每一行迭代这个过程,因此,为每一行找到其最相似的行。这将允许我执行凝聚聚类以及计算基于距离矩阵的 Silhoutte 等统计标准。

更新

一种可能的方法是将 one_row 复制到相同大小的 mydf 并通过成对执行相似度计算来向量化。

replicated <- [rep(1, 5), 1:ncol(a)]

正确答案

Jesse Tweedle 和 won782 的回答对我的问题都是正确的。

Jesse Tweedle 的积极方面是可以自定义距离函数,从而允许使用混合数据类型。不利的一面是它不是一个单一的表达式,而是一个函数管道。

won782 的积极方面是它在单个表达式中执行。不利的一面是它只适用于矩阵,因此也适用于数值变量。

我选择 won782 答案是因为他的解决方案可以很容易地扩展为用作计算轮廓标准的基本组件,而无需存储相异矩阵。

【问题讨论】:

    标签: r similarity


    【解决方案1】:

    如果我正确理解了您的问题,您希望对给定向量执行逐行运算并计算每一行的欧几里得距离。

    mydf <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm(5))
    one_row <- mydf[1,]
    
    result = apply(mydf, 1, function(x) {
      sqrt(sum((x - one_row)^2))
    })
    result
    [1] 0.000000 3.333031 3.737814 1.875482 4.216042
    

    结果是欧几里得距离向量。然后,您可以通过which.min 函数找到最小值的索引。

    使用矩阵运算:

    sqrt(rowSums((t(t(as.matrix(mydf)) - as.numeric(one_row)))^2))
    

    在更大的数据集上对两种方法进行基准测试

    > mydf <- data.frame(var1 = rnorm(10000), var2 = rnorm(10000), var3 = rnorm(10000))
    > one_row <- mydf[1,]
    > # Matrix operation method
    > system.time({ 
    +   sqrt(rowSums((t(t(as.matrix(mydf)) - as.numeric(one_row)))^2))
    +   })
       user  system elapsed 
      0.000   0.000   0.001 
    > # Apply Method
    > system.time({ 
    +   apply(mydf, 1, function(x) {
    +     sqrt(sum((x - one_row)^2))
    +   })
    + })
       user  system elapsed 
      5.186   0.014   5.204 
    

    很明显,矩阵运算是更快的方法。

    【讨论】:

    • 是否可以矢量化操作而不是应用循环?原因是因为要为每一行计算最相似的行,这将需要不可行的时间。它不是应用 nrow(mydf) 向量化操作,而是执行 nrow(mydf) 应用循环
    • 矩阵方法肯定更快。唯一的缺点是它不能用于混合数据类型。
    【解决方案2】:

    问题:

    您可以在mydf 上使用dist,但答案对于您的计算机来说太大了(1e11-ish 元素)。所以挑战是计算每一行 x 整个数据集的欧几里得距离。你不想一遍又一遍地复制整个事情,因为你会做 600,000 次。但是你可以写一个向量化的函数来计算欧几里得距离,并使用tidyverse的东西来简洁地应用它。

    答案:

    编写一个函数euc 并将其向量化到第二个参数上。

    library(tidyverse)
    euc <- function(x, y) { 
      sqrt(sum((x - y)^2))
    }
    euc_ <- Vectorize(euc, vectorize.args = "y")
    calculate_distances <- function(row, df) {
      dists <- euc_(row, split(df, 1:nrow(df)))
      # gives you name of row and distance that gives minimum distance.
      dists[dists>0 & dists == min(dists[dists>0])] %>% enframe()
    }
    

    然后calculate_distances 函数计算从单行到数据集其余部分的欧几里得距离,然后将参数折叠到具有最小距离的那个的名称和值(不包括它自己,所以我们需要包括dist&gt;0)。

    然后将变量组合成一列(这样可以更容易地传递给calculate_distances 之类的函数,而无需指定列名var1 等)。然后使用mutatemap 将函数应用于每一行,然后使用unnest 解压缩结果(如果您愿意,可以保留原始数据)。

    mydf <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm(5))
    
    mydf %>% 
      mutate(n = row_number()) %>% 
      group_by(n) %>% 
      nest(var1, var2, var3) %>% 
      mutate(ans = map(data, calculate_distances, df = mydf)) %>%
      unnest(ans, data)
    # A tibble: 5 x 6
          n  name    value         var1       var2       var3
      <int> <chr>    <dbl>        <dbl>      <dbl>      <dbl>
    1     1     4 1.027080  0.035684445  0.3152272  1.9001506
    2     2     5 1.453509 -0.985996620  0.2650241 -0.2146157
    3     3     2 1.645737  0.009665813 -0.8393461  0.4907029
    4     4     1 1.027080  0.314943627  0.9910671  1.1789382
    5     5     2 1.453509  0.436344415  0.5309611 -0.3521368
    

    ? 祝你好运! ? 希望这会有所帮助。

    【讨论】:

    • 基本上通过这一步我们直接获取到每条记录最近的记录,而不存储整个距离矩阵。此外,作为矢量化操作,我们可以快速计算相似度。此外,作为 calculate_distances 函数,我们可以将其更改为欧几里得以外的指标。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-04
    • 2011-05-17
    • 1970-01-01
    相关资源
    最近更新 更多