找到多个点之间的最短距离

时间:2017-09-20 13:44:24

标签: r coordinates grouping distance euclidean-distance

想象一下xy坐标的小数据集。这些点按名为indexR的变量分组,总共有3组。所有xy坐标都以相同的单位表示。数据看起来大致如下:

# A tibble: 61 x 3
   indexR     x     y
    <dbl> <dbl> <dbl>
 1      1   837   924
 2      1   464   661
 3      1   838   132
 4      1   245   882
 5      1  1161   604
 6      1  1185   504
 7      1   853   870
 8      1  1048   859
 9      1  1044   514
10      1   141   938
# ... with 51 more rows

目标是在最小化所选点之间的成对距离之和的意义上确定哪个3个点(每个组中的一个点)彼此最接近。

我通过考虑欧几里德距离尝试了这一点,如下所示。 (信用转到@Mouad_S,在此主题中,https://gis.stackexchange.com/questions/233373/distance-between-coordinates-in-r

#dput provided at bottom of this post
> df$dummy = 1
> df %>% 
+   full_join(df, c("dummy" = "dummy")) %>% 
+   full_join(df, c("dummy" = "dummy")) %>%
+   filter(indexR.x != indexR.y & indexR.x != indexR & indexR.y != indexR) %>% 
+   mutate(dist = 
+            ((.$x - .$x.x)^2 + (.$y- .$y.x)^2)^.5 +
+            ((.$x - .$x.y)^2 + (.$y- .$y.y)^2)^.5 +
+            ((.$x.x - .$x.y)^2 + (.$y.x- .$y.y)^2)^.5,
+          dist = round(dist, digits = 0)) %>%
+   arrange(dist) %>%
+   filter(dist == min(dist))
# A tibble: 6 x 11
  indexR.x   x.x   y.x dummy indexR.y   x.y   y.y indexR     x     y  dist
     <dbl> <dbl> <dbl> <dbl>    <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
1        1   638   324     1        2   592   250      3   442   513   664
2        1   638   324     1        3   442   513      2   592   250   664
3        2   592   250     1        1   638   324      3   442   513   664
4        2   592   250     1        3   442   513      1   638   324   664
5        3   442   513     1        1   638   324      2   592   250   664
6        3   442   513     1        2   592   250      1   638   324   664

由此我们可以确定最接近的三个点(最小距离;在下图中放大)。然而,当扩展这一点时,挑战就出现了,因为indexR有4,5 ...... n组。问题在于找到一种更实用或优化的方法来进行此计算。

enter image description here

structure(list(indexR = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 
2, 2, 2, 2, 2, 3, 3), x = c(836.65, 464.43, 838.12, 244.68, 1160.86, 
1184.52, 853.4, 1047.96, 1044.2, 141.06, 561.01, 1110.74, 123.4, 
1087.24, 827.83, 100.86, 140.07, 306.5, 267.83, 1118.61, 155.04, 
299.52, 543.5, 782.25, 737.1, 1132.14, 659.48, 871.78, 1035.33, 
867.81, 192.94, 1167.8, 1099.59, 1097.3, 1089.78, 1166.59, 703.33, 
671.64, 346.49, 440.89, 126.38, 638.24, 972.32, 1066.8, 775.68, 
591.86, 818.75, 953.63, 1104.98, 1050.47, 722.43, 1022.17, 986.38, 
1133.01, 914.27, 725.15, 1151.52, 786.08, 1024.83, 246.52, 441.53
), y = c(923.68, 660.97, 131.61, 882.23, 604.09, 504.05, 870.35, 
858.51, 513.5, 937.7, 838.47, 482.69, 473.48, 171.78, 774.99, 
792.46, 251.26, 757.95, 317.71, 401.93, 326.32, 725.89, 98.43, 
414.01, 510.16, 973.61, 445.33, 504.54, 669.87, 598.75, 225.27, 
789.45, 135.31, 935.51, 270.38, 241.19, 595.05, 401.25, 160.98, 
778.86, 192.17, 323.76, 361.08, 444.92, 354, 249.57, 301.64, 
375.75, 440.03, 428.79, 276.5, 408.84, 381.14, 459.14, 370.26, 
304.05, 439.14, 339.91, 435.85, 759.42, 513.37)), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -61L), .Names = c("indexR", 
"x", "y"))

4 个答案:

答案 0 :(得分:1)

一种可能性是制定识别最接近的元素的问题,每个组中一个元素作为混合整数程序。我们可以定义决策变量y_i是否选择了每个点i,以及x_ {ij}是否选择了点i和j(x_ {ij} = y_iy_j)。我们需要从每个组中选择一个元素。

实际上,您可以使用lpSolve包(或其他R优化包之一)实现此混合整数程序。

opt.closest <- function(df) {
  # Compute every pair of indices
  library(dplyr)
  pairs <- as.data.frame(t(combn(nrow(df), 2))) %>%
    mutate(G1=df$indexR[V1], G2=df$indexR[V2]) %>%
    filter(G1 != G2) %>%
    mutate(dist = sqrt((df$x[V1]-df$x[V2])^2+(df$y[V1]-df$y[V2])^2))

  # Compute a few convenience values
  n <- nrow(df)
  nP <- nrow(pairs)
  groups <- sort(unique(df$indexR))
  nG <- length(groups)
  gpairs <- combn(groups, 2)
  nGP <- ncol(gpairs)

  # Solve the optimization problem
  obj <- c(pairs$dist, rep(0, n))
  constr <- rbind(cbind(diag(nP), -outer(pairs$V1, seq_len(n), "==")),
                  cbind(diag(nP), -outer(pairs$V2, seq_len(n), "==")),
                  cbind(diag(nP), -outer(pairs$V1, seq_len(n), "==") - outer(pairs$V2, seq_len(n), "==")),
                  cbind(matrix(0, nG, nP), outer(groups, df$indexR, "==")),
                  cbind((outer(gpairs[1,], pairs$G1, "==") &
                         outer(gpairs[2,], pairs$G2, "==")) |
                        (outer(gpairs[2,], pairs$G1, "==") &
                         outer(gpairs[1,], pairs$G2, "==")), matrix(0, nGP, n)))
  dir <- rep(c("<=", ">=", "="), c(2*nP, nP, nG+nGP))
  rhs <- rep(c(0, -1, 1), c(2*nP, nP, nG+nGP))
  library(lpSolve)
  mod <- lp("min", obj, constr, dir, rhs, all.bin=TRUE)
  which(tail(mod$solution, n) == 1)
}

这可以计算示例数据集中距离每个群集最近的3个点:

df[opt.closest(df),]
# A tibble: 3 x 3
#   indexR      x      y
#    <dbl>  <dbl>  <dbl>
# 1      1 638.24 323.76
# 2      2 591.86 249.57
# 3      3 441.53 513.37

它还可以为具有更多点和组的数据集计算最佳解决方案。以下是数据集的运行时,每个数据集有7个组,100个和200个点:

make.dataset <- function(n, nG) {
  set.seed(144)
  data.frame(indexR = sample(seq_len(nG), n, replace=T), x = rnorm(n), y=rnorm(n))
}
df100 <- make.dataset(100, 7)
system.time(opt.closest(df100))
#    user  system elapsed 
#  11.536   2.656  15.407 
df200 <- make.dataset(200, 7)
system.time(opt.closest(df200))
#    user  system elapsed 
# 187.363  86.454 323.167 

这远非瞬时 - 对于100点,7组数据集需要15秒,对于200点,7组数据集需要323秒。尽管如此,它比迭代100点数据集中的所有9200万7元组或200点数据集中的所有138亿7元组要快得多。您可以使用Rglpk包中的解算器设置运行时限制,以获得在该限制内获得的最佳解决方案。

答案 1 :(得分:1)

你无法承担所有可能的解决方案,我没有看到任何明显的捷径。

所以我想你必须做一个分支和绑定优化方法。

首先猜测一个相当不错的解决方案。就像最近的两个不同标签的点。然后使用不同的标签添加最近的标签,直到覆盖所有标签。

现在做一些简单的优化:对于每个标签,尝试是否有一些点可以使用而不是当前点来改善结果。当你找不到任何进一步的改进时停止。

对于这个初始猜测,计算距离。这将为您提供上限,允许您提前停止搜索。您还可以计算下限,即所有最佳双标签解决方案的总和。

现在您可以尝试删除点,其中每个标签的最近邻居+所有其他标签的下限已经比您的初始解决方案更差。这有望消除很多观点。

然后你可以开始枚举解决方案(可能首先从最小的标签开始),但只要当前解决方案+剩余的下限大于你最熟知的解决方案(分支和绑定),就停止递归。

您还可以尝试对点进行排序,例如通过与剩余标签的最小距离,希望能够快速找到更好的界限。

我当然不会选择R来实现这个......

答案 2 :(得分:0)

你可以使用交叉连接来获得所有的点组合,计算所有三个点之间的总距离,然后取最小值。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<select name="number" id="paragraphSpaceOPtion">
    <option data-class="option-no-00" value="00">00</option>
    <option data-class="option-no-05" value="05">05</option>
    <option data-class="option-no-07" value="07">07</option>
    <option data-class="option-no-10" value="10">10</option>
    <option data-class="option-no-15" value="15">15</option>
    <option data-class="option-no-20" value="20">20</option>
</select>

<div class="content">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus, in!</p>
</div>

答案 3 :(得分:0)

我开发了一种简单的算法来快速解决此问题。第一步是在点的整个区域上覆盖一个网格。第一步是将每个组中的每个点分配给它所在的单元格或单位正方形。接下来,我们转到图表的左下角,然后越过一个单元格并向上移动一个单元格。这是起始单元格。然后,我们定义一个由该单元格及其所有8个邻居组成的感兴趣区域。然后进行测试以确定来自每个组的至少一个点是否在该9个细胞区域内。如果是这样,则计算从该区域中表示的每个点组到每个其他点的距离。换句话说,此9单元区域中所有点的组合都用于获得总距离,其中用于距离计算的成对点永远不会来自同一组。从这些计算中,将具有最小距离的一组(每个组中的单个点)保存为可能的解决方案。然后,通过向右移动一个单元格来重复整个过程。当中央单元向右移动时,将计算每个9单元区域。这是从右端停止一个单元格。当第一行完成时,该过程将继续进行,直到上一行再从左侧开始,但又是一个单元格。因此,当第一行结束时,已经考虑了每个单元格。解决方案将是针对每个9细胞区域进行的所有测试所计算出的最小距离。

我们之所以考虑9个单元的区域而不是逐个单元,是因为我们可能会错过位于单元角的不同组中间隔很近的点。

选择正确的像元或网格大小很重要。如果单元格太小,则将找不到可能的解决方案,因为每个区域都不会包含至少一个点。如果像元太大,则每个组中会有很多点,并且计算时间将过多。幸运的是,可以通过反复试验快速找到最佳的像元大小。

我已经多次使用不同数量的组和组中的点数来运行此算法。对于所有组中的随机散布点,我发现15 x 15的网格大小对于10组-400点(每组40点)的情况效果很好。该示例运行时间不到一秒钟。

相关问题