计算两个整数矩阵/数据帧的所有行之间的成对汉明距离

时间:2016-06-20 01:09:15

标签: r apply sapply tapply hamming-distance

我有两个数据框,df1包含参考数据,df2包含新数据。对于df2中的每一行,我需要在汉明距离方面找到与df1匹配的最佳(和第二好)匹配行。

我使用e1071包来计算汉明距离。可以计算两个向量xy之间的汉明距离,例如:

x <- c(356739, 324074, 904133, 1025460, 433677, 110525, 576942, 526518, 299386,
       92497, 977385, 27563, 429551, 307757, 267970, 181157, 3796, 679012, 711274,
       24197, 610187, 402471, 157122, 866381, 582868, 878)

y <- c(356739, 324042, 904133, 959893, 433677, 110269, 576942, 2230, 267130,
       92496, 960747, 28587, 429551, 438825, 267970, 181157, 36564, 677220,
       711274, 24485, 610187, 404519, 157122, 866413, 718036, 876)

xm <- sapply(x, intToBits)
ym <- sapply(y, intToBits)

distance <- sum(sapply(1:ncol(xm), function(i) hamming.distance(xm[,i], ym[,i])))

,结果距离为25.但我需要为df1df2的所有行执行此操作。一个简单的方法需要一个双循环嵌套,看起来非常慢。

任何想法如何更有效地做到这一点?最后,我需要附加到df2

  • 来自df1的行ID的列,该列具有最低距离;
  • 距离最短的列;
  • 来自df1的行ID的列,其给出第二个最低距离;
  • 距离第二低的列。

感谢。

3 个答案:

答案 0 :(得分:3)

快速计算两个等长的整数向量之间的汉明距离

正如我在评论中所说,我们可以这样做:

hmd0 <- function(x,y) sum(as.logical(xor(intToBits(x),intToBits(y))))

计算两个等长的整数向量 xy之间的汉明距离。这仅使用R base,但效率高于e1071::hamming.distance,因为它是矢量化的!

对于帖子中的示例xy,这会给出25.(我的其他答案将显示我们应该做什么,如果我们想要成对汉明距离。

矩阵与矢量之间的快速汉明距离

如果我们想计算单个y和多个x之间的汉明距离,即矢量和矩阵之间的汉明距离,我们可以使用以下函数。

hmd <- function(x,y) {
  rawx <- intToBits(x)
  rawy <- intToBits(y)
  nx <- length(rawx)
  ny <- length(rawy)
  if (nx == ny) {
    ## quick return
    return (sum(as.logical(xor(rawx,rawy))))
    } else if (nx < ny) {
    ## pivoting
    tmp <- rawx; rawx <- rawy; rawy <- tmp
    tmp <- nx; nx <- ny; ny <- tmp
    }
  if (nx %% ny) stop("unconformable length!") else {
    nc <- nx / ny  ## number of cycles
    return(unname(tapply(as.logical(xor(rawx,rawy)), rep(1:nc, each=ny), sum)))
    }
  }

请注意:

  1. hmd执行计算列式。它被设计为 CPU缓存友好。这样,如果我们想要进行一些行计算,我们应该首先转置矩阵;
  2. 这里没有明显的循环;相反,我们使用tapply()
  3. 两个矩阵/数据帧之间的快速汉明距离计算

    这就是你想要的。以下函数foo采用两个数据框或矩阵df1df2,计算df1df2的每一行之间的距离。参数p是一个整数,显示您想要保留多少结果。 p = 3df1中的行ID保持最小的3个距离。

    foo <- function(df1, df2, p) {
      ## check p
      if (p > nrow(df2)) p <- nrow(df2)
      ## transpose for CPU cache friendly code
      xt <- t(as.matrix(df1))
      yt <- t(as.matrix(df2))
      ## after transpose, we compute hamming distance column by column
      ## a for loop is decent; no performance gain from apply family
      n <- ncol(yt)
      id <- integer(n * p)
      d <- numeric(n * p)
      k <- 1:p
      for (i in 1:n) {
        distance <- hmd(xt, yt[,i])
        minp <- order(distance)[1:p]
        id[k] <- minp
        d[k] <- distance[minp]
        k <- k + p
        }
      ## recode "id" and "d" into data frame and return
      id <- as.data.frame(matrix(id, ncol = p, byrow = TRUE))
      colnames(id) <- paste0("min.", 1:p)
      d <- as.data.frame(matrix(d, ncol = p, byrow = TRUE))
      colnames(d) <- paste0("mindist.", 1:p)
      list(id = id, d = d)
      }
    

    请注意:

      根据之前的原因,
    1. 换位在开始时完成;
    2. 此处使用for循环。但这实际上是有效的,因为在每次迭代中都进行了大量的计算。它比使用*apply系列更优雅,因为我们要求多个输出(行ID id和距离d)。
    3. <强>实验

      这部分使用小数据集来测试/演示我们的功能。

      一些玩具数据:

      set.seed(0)
      df1 <- as.data.frame(matrix(sample(1:10), ncol = 2))  ## 5 rows 2 cols
      df2 <- as.data.frame(matrix(sample(1:6), ncol = 2))  ## 3 rows 2 cols
      

      首先测试hmd(需要换位):

      hmd(t(as.matrix(df1)), df2[1, ])  ## df1 & first row of df2
      # [1] 2 4 6 2 4
      

      测试foo

      foo(df1, df2, p = 2)
      
      # $id
      #   min1 min2
      # 1    1    4
      # 2    2    3
      # 3    5    2
      
      # $d
      #   mindist.1 mindist.2
      # 1         2         2
      # 2         1         3
      # 3         1         3
      

      如果您想将某些列附加到df2,您知道该怎么做,对吗?

答案 1 :(得分:3)

为什么我要接受另一部分,请不要感到惊讶。这部分给出了相关的内容。 这不是OP所要求的,但可以帮助任何读者。

一般汉明距离计算

在上一个答案中,我从函数hmd0开始,它计算两个相同长度的整数向量之间的汉明距离。这意味着如果我们有2个整数向量:

set.seed(0)
x <- sample(1:100, 6)
y <- sample(1:100, 6)

我们最终会得到一个标量:

hmd0(x,y)
# 13

如果我们想要计算两个向量的成对汉明距离怎么办?

事实上,对我们的函数hmd进行简单修改即可:

hamming.distance <- function(x, y, pairwise = TRUE) {
  nx <- length(x)
  ny <- length(y)
  rawx <- intToBits(x)
  rawy <- intToBits(y)
  if (nx == 1 && ny == 1) return(sum(as.logical(xor(intToBits(x),intToBits(y)))))
  if (nx < ny) {
    ## pivoting
    tmp <- rawx; rawx <- rawy; rawy <- tmp
    tmp <- nx; nx <- ny; ny <- tmp
    }
  if (nx %% ny) stop("unconformable length!") else {
    bits <- length(intToBits(0)) ## 32-bit or 64 bit?
    result <- unname(tapply(as.logical(xor(rawx,rawy)), rep(1:ny, each = bits), sum))
    }
  if (pairwise) result else sum(result)
  }

现在

hamming.distance(x, y, pairwise = TRUE)
# [1] 0 3 3 2 5 0
hamming.distance(x, y, pairwise = FALSE)
# [1] 13

汉明距离矩阵

如果我们想计算汉明距离矩阵,例如,

set.seed(1)
x <- sample(1:100, 5)
y <- sample(1:100, 7)

xy之间的距离矩阵为:

outer(x, y, hamming.distance)  ## pairwise argument has no effect here

#      [,1] [,2] [,3] [,4] [,5] [,6] [,7]
# [1,]    2    3    4    3    4    4    2
# [2,]    7    6    3    4    3    3    3
# [3,]    4    5    4    3    6    4    2
# [4,]    2    3    2    5    6    4    2
# [5,]    4    3    4    3    2    0    2

我们也可以这样做:

outer(x, x, hamming.distance)

#     [,1] [,2] [,3] [,4] [,5]
# [1,]    0    5    2    2    4
# [2,]    5    0    3    5    3
# [3,]    2    3    0    2    4
# [4,]    2    5    2    0    4
# [5,]    4    3    4    4    0

在后一种情况下,我们最终得到一个对角矩阵,对角线上有0。在这里使用outer效率很低,但它仍然比编写R循环更有效。由于我们的hamming.distance是用R代码编写的,因此我会继续使用outer。在my answerthis question中,我演示了使用编译代码的想法。这当然需要编写hamming.distance的C版本,但我不会在此处显示。

答案 2 :(得分:1)

这是一个仅使用基本R的替代解决方案,应该非常快,特别是当你的df1和df2有很多行时。主要原因是它不使用任何 R级循环来计算汉明距离,例如for循环,while循环或* apply函数。相反,它使用matrix multiplication for computing the Hamming distance。在R中,这比使用R级循环的任何方法快得多。另请注意,使用* apply函数不一定会使您的代码比使用for循环更快。这种方法的另外两个与效率相关的特征是:(1)它使用partial sorting为df2中的每一行找到最佳的两个匹配,以及(2)它将df1的整个按位表示存储在一个矩阵中(相同)对于df2),并且在一个步骤中完成,不使用任何R级循环。

完成所有工作的功能:

# INPUT:       
# X corresponds to your entire df1, but is a matrix
# Y corresponds to your entire df2, but is a matrix
# OUTPUT:
# Matrix with four columns corresponding to the values 
# that you specified in your question
fun <- function(X, Y) {

  # Convert integers to bits 
  X <- intToBits(t(X))
  # Reshape into matrix
  dim(X) <- c(ncols * 32, nrows)

  # Convert integers to bits
  Y <- intToBits(t(Y))
  # Reshape into matrix
  dim(Y) <- c(ncols * 32, nrows)

  # Calculate pairwise hamming distances using matrix 
  # multiplication. 
  # Columns of H index into Y; rows index into X.
  # The code for the hamming() function was retrieved
  # from this page:
  # https://johanndejong.wordpress.com/2015/10/02/faster-hamming-distance-in-r-2/
  H <- hamming(X, Y)

  # Now, for each row in Y, find the two best matches 
  # in X. In other words: for each column in H, find 
  # the two smallest values and their row indices.
  t(apply(H, 2, function(h) {
    mindists <- sort(h, partial = 1:2)
    c(
      ind1 = which(h == mindists[1])[1],
      val1 = mindists[1],
      hmd2 = which(h == mindists[2])[1],
      val2 = mindists[2]
    )
  }))
}

要在某些随机数据上调用该函数:

# Generate some random test data with no. of columns 
# corresponding to your data
nrows <- 1000
ncols <- 26 

# X corresponds to your df1
X <- matrix(
  sample(1e6, nrows * ncols, replace = TRUE), 
  nrow = nrows, 
  ncol = ncols
)

# Y corresponds to your df2
Y <- matrix(
  sample(1e6, nrows * ncols, replace = TRUE), 
  nrow = nrows, 
  ncol = ncols
)

res <- fun(X, Y)

以上X(df1)和Y(df2)中包含1000行的示例在我的笔记本电脑上运行大约需要1.1到1.2秒。