2015-02-23 6 views
10

Ziel: Mit R, Breiten- und Längendaten für einen Vektor von Adressen durch open.mapquestapiGeocode Batch-Adressen in R mit offenen mapquestapi

Abfahrtpunkt erhalten: Seit geocode vom ggmap Paket beschränkt zu 2500 Abfragen pro Tag, musste ich einen anderen Weg finden (Mein data.frame besteht aus 9M Einträgen). Das Data-Science-Toolkit ist keine Option, da die meisten meiner Adressen außerhalb des Vereinigten Königreichs/der USA liegen. Ich fand dieses exzellente Snippet unter http://rpubs.com/jvoorheis/Micro_Group_Rpres mit 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") 

Wir brauchen diese Bibliotheken:

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

Lasst uns ein Mock-up data.frame mit 5 Adressen erstellen.

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 

Für ein Hinzufügen einer Breite und Länge latlon Variable auf den data.frame wir mit einem for -Loop arbeiten konnte. Ich werde den Code präsentieren, nur um zu zeigen, dass die Funktion im Prinzip funktioniert.

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] 
} 

Vom Leistungsstandpunkt ist dieser Code ziemlich schlecht. Selbst für diesen kleinen data.frame benötigte mein Computer etwa 9 Sekunden, wahrscheinlich aufgrund der Webservice-Abfrage, aber egal. Also könnte ich diesen Code in meinen 9 Millionen Zeilen laufen lassen, aber die Zeit wäre enorm.

Mein Versuch war die mutate Funktion aus dem dplyr Paket zu verwenden. Hier ist, was ich versucht:

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

system.time Anschläge in nur 2,3 Sekunden. Nicht so schlecht. Aber hier ist das Problem:

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 und lon sind genau die gleichen für alle Einträge. In meinem Verständnis funktioniert die mutate Funktion reihenweise. Aber hier sind lat und lon aus der ersten Reihe berechnet. Dementsprechend ist die erste Zeile korrekt. Hat jemand eine Idee warum? Der von mir bereitgestellte Code ist vollständig. Nichts extra geladen. Irgendwelche Ideen? Wenn Sie einen performanten alternativen Weg anstelle einer Optimierung meines Codes haben, wäre ich auch dankbar.

+0

Wie funktioniert die von @NicE bereitgestellte Abfrage für Ihre 9M Zeilen? Konnten Sie alle Instanzen in relativ kurzer Zeit geocodieren oder haben Sie mit MapQuest eine Einschränkung erreicht? – bshelt141

Antwort

4

Es ist wirklich einfach zu mutate() schauen und den Schluss zu ziehen, dass ähnliche, was passiert ist, was Sie in Ihrem for-Schleife zeigen - aber was Sie tatsächlich sehen, gibt es nur eine vectorized R-Funktion, die der auf der gesamten Spalte handeln der Datenrahmen.

Ich wäre nicht überrascht, wenn andere dieses Missverständnis hätten - die dplyr Tutorials behandeln nicht die Unterscheidung zwischen vektorisierten/nicht vektorisierten Funktionen und (noch gefährlicher) Rs recycling Regeln bedeuten, dass die Anwendung einer Skalarfunktion nicht notwendigerweise einen Fehler melden. Es gibt einige weitere Diskussionen über diese here.

Eine Möglichkeit ist, Ihre geocode_attempt so neu zu schreiben, dass sie einen Vektor von Adressen nehmen kann.

Wenn Sie Ihre Funktion zu halten wie es ist, aber dplyr wollen eher wie etwas aus dem -ply family Sie haben zwei mögliche Ansätze verhalten:

Die erste ist die Gruppenvariable Sie in Ihre Daten zu verwenden:

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]) 

Die zweite ist zu verwenden rowwise() Funktion beschrieben in this Antwort.

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

Die group_by-Lösung ist auf meiner Maschine wesentlich schneller. Nicht sicher warum!

Leider sind die Geschwindigkeitseinsparungen, die Sie von dplyr oben sehen, wahrscheinlich etwas illusorisch - höchstwahrscheinlich das Ergebnis der Geocodierungsfunktion, die nur einmal aufgerufen wird (vs einmal pro Zeile in der Schleife). Es kann durchaus zu Gewinnen kommen, aber Sie müssen die Timings erneut ausführen.

10

Möglicherweise müssen Sie geocode_attempt Funktion vektorisieren, es zu tun spalten:

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

Und dann rufen:

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

Um Geschwindigkeit Ding, Sie vielleicht im Batch-Modus suchen, die API, um bis zu 100 Lats und Longs auf einmal zu bekommen.

Um die Stapelanforderungen der API verwenden Sie diese Funktion nutzen zu können:

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)) 
} 

Um es zu testen:

#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]) 
+0

Wie müsste ich die Funktion ändern? Ich denke, nur ändern URL2 wird nicht funktionieren :) Vectorizing die Funktion funktioniert und ist etwas schneller als die 'group_by' und' rowwise' Optionen –

+0

ja, ich habe meine Antwort geändert, um eine modifizierte Version des geocode_attempt, die Batch-Anfragen behandeln würde . In 125 Reihen ist es ~ doppelt so schnell. – NicE

+0

Funktioniert wie ein Charme ... da es ein API-Aufruf ist, ist es nicht sehr schnell, aber funktioniert. Hat jemand eine Idee, was das Abfrage-Limit für Mapquest ist? –

0

Es gibt einen geocoding package Nokia mit HIER Service. Es hat einen Stapelmodus. Sie können es mit den Test-API-Schlüsseln verwenden und Sie können möglicherweise kein Limit erreichen. Einen Blick wert ...

+0

Was wäre die Batch-Grenze für GeocodeHERE? Habe ich Recht, wenn ich annehme, dass Sie der Entwickler sind? –

+0

Limit ist 10K, aber sie ermöglichen die Verwendung ihrer Standardschlüssel scheinbar ohne Limit. Ja, es ist nur ein einfacher Wrapper für die API. Die Batch-Funktionalität ist aber irgendwie nett. – cory