【发布时间】:2018-04-25 10:14:37
【问题描述】:
我想从数组中随机选择一定数量的元素,以使这些元素的相互距离始终受到限制。
例如,有一个向量a <- seq(1,1000),我怎样才能选择 20 个元素之间的最小距离为 15?
目前,我正在使用一个简单的迭代,只要太靠近任何元素,我都会拒绝选择,但如果要选择的元素数量很多,它会很麻烦并且往往很长。是否有最佳实践/功能?
编辑 - 答案和分析摘要
到目前为止,我有两个可行的答案,我将它们包含在两个特定的函数中。
# dash2 approach
# ---------------
rand_pick_min <- function(ar, min.dist, n.picks){
stopifnot(is.numeric(min.dist),
is.numeric(n.picks), n.picks%%1 == 0)
if(length(ar)/n.picks < min.dist)
stop('The number of picks exceeds the maximum number of divisions that the array allows which is: ',
floor(length(ar)/min.dist))
picked <- array(NA, n.picks)
copy <- ar
for (i in 1:n.picks) {
stopifnot(length(copy) > 0)
picked[i] <- sample(copy, 1)
copy <- copy[ abs(copy - picked[i]) >= min.dist ]
}
return(picked)
}
# denis approach
# ---------------
rand_pick_min2 <- function(ar, min.dist, n.picks){
require(Surrogate)
stopifnot(is.numeric(min.dist),
is.numeric(n.picks), n.picks%%1 == 0)
if(length(ar)/n.picks < min.dist)
stop('The number of picks exceeds the maximum number of divisions that the array allows which is: ',
floor(length(ar)/min.dist))
lar <- length(ar)
dist <- Surrogate::RandVec(a=min.dist, b=(lar-(n.picks)*min.dist),
s=lar, n=(n.picks+1), m=1, Seed=sample(1:lar, size = 1))$RandVecOutput
return(cumsum(round(dist))[1:n.picks])
}
使用建议的相同示例,我运行了 3 个测试。一、最低限度的有效有效期
# Libs
require(ggplot2)
require(microbenchmark)
# Inputs
a <- seq(1, 1000) # test vector
md <- 15 # min distance
np <- 20 # number of picks
# Run
dist_vec <- c(sapply(1:500, function(x) c(dist(rand_pick_min(a, md, np))))) # sol 1
dist_vec2 <- c(sapply(1:500, function(x) c(dist(rand_pick_min2(a, md, np))))) # sol 2
# Tests - break the min
cat('Any distance breaking the min in sol 1?', any(dist_vec < md), '\n') # FALSE
cat('Any distance breaking the min in sol 2?', any(dist_vec2 < md), '\n') # FALSE
其次,我测试了结果距离的分布,按照解的顺序获得了前两个图(sol1 [A] 是 dash2 的 sol,而 sol2 [B] 是 denis 的一个)。
pa <- ggplot() + theme_classic() +
geom_density(aes_string(x = dist_vec), fill = 'lightgreen') +
geom_vline(aes_string(xintercept = mean(dist_vec)), col = 'darkred') + xlab('Distances')
pb <- ggplot() + theme_classic() +
geom_density(aes_string(x = dist_vec2), fill = 'lightgreen') +
geom_vline(aes_string(xintercept = mean(dist_vec)), col = 'darkred') + xlab('Distances')
print(pa)
print(pb)
最后,我计算了两种方法所需的计算时间,如下图并得到最后一个数字。
comp_times <- microbenchmark::microbenchmark(
'solution_1' = rand_pick_min(a, md, np),
'solution_2' = rand_pick_min2(a, md, np),
times = 500
)
ggplot2::autoplot(comp_times); ggsave('stckoverflow2.png')
受到结果的启发,我问自己,距离分布是应该预期的还是由于所应用的方法而导致的偏差。
EDIT2 - 根据丹尼斯的评论回答最后一个问题
使用更多的采样程序 (5000),我生成了结果位置的 pdf,并且确实您的方法包含一些人工制品,使您的解决方案 (B) 偏离了我需要的解决方案。尽管如此,能够强制执行特定的最终职位分配将会很有趣。
【问题讨论】:
-
这个问题在 python 中很相似,但在我看来它缺少随机化过程:stackoverflow.com/questions/21916979/…
-
如果您有
a这样的范围,一个解决方案是在您的最后选择中添加一个随机数:picked[i] <- picked[i-1] + sample(15:100, 1)。但这不适用于任意向量,并且可能很难确保它是一致随机的......但是鉴于您的要求,您不能有一致的随机选择。 -
感谢测试!!看起来 dash2 的答案大部分时间更快,分布更平滑(与我的相比,使用 dash2 的答案,位置的直方图非常平坦)。我认为距离分布应该是这样,峰值应该在 1000/20 = 50。用位置分布图(应该是平坦的)更容易检查。
-
我也添加了这个分析,看来你是对的;)