【问题标题】:Find nearest neighbours of polygons in R在R中查找多边形的最近邻
【发布时间】:2020-10-22 22:14:29
【问题描述】:

我有一个数据框,其坐标已转换为 R 中的 sf 对象,如下所示:

> head(df1)
  Cell_ID   Spot_ID       X       Y
1       0 600000000 193.722 175.733
2       0 600000001 192.895 176.727
3       0 600000002 193.828 177.462
4       8 600000003 178.173 178.220
5       7 600000004 187.065 178.285
6       0 600000005 190.754 178.186

> df1_sf <- st_as_sf(df1,
                     coords = c('X', 'Y')) %>%
    group_by(Cell_ID) %>%
    summarise() %>%
    ungroup() %>%  
    st_convex_hull()
>plot(st_geometry(df1_sf), border = "red")

然后我可以绘制我所有的多边形,它看起来像这样:

现在我想获取每个多边形的邻居的 ID。为此,我正在这样做

n = st_set_geometry(st_intersection(df1_sf,df1_sf), NULL)
head(n)
# A tibble: 6 x 2
  Cell_ID Cell_ID.1
    <int>     <int>
1       0         0
2       7         0
3      51         0
4       1         1
5       4         1
6       5         1

但这是一项平庸的工作,因为它需要一个交叉点,而如果它们是最近的交叉点,我也对它们感兴趣(虽然没有像下图中那样接触,但 Cell_ID 1 将具有相邻单元格 3-6 但还将检测到单元格 7,因为它位于给定的半径内)。 谁能帮我解决这个问题?

谢谢!!

【问题讨论】:

  • 您能澄清一下您所说的“邻居”是什么意思吗?在您的示例图片中,多边形 2 是否也应算作邻居?你想包括 7,还是排除 7?介于 6 和 7 之间的多边形呢?
  • 抱歉,多边形 1 的邻居是:2,3,4,5,6,因为 st_intersect 会选择,但我还想添加 7,因为虽然不相交,但它已经接近了半径。
  • this 有兴趣吗?或者可以使用像st_join(x, y, st_is_within_distance, dist = some_distance) 这样的非重叠连接?也是对类似问题的类似答案,但使用sphere
  • 您可以sf::st_buffer() 每个多边形将边界扩展到您所需的“半径”,然后找到所有接触的边界?
  • 这是个好主意。我也想过,但不知道怎么做。现在试试,看起来很有希望!

标签: r spatial sf


【解决方案1】:

为了说明在每个多边形周围使用缓冲区的绝佳建议 (每个多边形的数学膨胀)这是一个快速而肮脏的spatstat 解决方案。

首先加载包并制作一些示例数据:

library(spatstat)
dat <- tiles(dirichlet(cells))
ii <- seq(2, 42, by=2)
dat[ii] <- lapply(dat[ii], erosion, r = .01)
dat <- lapply(seq_along(dat), function(i) cbind(Cell_ID = i, as.data.frame(dat[[i]])))
dat <- Reduce(rbind, dat)
df1 <- cbind(Spot_ID = 1:nrow(dat), dat)
head(df1)
#>   Spot_ID Cell_ID         x         y
#> 1       1       1 0.4067780 0.0819020
#> 2       2       1 0.3216680 0.1129640
#> 3       3       1 0.1967080 0.0000000
#> 4       4       1 0.4438430 0.0000000
#> 5       5       2 0.5630909 0.1146781
#> 6       6       2 0.4916145 0.1649979

对每个Cell_ID进行拆分,找到凸包并绘制数据:

dat <- split(df1[,c("x", "y")], df1$Cell_ID)
dat <- lapply(dat, convexhull)
plot(owin(), main = "")
for(i in seq_along(dat)){
  plot(dat[[i]], add = TRUE, border = "red")
}

扩张每个多边形:

bigdat <- lapply(dat, dilation, r = 0.0125)

创建一个简单的 for 循环来分配哪些扩张的多边形重叠(即完整 n^2 成对的交点):

neigh <- list()
for(i in seq_along(bigdat)){
  overlap <- sapply(bigdat[-i], function(x) !is.empty(intersect.owin(x, bigdat[[i]])))
  neigh[[i]] <- which(overlap)
}

绘制具有邻居数量的膨胀多边形(邻居的 ID 在 列表neigh):

plot(owin(), main = "")
for(i in seq_along(bigdat)){
  plot(bigdat[[i]], add = TRUE, border = "red")
}
text.ppp(cells, labels = sapply(neigh, length))

基于替代曲面细分的解决方案

是否需要使用凸包作为单元格的定义 领域?我很想简单地用质心来表示每个单元格 样本点,然后使用 Dirichlet/Voronoi 细分作为 地区。这些在任何地方都有明确的邻居,唯一的问题是 如何定义细胞集合的边界区域。

对每个Cell_ID进行拆分,找到质心,细分并绘制数据:

dat <- split(df1[,c("x", "y")], df1$Cell_ID)
dat <- t(sapply(dat, colMeans))
X <- as.ppp(dat, W = ripras)
D <- dirichlet(X)
plot(D)

查找邻居 ID 的额外代码:

eps <- sqrt(.Machine$double.eps) # Epsilon for numerical comparison below
tilelist <- tiles(D)
v_list <- lapply(tilelist, vertices.owin)
v_list <- lapply(v_list, function(v){ppp(v$x, v$y, window = Window(X), check = FALSE)})
neigh <- list()
dd <- safedeldir(X)
for(i in seq_len(npoints(X))){
  ## All neighbours from deldir (infinite border tiles)
  all_neigh <- c(dd$delsgs$ind1[dd$delsgs$ind2==i],
                 dd$delsgs$ind2[dd$delsgs$ind1==i])
  ## The remainder keeps only neighbour tiles that share a vertex with tile i:
  true_neigh <- sapply(v_list[all_neigh], function(x){min(nncross.ppp(v_list[[i]], x))}) < eps
  neigh[[i]] <- sort(all_neigh[true_neigh])
}
plot(D, main = "Tessellation with Cell_ID")
text(X)

neigh[[1]] # Neighbours of tile 1
#> [1] 2 7 8
neigh[[10]] # Neighbours of tile 10
#> [1]  3  4  5  9 15 16 20

【讨论】:

  • 谢谢,我只是无法使用我的初始数据重现此问题,该数据是具有 4 列的数据框:Cell_ID、spots_IDs、X、Y、Z
  • 能否提供一些示例数据进行测试?
  • 一段数据:dput(df_80k[1:12,]) structure(list(Cell_ID = c(0L, 0L, 0L, 8L, 7L, 0L, 8L, 0L, 7L, 7L,7L,8L),Spot_id = 600000000:600000011,X = C(193.722,192.895,193.828,178.173,183.065,190.754,176.811,192.296,183.826,187.678,189.421,179.755),Y = C(175.733,176.727 177.462,178.22,178.285,178.186,178.739,178.648,178.79,178.963,179.012,179.347)中,Z = C(0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975,0.0998975)) , row.names = c(NA, 12L), class= "data.frame")
  • 好吧@Amaranta_Remedios,很难用您在评论中提供的三个多边形的数据来说明该过程。相反,我试图让我的人工数据更接近您的数据,因此 df1 的格式与您的格式相似,您应该能够通过小的修改运行代码,例如 X 而不是 x。您如何看待曲面细分解决方案?
  • 谢谢,问题是我意识到我只是选择了数据框中的第一个n,所以点不一定是连接的,谢谢你的帮助。您的解决方案似乎很棒。遇到了一些奇怪的错误Error: Attempted to create binary mask with 6771 * 3836 = 25973556 entries In addition: Warning messages: 1: Window occupies only 0.0031% of frame area. Did you mean owin(poly=df) ? 2: Window occupies only 0.01% of frame area. Did you mean owin(poly=df) ?
【解决方案2】:

从您的问题看来,您似乎对通用 最近邻 类型的方法更感兴趣。如果这过于简单,请纠正我。

您可以简单地获取中心坐标并使用任何knn 类型算法将k nearest neighbours 分类到给定坐标,而不是考虑每个多边形及其边界。

由于我无权访问您的数据,因此我创建了一些虚拟坐标。 使用包RANN和函数nn2see here

install.packages('RANN')
library(RANN)

# Make dummy coordinates
df <- 
  data.frame(   X = runif(100)
              , Y = runif(100)
               )

# Find closest 5 points between df and itself
closest <- nn2(data = df, query = df , k = 5)

closest$nn.idx # Index of Closest neigbours
closest$nn.dists # Euclidean distance of Closest neigbours

# Note the first colum is a reference to itself, so real 5 nearest neighbours (not including itself) would mean you select k = 6.

> head(closest$nn.idx) # Euclidean distance of Closest neigbours
     [,1] [,2] [,3] [,4] [,5]
[1,]    1   82   31   86   49
[2,]    2   22   41   34   91
[3,]    3   96   20   55   32
[4,]    4   65   53   77   14
[5,]    5   38   48   59   30
[6,]    6   36   43   97   61

> head(closest$nn.dists) # Euclidean distance of Closest neigbours
     [,1]       [,2]       [,3]       [,4]       [,5]
[1,]    0 0.04971692 0.06305752 0.08597908 0.09485483
[2,]    0 0.03668956 0.05248395 0.09570358 0.10489092
[3,]    0 0.07257007 0.10263107 0.11204297 0.13275642
[4,]    0 0.07209561 0.07227328 0.07259919 0.07326718
[5,]    0 0.02842711 0.06003873 0.08930219 0.12286905
[6,]    0 0.08018734 0.09312385 0.10844622 0.11368332

您也可以使用searchtype = "radius"radius 根据您的问题中提到的半径方法执行此操作。

【讨论】:

  • 谢谢,这是一个很好的建议,但不幸的是我不能使用中心点,也不能只使用最近的邻居,因为这些点来自生物数据(细胞),我需要找到接触的邻居(接触膜到膜)。这就是为什么我试图将每个单元格视为一个多边形。
  • 啊我明白了,但是当你说“接触膜到膜”时,你想要留出一些余量,因为你希望细胞 7 仍然是邻居,即使它没有接触?
  • 是的,确切地说,我想留出一点边距(例如 2 um),以便该边距内的所有内容都称为邻居。
猜你喜欢
  • 2014-12-17
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 2021-07-20
相关资源
最近更新 更多