2015-02-23 37 views
10

Cel: Korzystanie R, dostać szerokości i długości geograficznej danych dla wektora adresów poprzez open.mapquestapiadresy partii przetwarzania danych geograficznych w R z otwartym mapquestapi

punkt wyjścia: Od geocode z pakietu ggmap jest ograniczone do 2500 zapytań dziennie, musiałem znaleźć inny sposób (Moja data.frame składa się z 9M wpisów). Zestaw narzędzi do nauki danych nie wchodzi w grę, ponieważ większość moich adresów znajduje się poza Wielką Brytanią/USA. Znalazłem ten wspaniały fragment na http://rpubs.com/jvoorheis/Micro_Group_Rpres z wykorzystaniem 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") 

Potrzebujemy tych bibliotek:

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

Stwórzmy mock-up data.frame z 5 adresów.

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 

Przez dodanie zmienną szerokość i długość geograficzna lonlat do data.frame mogliśmy pracować z for -loop. Przedstawię kod, aby pokazać, że funkcja działa w zasadzie.

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

Z punktu widzenia wydajności ten kod jest dość zły. Nawet dla tego małego pliku data.frame mój komputer trwał około 9 sekund, najprawdopodobniej z powodu zapytania do usługi sieciowej, ale nieważne. Mógłbym uruchomić ten kod w moich wierszach 9M, ale czas byłby olbrzymi.

Moja próba polegała na wykorzystaniu funkcji mutate z pakietu dplyr. Oto, co starałem:

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

system.time przystanki w zaledwie 2,3 sekundy. Nieźle. Ale tutaj jest 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 i lon są dokładnie takie same dla wszystkich wpisów. W moim rozumieniu funkcja mutate działa w sposób wierszowy. Ale tutaj, lat i lon są obliczane z pierwszego rzędu. W związku z tym pierwszy wiersz jest poprawny. Czy ktoś ma pomysł, dlaczego? Podany przeze mnie kod jest kompletny. Nic ekstra załadowanego. Jakieś pomysły? Jeśli masz alternatywny sposób działania zamiast optymalizacji mojego kodu, również byłbym wdzięczny.

+0

W jaki sposób zapytanie dostarczone przez @NicE zakończyło pracę dla twoich wierszy 9M? czy byłeś w stanie geokodować wszystkie instancje w stosunkowo niewielkim czasie, czy też trafiłeś w MapQuest? – bshelt141

Odpowiedz

4

To bardzo proste, aby spojrzeć na mutate() i wyciągnąć wniosek, że to, co się dzieje, jest podobny do tego, co pokazują w twojej pętli - ale to, czego rzeczywiście skoro tam jest tylko funkcja vectorized R, który działając na całej kolumnie ramka danych.

Nie byłbym zaskoczony, gdyby inni mieli takie nieporozumienie - tutoriale dplyr nie zajmują się rozróżnieniem pomiędzy funkcjami wektoryzowanymi/niewizualizowanymi, a (nawet bardziej niebezpiecznymi) regułami R recycling oznaczają, że zastosowanie funkcji skalarnej nie będzie koniecznie podnieść błąd. Dyskusja na temat tego here.

Jedną opcją jest przepisanie swojego geocode_attempt, aby mógł pobrać wektor adresów.

Jeśli chcesz zachować swoją funkcję, jak jest, ale chce dplyr zachowywać się bardziej jak coś z -ply family masz dwa potencjalne podejścia:

Pierwszym jest użycie zmienną grupującą masz w danych:

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

Drugi to użycie funkcji rowwise() opisanej w odpowiedzi this.

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

Rozwiązanie group_by jest znacznie szybsze na moim komputerze. Nie pewny dlaczego!

Niestety, oszczędności prędkości, które widzisz od dplyr powyżej, są prawdopodobnie nieco iluzoryczne - najprawdopodobniej wynik wywołania funkcji geokodowania tylko jeden raz (w stosunku do wiersza w pętli raz). Może i zyski, ale musisz ponownie uruchomić timingi.

10

Być może trzeba wektorować swoją funkcję geocode_attempt to zrobić columnwise:

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

a następnie zadzwonić:

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

do prędkości rzeczy w górę, warto spojrzeć na trybie wsadowym API, aby za jednym razem uzyskać do 100 łat i więcej.

Aby użyć partii żądań API mogłyby skorzystać z tej funkcji:

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

Aby przetestować go:

#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

Jak potrzebowałbym zmienić funkcję? Domyślam się, że zmiana adresu URL2 nie zadziała :) Wektoryzacja funkcji działa i jest nieco szybsza niż opcje 'group_by' i' rowwise' –

+0

tak, Edytowałem moją odpowiedź, aby dodać zmodyfikowaną wersję geocode_attempt, która obsługiwałaby żądania wsadowe . W 125 rzędach jest ~ dwa razy szybszy. – NicE

+0

Działa jak urok ... ponieważ jest to wywołanie API, nie jest bardzo szybkie, ale działa. Ktoś ma pojęcie o tym, jaki jest limit zapytań dla mapquest? –

0

Jest geocoding package użyciu Nokia HERE usługi. Ma tryb wsadowy. Możesz go użyć za pomocą testowych kluczy API i nie możesz przekroczyć limitu. Warto spojrzeć ...

+0

Jaki byłby limit wsadowy dla geocodeHERE? Czy mam rację, zakładając, że jesteś programistą? –

+0

Limit wynosi 10K, ale pozwalają na użycie domyślnych kluczy pozornie bez ograniczeń. Tak, to tylko proste opakowanie dla interfejsu API. Funkcjonalność partii jest jednak przyjemna. – cory