R - 查找最近的相邻点和给定半径内的邻居数,坐标为lat-long

时间:2014-02-24 02:11:47

标签: r distance latitude-longitude

我想弄清楚我的数据集中某些点是如何孤立的。我使用两种方法来确定隔离,最近邻居的距离和给定半径内的相邻站点的数量。我所有的坐标都是纬度和经度

这就是我的数据:

    pond            lat         long        area    canopy  avg.depth   neighbor    n.lat   n.long  n.distance  n.area  n.canopy    n.depth n.avg.depth radius1500
    A10             41.95928    -72.14605   1500    66      60.61538462                                 
    AA006           41.96431    -72.121     250     0       57.77777778                                 
    Blacksmith      41.95508    -72.123803  361     77      71.3125                                 
    Borrow.Pit.1    41.95601    -72.15419   0       0       41.44444444                                 
    Borrow.Pit.2    41.95571    -72.15413   0       0       37.7                                    
    Borrow.Pit.3    41.95546    -72.15375   0       0       29.22222222                                 
    Boulder         41.918223   -72.14978   1392    98      43.53333333                                 

我想把最近的邻近池塘的名称放在邻居列中,它的纬度和长度在n.lat和n.long,两个池塘之间的距离为n.distance,以及区域,冠层和平均值.depth在每个适当的列中。

其次,我想把目标池塘1500米范围内的池塘数量调整到半径1500.

有没有人知道有助于我计算我想要的距离/数字的功能或包?如果这是一个问题,输入我需要的其他数据并不困难,但最近邻居的名字和距离加上1500米以内的池塘数量是我真正需要帮助的。

谢谢。

6 个答案:

答案 0 :(得分:35)

最佳选择是使用库sprgeos,这使您可以构建空间类并执行地理处理。

library(sp)
library(rgeos)

读取数据并将其转换为空间对象:

mydata <- read.delim('d:/temp/testfile.txt', header=T)

sp.mydata <- mydata
coordinates(sp.mydata) <- ~long+lat

class(sp.mydata)
[1] "SpatialPointsDataFrame"
attr(,"package")
[1] "sp"

现在计算点之间的成对距离

d <- gDistance(sp.mydata, byid=T)

找到第二个最短距离(最近距离指向自身,因此使用第二个最短距离)

min.d <- apply(d, 1, function(x) order(x, decreasing=F)[2])

使用所需变量构建新数据框

newdata <- cbind(mydata, mydata[min.d,], apply(d, 1, function(x) sort(x, decreasing=F)[2]))

colnames(newdata) <- c(colnames(mydata), 'neighbor', 'n.lat', 'n.long', 'n.area', 'n.canopy', 'n.avg.depth', 'distance')

newdata
            pond      lat      long area canopy avg.depth     neighbor    n.lat    n.long n.area n.canopy n.avg.depth
6            A10 41.95928 -72.14605 1500     66  60.61538 Borrow.Pit.3 41.95546 -72.15375      0        0    29.22222
3          AA006 41.96431 -72.12100  250      0  57.77778   Blacksmith 41.95508 -72.12380    361       77    71.31250
2     Blacksmith 41.95508 -72.12380  361     77  71.31250        AA006 41.96431 -72.12100    250        0    57.77778
5   Borrow.Pit.1 41.95601 -72.15419    0      0  41.44444 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000
4   Borrow.Pit.2 41.95571 -72.15413    0      0  37.70000 Borrow.Pit.1 41.95601 -72.15419      0        0    41.44444
5.1 Borrow.Pit.3 41.95546 -72.15375    0      0  29.22222 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000
6.1      Boulder 41.91822 -72.14978 1392     98  43.53333 Borrow.Pit.3 41.95546 -72.15375      0        0    29.22222
        distance
6   0.0085954872
3   0.0096462277
2   0.0096462277
5   0.0003059412
4   0.0003059412
5.1 0.0004548626
6.1 0.0374480316

编辑:如果坐标为度,并且您想以千米为单位计算距离,请使用包geosphere

library(geosphere)

d <- distm(sp.mydata)

# rest is the same

如果点遍布地球并且坐标为度

,则应该提供更好的结果

答案 1 :(得分:1)

由@Zbynek提出的解决方案是相当不错的,但如果你像我一样寻找两公里之间的距离,我建议这个解决方案。

   earth.dist<-function(lat1,long1,lat2,long2){

           rad <- pi/180
           a1 <- lat1 * rad
           a2 <- long1 * rad
           b1 <- lat2 * rad
           b2 <- long2 * rad
           dlat <- b1-a1
           dlon<- b2-a2
           a <- (sin(dlat/2))^2 +cos(a1)*cos(b1)*(sin(dlon/2))^2
           c <- 2*atan2(sqrt(a),sqrt(1-a))
           R <- 6378.145
           dist <- R *c
           return(dist)
           }


    Dist <- matrix(0,ncol=length(mydata),nrow=length(mydata.sp))

  for (i in 1:length(mydata)){
      for(j in 1:length(mydata.sp)){
          Dist[i,j] <- earth.dist(mydata$lat[i],mydata$long[i],mydata.sp$lat[j],mydata.sp$long[j])
 }}



     DDD <- matrix(0, ncol=5,nrow=ncol(Dist))   ### RECTIFY the nb of col by the number of variable you want

   for(i in 1:ncol(Dist)){
       sub<- sort(Dist[,i])[2]
       DDD[i,1] <- names(sub) 
       DDD[i,2] <- sub
       DDD[i,3] <- rownames(Dist)[i]
       sub_neig_atr <- Coord[Coord$ID==names(sub),]
       DDD[i,4] <- sub_neig_atr$area
       DDD[i,5] <- sub_neig_atr$canopy
       ### Your can add any variable you want here 

   }

    DDD <- as.data.frame(DDD)

    names(DDD)<-c("neigboor_ID","distance","pond","n.area","n.canopy")
   data <- merge(mydata,DDD, by="pond")

如果您的坐标很长且纬度很近,您最终会得到以km为单位的距离。

有什么建议让它更好吗?

答案 2 :(得分:1)

我在下面添加了使用较新的sf软件包的替代解决方案,以供有兴趣并立即访问此页面的人员使用(与我一样)。

首先,加载数据并创建sf对象。

# Using sf
mydata <- structure(
  list(pond = c("A10", "AA006", "Blacksmith", "Borrow.Pit.1", 
                "Borrow.Pit.2", "Borrow.Pit.3", "Boulder"), 
       lat = c(41.95928, 41.96431, 41.95508, 41.95601, 41.95571, 41.95546, 
               41.918223), 
       long = c(-72.14605, -72.121, -72.123803, -72.15419, -72.15413, 
                -72.15375, -72.14978), 
       area = c(1500L, 250L, 361L, 0L, 0L, 0L, 1392L), 
       canopy = c(66L, 0L, 77L, 0L, 0L, 0L, 98L), 
       avg.depth = c(60.61538462, 57.77777778, 71.3125, 41.44444444, 
                     37.7, 29.22222222, 43.53333333)), 
  class = "data.frame", row.names = c(NA, -7L))


library(sf)
data_sf <- st_as_sf(mydata, coords = c("long", "lat"),
                    # Change to your CRS
                    crs = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
st_is_longlat(data_sf)

sf::st_distance使用纬度/经度数据时,使用大圆距离计算以米为单位的距离矩阵。

dist.mat <- st_distance(data_sf) # Great Circle distance since in lat/lon
# Number within 1.5km: Subtract 1 to exclude the point itself
num.1500 <- apply(dist.mat, 1, function(x) {
  sum(x < 1500) - 1
})

# Calculate nearest distance
nn.dist <- apply(dist.mat, 1, function(x) {
  return(sort(x, partial = 2)[2])
})
# Get index for nearest distance
nn.index <- apply(dist.mat, 1, function(x) { order(x, decreasing=F)[2] })

n.data <- mydata
colnames(n.data)[1] <- "neighbor"
colnames(n.data)[2:ncol(n.data)] <- 
  paste0("n.", colnames(n.data)[2:ncol(n.data)])
mydata2 <- data.frame(mydata,
                      n.data[nn.index, ],
                      n.distance = nn.dist,
                      radius1500 = num.1500)
rownames(mydata2) <- seq(nrow(mydata2))
mydata2
          pond      lat      long area canopy avg.depth     neighbor    n.lat    n.long n.area n.canopy
1          A10 41.95928 -72.14605 1500     66  60.61538 Borrow.Pit.1 41.95601 -72.15419      0        0
2        AA006 41.96431 -72.12100  250      0  57.77778   Blacksmith 41.95508 -72.12380    361       77
3   Blacksmith 41.95508 -72.12380  361     77  71.31250        AA006 41.96431 -72.12100    250        0
4 Borrow.Pit.1 41.95601 -72.15419    0      0  41.44444 Borrow.Pit.2 41.95571 -72.15413      0        0
5 Borrow.Pit.2 41.95571 -72.15413    0      0  37.70000 Borrow.Pit.1 41.95601 -72.15419      0        0
6 Borrow.Pit.3 41.95546 -72.15375    0      0  29.22222 Borrow.Pit.2 41.95571 -72.15413      0        0
7      Boulder 41.91822 -72.14978 1392     98  43.53333 Borrow.Pit.3 41.95546 -72.15375      0        0
  n.avg.depth n.distance radius1500
1    41.44444  766.38426          3
2    71.31250 1051.20527          1
3    57.77778 1051.20527          1
4    37.70000   33.69099          3
5    41.44444   33.69099          3
6    37.70000   41.99576          3
7    29.22222 4149.07406          0

要在计算距离后获得最近的邻居,可以将sort()partial = 2参数一起使用。根据数据量的不同,这可能比以前的解决方案中使用order的速度要快得多。软件包Rfast可能更快,但我避免在此处包括其他软件包。有关各种解决方案的讨论和基准测试,请参见此相关文章:https://stackoverflow.com/a/53144760/12265198

答案 3 :(得分:0)

我在下面添加了使用spatialrisk软件包的解决方案。此软件包中的关键功能是用C ++(Rcpp)编写的,因此非常快。

首先,加载数据:

df <- data.frame(pond = c("A10", "AA006", "Blacksmith", "Borrow.Pit.1", 
                          "Borrow.Pit.2", "Borrow.Pit.3", "Boulder"), 
                 lat = c(41.95928, 41.96431, 41.95508, 41.95601, 
                         41.95571, 41.95546, 41.918223), 
                 long = c(-72.14605, -72.121, -72.123803, -72.15419, 
                          -72.15413, -72.15375, -72.14978), 
                 area = c(1500, 250, 361, 0, 0, 0, 1392), 
                 canopy = c(66, 0, 77, 0, 0, 0, 98), 
                 avg.depth = c(60.61538462, 57.77777778, 71.3125, 41.44444444,
                               37.7, 29.22222222, 43.53333333))

spatialrisk :: points_in_circle()函数计算从中心点开始的半径范围内的观测值。请注意,距离是使用Haversine公式计算的。由于输出的每个元素都是一个数据帧,因此使用purrr :: map_dfr将它们行绑定在一起:

ans1 <- purrr::map2_dfr(df$long, df$lat, 
                        ~spatialrisk::points_in_circle(df, .x, .y, 
                                                       lon = long, 
                                                       radius = 100000)[2,])

colnames(ans1) <- c("neighbor", "n.lat", "n.long", "n.area", 
                    "n.canopy", "n.avg.depth", "distance_m")

      neighbor    n.lat    n.long n.area n.canopy n.avg.depth distance_m
1 Borrow.Pit.1 41.95601 -72.15419      0        0    41.44444  765.87823
2   Blacksmith 41.95508 -72.12380    361       77    71.31250 1053.35200
3        AA006 41.96431 -72.12100    250        0    57.77778 1053.35200
4 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000   33.76321
5 Borrow.Pit.1 41.95601 -72.15419      0        0    41.44444   33.76321
6 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000   42.00128
7 Borrow.Pit.3 41.95546 -72.15375      0        0    29.22222 4158.21978

现在计算距离目标池塘1500m以内的池塘数量。空间风险::浓度()函数对从中心点开始的半径范围内的观测值求和。从池塘数量中减去1排除池塘本身。

df$npond <- 1  
radius1500 <- spatialrisk::concentration(df, df, npond, lon_sub = long, 
                                         lon_full = long, radius = 1500, 
                                         display_progress = FALSE)$concentration - 1

将数据帧绑定在一起:

cbind(df, ans1, radius1500)

          pond      lat      long area canopy avg.depth     neighbor    n.lat    n.long n.area n.canopy n.avg.depth distance_m radius1500
1          A10 41.95928 -72.14605 1500     66  60.61538 Borrow.Pit.1 41.95601 -72.15419      0        0    41.44444  765.87823          3
2        AA006 41.96431 -72.12100  250      0  57.77778   Blacksmith 41.95508 -72.12380    361       77    71.31250 1053.35200          1
3   Blacksmith 41.95508 -72.12380  361     77  71.31250        AA006 41.96431 -72.12100    250        0    57.77778 1053.35200          1
4 Borrow.Pit.1 41.95601 -72.15419    0      0  41.44444 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000   33.76321          3
5 Borrow.Pit.2 41.95571 -72.15413    0      0  37.70000 Borrow.Pit.1 41.95601 -72.15419      0        0    41.44444   33.76321          3
6 Borrow.Pit.3 41.95546 -72.15375    0      0  29.22222 Borrow.Pit.2 41.95571 -72.15413      0        0    37.70000   42.00128          3
7      Boulder 41.91822 -72.14978 1392     98  43.53333 Borrow.Pit.3 41.95546 -72.15375      0        0    29.22222 4158.21978          0

答案 4 :(得分:0)

Rfast 中,有一个名为“ dista” 的函数,该函数仅计算当前的欧几里得距离或曼哈顿距离。它提供了计算k个最小距离的选项。或者,它可以返回具有最小距离的观测索引。余弦距离与欧几里得距离基本相同(我认为不包括常数2)。

答案 5 :(得分:0)

另一个答案虽然可能较慢,但可能对dplyr上瘾者有直观的吸引力。

您可以为纬度/经度的每种可能组合创建一个巨型网格,然后使用地理球找到距离最小的网格。

在示例中,您有两个具有不同点的数据集进行比较-但是您可以通过复制第一个数据集轻松地对其进行调整。

    library(tidyverse)
    library(geosphere)
    library(data.table)

    #This function creates a big dataframe with every possible combination
    expand.grid.df <- function(...) Reduce(function(...) merge(..., by=NULL), list(...))

shortest_distance <- expand.grid.df(df1,df2) %>%
      mutate(distance = distHaversine(p1 = cbind(lon_2,lat_2),
                                      p2 = cbind(lon,lat))) %>% 
      group_by(ACCIDENT_NO) %>% 
      slice(which.min(distance))