我想从一个数组中随机选择一定数量的元素,这些方面总是限制它们的倒数距离。
例如,有一个向量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&#39; one)。
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)偏离我需要的人工制品。尽管如此,能够强制执行特定的最终头寸分配会很有趣。
答案 0 :(得分:3)
如果您想避免命中和未命中方法,则必须将问题转换为距离采样,并限制距离总和。
基本上我如何翻译你想要的东西:你采样的N个位置相当于N + 1距离,从最小距离到你的矢量大小 - N * mindist(所有样本被打包在一起的情况)。然后,您需要将距离的总和约束为1000(向量的大小)。
在这种情况下,解决方案将使用Surrogate包中的Surrogate :: RandVec(参见Random sampling to give an exact sum),它允许采用固定总和进行采样。
library(Surrogate)
a <- seq(1,1000)
mind <- 15
N <- 20
dist <- Surrogate::RandVec(a=mind, b=(1000-(N)*mind), s=1000, n=(N+1), m=1, Seed=sample(1:1000, size = 1))$RandVecOutput
pos <- cumsum(round(dist))[1:20]
pos
> pos
[1] 22 59 76 128 204 239 289 340 389 440 489 546 567 607 724 773 808 843 883 927
dist
是距离的采样。您可以通过计算距离的总和来重建您的位置。它会为您提供pos
,即索引位置的向量。
优势在于您可以获得任何价值,并且您的抽样应该是随机的。对于我不知道的速度部分,您需要与您的大数据案例的方法进行比较。
这是1000次尝试的直方图:
答案 1 :(得分:2)
我认为最好的解决方案,在某种意义上保证随机性(我不确定是什么意义!)可能是:
所以:
min_dist <- 15
a <- seq(1, 1000)
picked <- integer(20)
copy <- a
for (i in 1:20) {
stopifnot(length(copy) > 0)
picked[i] <- sample(copy, 1)
copy <- copy[ abs(copy - picked[i]) >= min_dist ]
}
这是否比采样和拒绝更快可能取决于原始矢量的特征。此外,正如您所看到的,您无法保证能够获得所需的所有元素,但在特定情况下不会出现问题,因为宽度为30的19个区间永远不会覆盖整个{{1} }。