目标:使用R,通过open.mapquestapi获取地址向量的纬度和经度数据
出发地点:由于geocode
包中的ggmap
每天限制为2500次查询,我需要找到一种不同的方式(我的数据。框架包括9M条目)。数据科学工具包不是一种选择,因为我的大多数地址都在英国/美国之外。我使用open.mapquestapi在http://rpubs.com/jvoorheis/Micro_Group_Rpres上找到了这个优秀的片段。
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个地址的模拟数据框架。
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
- 循环。我将展示代码,只是为了证明该函数原则上是有效的。
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]
}
从性能角度来看,这段代码非常糟糕。即使对于这个小型数据框架,我的计算机大约花了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])
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
所有条目的 lat
和lon
完全相同。据我所知,mutate
函数正在按行运行。但是在这里,lat和lon是从第一行计算出来的。因此,第一行是正确的。有谁知道为什么?我提供的代码已经完成。没有额外的装载。有任何想法吗?如果您有一种高效的替代方式而不是优化我的代码,我将不胜感激。
答案 0 :(得分:10)
您可能需要对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])
答案 1 :(得分:4)
很容易看到mutate()
并得出结论:发生的事情与你在for循环中所说的相似 - 但你实际上在那里看到的是什么只是一个vectorized R函数,它作用于数据帧的整个列。
如果其他人有这种误解,我不会感到惊讶 - dplyr教程没有解决矢量化/非矢量化函数之间的区别,以及(甚至更危险的)R {'{3}}规则意味着应用标量函数不一定会引发错误。还有一些关于这个recycling的讨论。
一种选择是重写geocode_attempt
,以便接收地址向量。
如果您希望按原样保留您的功能,但希望dplyr的行为更像here中的某些内容,那么您有两种可能的方法:
第一种方法是使用数据中的分组变量:
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])
第二种是使用-ply family回答中描述的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上面看到的速度节省可能有些虚幻 - 很可能是地理编码功能只被调用一次(在循环中每行一次)。可能会有所收获,但你需要再次运行这些时间。
答案 2 :(得分:0)
使用诺基亚HERE服务geocoding package。它有一个批处理模式。您可以将它与测试API密钥一起使用,但您可能没有达到限制。值得一看......