R中的地理编码批处理地址使用open mapquestapi

Dan*_*ltz 11 google-maps geocoding r openstreetmap

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

出发点:由于geocodeggmap一天开始限制为2500个查询,我需要找到一种不同的方式(我的data.frame由9M条目组成).数据科学工具包不是一种选择,因为我的大多数地址都在英国/美国之外.我在http://rpubs.com/jvoorheis/Micro_Group_Rpres上使用open.mapquestapi 找到了这个优秀的片段.

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")
Run Code Online (Sandbox Code Playgroud)

我们需要这些库:

library(RCurl)
library(rjson)
library(dplyr)
Run Code Online (Sandbox Code Playgroud)

让我们创建一个包含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
Run Code Online (Sandbox Code Playgroud)

对于向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]
}
Run Code Online (Sandbox Code Playgroud)

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

我的尝试是利用包中的mutate功能dplyr.这是我尝试过的:

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

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
Run Code Online (Sandbox Code Playgroud)

lat并且lon所有条目都完全相同.根据我的理解,该mutate功能正在按行运行.但是在这里,lat和lon是从第一行计算出来的.因此,第一行是正确的.有谁知道为什么?我提供的代码已经完成.没有额外的装载.有任何想法吗?如果您有一种高效的替代方式而不是优化我的代码,我将不胜感激.

Nic*_*icE 10

您可能需要对geocode_attempt函数进行矢量化以按列方式执行:

vecGeoCode<-Vectorize(geocode_attempt,vectorize.args = c('address'))
Run Code Online (Sandbox Code Playgroud)

然后打电话:

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

为了加快速度,您可能需要查看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))
}
Run Code Online (Sandbox Code Playgroud)

测试它:

#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])
Run Code Online (Sandbox Code Playgroud)


And*_*rew 4

很容易查看mutate()并得出结论,即发生的情况与您在 for 循环中说明的情况类似 - 但您实际上看到的只是一个矢量化R 函数,它作用于数据帧的整个列。

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

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

如果你想保持你的功能不变,但希望 dplyr 的行为更像-ply 系列的东西,你有两种可能的方法:

第一个是使用数据中的分组变量:

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])
Run Code Online (Sandbox Code Playgroud)

第二种是使用这个rowwise()答案中描述的功能。

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

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

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