我有两个数据框,df1
包含参考数据,df2
包含新数据。对于df2
中的每一行,我需要在汉明距离方面找到与df1
匹配的最佳(和第二好)匹配行。
我使用e1071
包来计算汉明距离。可以计算两个向量x
和y
之间的汉明距离,例如:
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.但我需要为df1
和df2
的所有行执行此操作。一个简单的方法需要一个双循环嵌套,看起来非常慢。
任何想法如何更有效地做到这一点?最后,我需要附加到df2
:
df1
的行ID的列,该列具有最低距离; df1
的行ID的列,其给出第二个最低距离; 感谢。
答案 0 :(得分:3)
快速计算两个等长的整数向量之间的汉明距离
正如我在评论中所说,我们可以这样做:
hmd0 <- function(x,y) sum(as.logical(xor(intToBits(x),intToBits(y))))
计算两个等长的整数向量 x
和y
之间的汉明距离。这仅使用R base,但效率高于e1071::hamming.distance
,,因为它是矢量化的!
对于帖子中的示例x
和y
,这会给出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)))
}
}
请注意:
hmd
执行计算列式。它被设计为 CPU缓存友好。这样,如果我们想要进行一些行计算,我们应该首先转置矩阵; tapply()
。两个矩阵/数据帧之间的快速汉明距离计算
这就是你想要的。以下函数foo
采用两个数据框或矩阵df1
和df2
,计算df1
与df2
的每一行之间的距离。参数p
是一个整数,显示您想要保留多少结果。 p = 3
将df1
中的行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)
}
请注意:
for
循环。但这实际上是有效的,因为在每次迭代中都进行了大量的计算。它比使用*apply
系列更优雅,因为我们要求多个输出(行ID id
和距离d
)。<强>实验强>
这部分使用小数据集来测试/演示我们的功能。
一些玩具数据:
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)
x
和y
之间的距离矩阵为:
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 answer到this 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秒。