如何计算R中某个阈值以下2个坐标之间的距离?

时间:2016-04-18 05:49:30

标签: r algorithm performance geolocation distance

我有44,000个美国邮政编码,它在R中对应的质心lat / long。这是来自R中的'zipcode'包。 我需要计算每个邮政编码之间的距离,并保持距离小于5英里。问题是计算我必须创建一个大小为44,000x44,0000的矢量的zipcodes之间的所有距离,由于空间问题,我不能这样做。

我查看了R中的帖子,最接近我的要求的是一个吐出2个数据集与lat / long之间的最小距离

DB1 <- data.frame(location_id=1:7000,LATITUDE=runif(7000,min = -90,max = 90),LONGITUDE=runif(7000,min = -180,max = 180))
DB2 <- data.frame(location_id=7001:12000,LATITUDE=runif(5000,min = -90,max = 90),LONGITUDE=runif(5000,min = -180,max = 180))

DistFun <- function(ID){
  TMP <- DB1[DB1$location_id==ID,]
  TMP1 <- distGeo(TMP[,3:2],DB2[,3:2])
  TMP2 <- data.frame(DB1ID=ID,DB2ID=DB2[which.min(TMP1),1],DistanceBetween=min(TMP1)      ) 
  print(ID)
  return(TMP2)
}

DistanceMatrix <- rbind_all(lapply(DB1$location_id, DistFun))

即使我们可以修改上述代码以包含所有距离&lt; = 5英里(例如),但执行速度极慢。

是否有一种有效的方法来获得距离彼此质心<= 5英里的所有邮政编码组合?

3 个答案:

答案 0 :(得分:4)

一次生成整个距离矩阵将非常消耗RAM,循环遍历每个独特的zipcodes组合 - 非常耗时。让我们找到一些妥协。

我建议将zipcode data.frame分成几行(例如)100行(在chunk函数的bit函数的帮助下),然后计算两者之间的距离44336和100点,根据目标距离阈值进行过滤,然后继续前进到下一个数据块。在我的示例中,我将zipcode数据转换为data.table以获得一定的速度并节省RAM。

library(zipcode)
library(data.table)
library(magrittr)
library(geosphere)

data(zipcode)

setDT(zipcode)
zipcode[, dum := NA] # we'll need it for full outer join

仅供参考 - 这是RAM中每个数据的大致大小。

merge(zipcode, zipcode[1:100], by = "dum", allow.cartesian = T) %>% 
  object.size() %>% print(unit = "Mb")
# 358.2 Mb

代码本身。

lapply(bit::chunk(1, nrow(zipcode), 1e2), function(ridx) {
  merge(zipcode, zipcode[ridx[1]:ridx[2]], by = "dum", allow.cartesian = T)[
    , dist := distGeo(matrix(c(longitude.x, latitude.x), ncol = 2), 
                      matrix(c(longitude.y, latitude.y), ncol = 2))/1609.34 # meters to miles
    ][dist <= 5 # necessary distance treshold
      ][, dum := NULL]
  }) %>% rbindlist -> zip_nearby_dt

zip_nearby_dt # not the whole! for first 10 chunks only

       zip.x          city.x state.x latitude.x longitude.x zip.y     city.y state.y latitude.y longitude.y     dist
    1: 00210      Portsmouth      NH   43.00590   -71.01320 00210 Portsmouth      NH   43.00590   -71.01320 0.000000
    2: 00210      Portsmouth      NH   43.00590   -71.01320 00211 Portsmouth      NH   43.00590   -71.01320 0.000000
    3: 00210      Portsmouth      NH   43.00590   -71.01320 00212 Portsmouth      NH   43.00590   -71.01320 0.000000
    4: 00210      Portsmouth      NH   43.00590   -71.01320 00213 Portsmouth      NH   43.00590   -71.01320 0.000000
    5: 00210      Portsmouth      NH   43.00590   -71.01320 00214 Portsmouth      NH   43.00590   -71.01320 0.000000
---                                                                                                              
15252: 02906      Providence      RI   41.83635   -71.39427 02771    Seekonk      MA   41.84345   -71.32343 3.688747
15253: 02912      Providence      RI   41.82674   -71.39770 02771    Seekonk      MA   41.84345   -71.32343 4.003095
15254: 02914 East Providence      RI   41.81240   -71.36834 02771    Seekonk      MA   41.84345   -71.32343 3.156966
15255: 02916         Rumford      RI   41.84325   -71.35391 02769   Rehoboth      MA   41.83507   -71.26115 4.820599
15256: 02916         Rumford      RI   41.84325   -71.35391 02771    Seekonk      MA   41.84345   -71.32343 1.573050

在我的机器上花了1.7分钟处理10个块,因此整个处理可能需要70-80分钟,而不是很快,但可能会令人满意。我们可以根据可用的RAM容量将块大小增加到200或300行,这将分别缩短处理时间2到3次。

此解决方案的缺点是生成的data.table包含“重复”行 - 我的意思是从A点到B点,从B到A都有距离。这可能需要一些额外的过滤。 / p>

答案 1 :(得分:0)

我想最有效的算法首先会将空间位置转换为树状数据结构。你不需要明确地这样做,如果你有一个算法可以1)将lat / long分成空间索引,2)告诉你那个索引的邻居,那么你可以用它来过滤你的方形数据。 (这比构建树的效率低,但可能更容易实现。)

geohash就是这样一种算法。它将连续纬度/长度变为2-d箱。有一个(非常新的)包providing geohash in R。以下是关于如何将其用于解决此问题的一个想法:

首先,使用geohash进行一些初步校准

  1. 将lat / long转换为具有bin精度的哈希p(比如说​​)

  2. 评估散列是否以与您感兴趣的距离相似的精度进行校准(例如,相邻质心之间的距离为3-7英里),如果不是返回 1 并调整精度p

  3. 这会产生邮政编码 - 哈希值关系。

    然后,计算每个(唯一)哈希值的距离

    1. 确定其(8,bc哈希形成2-d网格)最近邻居,因此选择9个哈希值

    2. 计算9个哈希中所有拉链之间的成对距离(使用例如问题中的distGeo

    3. 返回哈希值的所有zip-zip成对距离(例如,在矩阵中)

    4. 这会产生哈希值 - zip-zip距离对象关系

      (在步骤 2 中,它显然最适合仅计算每个最近邻对一次。但这可能没有必要。)

      最后,为每个zip

      1. 使用以上两个步骤(通过哈希值作为键)获取zip-zip
        zip的距离对象
      2. 将对象过滤到焦点拉链的距离(回想一下,它是与焦点拉链相邻的一组散列中的所有成对距离)
      3. 仅保留距离< 5 miles
      4. 这会在5英里对象内产生 zip - 拉链。 (焦点拉链5英里范围内的拉链可以存储为一列列表(每个元素是一个列表)存储在一个焦点拉链列旁边的数据框中,或者作为一个单独的列表,其中焦点拉链作为名称)。

答案 2 :(得分:0)

以下是使用 spatialrisk 的解决方案。这些函数是用 C++ 编写的,因此速度非常快。在我的机器上大约需要 25 秒。

library(zipcodeR)
library(spatialrisk)
library(dplyr)

# Zip code data
zipcode <- zipcodeR::zip_code_db

# Radius in meters
radius_meters <- 5000

# Find zipcodes within 5000 meters
sel <- tibble(zipcode) %>%
  select(zipcode, lat, lon = lng) %>%
  filter(!is.na(lat), !is.na(lon)) %>%
  mutate(zipcode_within_radius = purrr::map2(lon, lat, ~points_in_circle(zipcode_sel, .x, .y, radius = radius_meters)[-1,])) %>%
  unnest(cols = c(zipcode_within_radius), names_repair = "unique")