Адреса партий Geocode в R с открытым mapquestapi

Цель: Использование R, получение данных широты и долготы для вектора адресов через open.mapquestapi

Точка отправления: Поскольку geocode из пакета ggmap ограничено 2500 запросами в день, мне нужно было найти другой способ (My 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")

Нам нужны эти библиотеки:

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

Позвольте создать макет data.frame с 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

Для добавления переменной latitude lat и longitude lon в data.frame мы могли бы работать с 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 секунд, скорее всего, из-за запроса webservice, но неважно. Таким образом, я мог бы запустить этот код на своих девяти строках, но время было бы огромным.

Моя попытка состояла в использовании функции 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, вычисляются из первого ряда. Соответственно, первая строка верна. У кого-нибудь есть идея, почему? Код, который я предоставил, завершен. Ничего лишнего. Есть идеи? Если у вас есть альтернативный вариант, а не оптимизация моего кода, я был бы также благодарен.

Ответ 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])

Ответ 2

Очень просто посмотреть на mutate() и сделать вывод, что то, что происходит, похоже на то, что вы проиллюстрируете в своем цикле for, но то, что вы на самом деле видите, есть только vectorized Функция R, действующая на весь столбец кадра данных.

Я бы не удивился, если бы у других было это заблуждение. Учебники dplyr не рассматривают различия между векторизованными/не-векторизованными функциями и (еще более опасными) R recycling означает, что применение скалярной функции не обязательно приведет к ошибке. Там еще несколько обсуждений этого здесь.

Один из вариантов заключается в том, чтобы переписать ваш 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])

Вторым является использование функции 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 выше, скорее всего, несколько иллюзорны - скорее всего, результат функции геокодирования получает вызов только один раз (vs один раз за строку в цикле). Там может быть достигнута прибыль, но вам нужно снова запустить тимпинг.

Ответ 3

Здесь пакет геокодирования, используя службу Nokia HERE. Он имеет пакетный режим. Вы можете использовать его с помощью тестовых API-ключей, и вы не можете получить ограничение. Стоит посмотреть...