【问题标题】:Geocode batch addresses in R with open mapquestapi使用打开的 mapquestapi 在 R 中对批处理地址进行地理编码
【发布时间】:2015-04-25 04:51:07
【问题描述】:

目标:使用R,通过open.mapquestapi获取地址向量的经纬度数据

出发点:由于ggmap 包中的geocode 每天只能查询2500 个,我需要找到不同的方式(我的data.frame 包含9M 条目)。数据科学工具包不是一个选项,因为我的大部分地址都位于英国/美国以外。我使用 open.mapquestapi 在http://rpubs.com/jvoorheis/Micro_Group_Rpres 上找到了这个出色的 sn-p。

geocode_attempt <- function(address) {
    URL2 = paste("http://open.mapquestapi.com/geocoding/v1/address?key=", "Fmjtd%7Cluub2huanl%2C20%3Do5-9uzwdz", 
        "&location=", address, "&outFormat='json'", "boundingBox=24,-85,50,-125", 
        sep = "")
    # print(URL2)
    URL2 <- gsub(" ", "+", URL2)
    x = getURL(URL2)
    x1 <- fromJSON(x)
    if (length(x1$results[[1]]$locations) == 0) {
        return(NA)
    } else {
        return(c(x1$results[[1]]$locations[[1]]$displayLatLng$lat, x1$results[[1]]$locations[[1]]$displayLatLng$lng))
    }
}
geocode_attempt("1241 Kincaid St, Eugene,OR")

我们需要这些库:

library(RCurl)
library(rjson)
library(dplyr)

让我们创建一个带有 5 个地址的模型 data.frame。

id <- c(seq(1:5))
street <- c("Alexanderplatz 10", "Friedrichstr 102", "Hauptstr 42", "Bruesseler Platz 2", "Aachener Str 324")
postcode <- c("10178","10117", "31737", "50672", "50931")
city <- c(rep("Berlin", 2), "Rinteln", rep("Koeln",2))
country <- c(rep("DE", 5))

df <- data.frame(id, street, postcode, city, country

要向 data.frame 添加纬度 lat 和经度 lon 变量,我们可以使用 for-Loop。我将展示代码,只是为了证明该功能在原则上有效。

for(i in 1:5){
  df$lat[i] <- geocode_attempt(paste(df$street[i], df$postcode[i], df$city[i], df$country[i], sep=","))[1]
  df$lon[i] <- geocode_attempt(paste(df$street[i], df$postcode[i], df$city[i], df$country[i], sep=","))[2]
}

从性能的角度来看,这段代码非常糟糕。即使对于这个小的 data.frame,我的计算机也花了大约 9 秒,很可能是由于 web 服务查询,但没关系。所以我可以在我的 9M 行上运行这段代码,但是时间会非常长。

我的尝试是利用 dplyr 包中的 mutate 函数。 这是我尝试过的:

df %>%
  mutate(lat = geocode_attempt(paste(street, postcode, city, country, sep=","))[1],
        lon = geocode_attempt(paste(street, postcode, city, country, sep=","))[2])

system.time 仅在 2.3 秒内停止。还不错。但是问题来了:

 id             street postcode    city country      lat      lon
1  1  Alexanderplatz 10    10178  Berlin      DE 52.52194 13.41348
2  2   Friedrichstr 102    10117  Berlin      DE 52.52194 13.41348
3  3        Hauptstr 42    31737 Rinteln      DE 52.52194 13.41348
4  4 Bruesseler Platz 2    50672   Koeln      DE 52.52194 13.41348
5  5   Aachener Str 324    50931   Koeln      DE 52.52194 13.41348

latlon 对于所有条目都完全相同。据我了解,mutate 函数按行工作。但在这里,lat 和 lon 是从第一行计算出来的。因此,第一行是正确的。有谁知道为什么?我提供的代码是完整的。没有额外加载。有任何想法吗?如果您有一个高效的替代方法,而不是优化我的代码,我也将不胜感激。

【问题讨论】:

  • @NicE 提供的查询如何最终为您的 9M 行工作?您是否能够在相对较短的时间内对所有实例进行地理编码,或者您是否遇到了 MapQuest 的限制?

标签: r google-maps geocoding openstreetmap


【解决方案1】:

您可能需要矢量化您的 geocode_attempt 函数以按列进行:

vecGeoCode<-Vectorize(geocode_attempt,vectorize.args = c('address'))

然后调用:

df %>%
        mutate(lat = vecGeoCode(paste(street, postcode, city, country, sep=","))[1,],
               lon =vecGeoCode(paste(street, postcode, city, country, sep=","))[2,])

为了加快速度,您可能需要查看 API 的批处理模式,以便一次获得多达 100 个经纬度。

要使用 API 的批处理请求,您可以使用此函数:

geocodeBatch_attempt <- function(address) {
  #URL for batch requests
  URL=paste("http://open.mapquestapi.com/geocoding/v1/batch?key=", "Fmjtd%7Cluub2huanl%2C20%3Do5-9uzwdz", 
             "&location=", paste(address,collapse="&location="),sep = "") 

  URL <- gsub(" ", "+", URL)
  data<-getURL(URL)
  data <- fromJSON(data)

  p<-sapply(data$results,function(x){
    if(length(x$locations)==0){
      c(NA,NA)
    } else{
      c(x$locations[[1]]$displayLatLng$lat, x$locations[[1]]$displayLatLng$lng)   
    }})
  return(t(p))
}

测试它:

#make a bigger df from the data (repeat the 5 lines 25 times)
biggerDf<-df[rep(row.names(df), 25), ]

#add a reqId column to split the data in batches of 100 requests 
biggerDf$reqId<-seq_along(biggerDf$id)%/%100

#run the function, first grouping by reqId to send batches of 100 requests
biggerDf %>%
  group_by(reqId) %>%
  mutate(lat = geocodeBatch_attempt(paste(street, postcode, city, country, sep=","))[,1],
         lon =geocodeBatch_attempt(paste(street, postcode, city, country, sep=","))[,2])

【讨论】:

  • 我需要如何更改功能?我猜只是更改 URL2 是行不通的 :) 向量化函数可以工作,并且比 group_byrowwise 选项略快
  • 是的,我编辑了我的答案以添加可以处理批处理请求的 geocode_attempt 的修改版本。在 125 行上,它的速度大约是原来的两倍。
  • 像魅力一样工作......因为它是一个 API 调用,它不是很快,但可以工作。有人知道 mapquest 的查询限制是多少吗?
【解决方案2】:

查看mutate() 并得出结论非常容易,即正在发生的事情与您在 for 循环中说明的内容相似 - 但您实际上看到的只是一个 vectorized R 函数,它作用于数据框的整个列。

如果其他人有这种误解,我不会感到惊讶 - dplyr 教程没有解决矢量化/非矢量化函数之间的区别,并且(甚至更危险)R 的 recycling 规则意味着应用标量函数不会不一定会引发错误。对此here 进行了更多讨论。

一种选择是重写您的geocode_attempt,以便它可以获取地址向量。

如果您想保持功能不变,但希望 dplyr 表现得更像 -ply family 中的某些东西,您有两种可能的方法:

首先是使用数据中的分组变量:

df %>%
  group_by(id) %>%
  mutate(
    lat = geocode_attempt(paste(street, postcode, city, country, sep=","))[1],
    lon = geocode_attempt(paste(street, postcode, city, country, sep=","))[2])

第二种是使用this答案中描述的rowwise()函数。

df %>%
  rowwise() %>%
  mutate(
    lat = geocode_attempt(paste(street, postcode, city, country, sep=","))[1],
    lon = geocode_attempt(paste(street, postcode, city, country, sep=","))[2])

group_by 解决方案在我的机器上明显更快。不知道为什么!

不幸的是,您从上面的 dplyr 看到的速度节省可能有点虚幻 - 很可能是地理编码函数只被调用一次的结果(而不是循环中的每行一次)。可能会有收获,但您需要再次运行计时。

【讨论】:

    【解决方案3】:

    有一个geocoding package 使用诺基亚 HERE 服务。它具有批处理模式。您可以将它与测试 API 密钥一起使用,并且可能不会达到限制。值得一看……

    【讨论】:

    • geocodeHERE 的批量限制是多少?当我假设您是开发人员时,我是否正确?
    • 限制是 10K,但他们允许使用他们的默认密钥似乎没有限制。是的,它只是 API 的一个简单包装器。批处理功能还是不错的。
    猜你喜欢
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    • 2012-11-06
    • 2016-11-07
    • 1970-01-01
    相关资源
    最近更新 更多