amo*_*ine 7 geocoding r ggmap purrr
的背景
我有一个问题,其中有许多解决方案可能,但我相信有一个尚未发现的优雅解决方案利用purrr.
示例代码
我有一个如下的大数据框,我在其中包含了一个例子:
library(tibble)
library(ggmap)
library(purrr)
library(dplyr)
# Define Example Data
df <- frame_data(
~Street, ~City, ~State, ~Zip, ~lon, ~lat,
"226 W 46th St", "New York", "New York", 10036, -73.9867, 40.75902,
"5th Ave", "New York", "New York", 10022, NA, NA,
"75 Broadway", "New York", "New York", 10006, -74.01205, 40.70814,
"350 5th Ave", "New York", "New York", 10118, -73.98566, 40.74871,
"20 Sagamore Hill Rd", "Oyster Bay", "New York", 11771, NA, NA,
"45 Rockefeller Plaza", "New York", "New York", 10111, -73.97771, 40.75915
)
Run Code Online (Sandbox Code Playgroud)
挑战
我想对当前列lon和lat列的所有位置进行地理标记NA.我有很多方法可以解决这个问题,其中一个方法如下所示:
# Safe Code is Great Code
safe_geocode <- safely(geocode)
# Identify Data to be Geotagged by Absence of lon and lat
data_to_be_geotagged <- df %>% filter(is.na(lon) | is.na(lat))
# GeoTag Addresses of Missing Data Points
fullAddress <- paste(data_to_be_geotagged$Street,
data_to_be_geotagged$City,
data_to_be_geotagged$State,
data_to_be_geotagged$Zip,
sep = ", ")
fullAddress %>%
map(safe_geocode) %>%
map("result") %>%
plyr::ldply()
Run Code Online (Sandbox Code Playgroud)
问题
虽然我可以使上述工作,甚至将新识别的lon和lat坐标回到原始数据框架中,整个方案感觉很脏.我相信有利用管道和purrr要经过数据帧和有条件地地理标记基于缺少的位置优雅的方式lon和lat.
purrr::pmap在构建完整地址(以及rowwise()和by_row())时,我已经陷入了许多兔子洞,包括试图并行穿过多个列.尽管如此,我还是无法构建任何符合优雅解决方案的东西.
提供的任何见解将非常感激.
实际上,您希望避免拨打geocode任何超过必要的内容,因为它很慢,如果您使用Google,则每天只有2500次查询.因此,最好从同一个调用中创建两个列,这可以通过列表列来完成,使用新的版本的data.frame do或自联接.
使用列表列,您可以创建新版本lon和lat带有ifelse地理编码(如果有)NA,否则只需复制现有值.之后,摆脱旧版本的列并取消新版本:
library(dplyr)
library(ggmap)
library(tidyr) # For `unnest`
# Evaluate each row separately
df %>% rowwise() %>%
# Add a list column. If lon or lat are NA,
mutate(data = ifelse(any(is.na(c(lon, lat))),
# return a data.frame of the geocoded results,
list(geocode(paste(Street, City, State, Zip))),
# else return a data.frame of existing columns.
list(data_frame(lon = lon, lat = lat)))) %>%
# Remove old columns
select(-lon, -lat) %>%
# Unnest newly created ones from list column
unnest(data)
## # A tibble: 6 × 6
## Street City State Zip lon lat
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 226 W 46th St New York New York 10036 -73.98670 40.75902
## 2 5th Ave New York New York 10022 -73.97491 40.76167
## 3 75 Broadway New York New York 10006 -74.01205 40.70814
## 4 350 5th Ave New York New York 10118 -73.98566 40.74871
## 5 20 Sagamore Hill Rd Oyster Bay New York 11771 -73.50538 40.88259
## 6 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
Run Code Online (Sandbox Code Playgroud)
dodo另一方面,从旧的数据框架创建一个全新的data.frame.它需要稍微笨重的$符号,.用来表示分组的data.frame管道输入.使用if和else代替ifelse可以避免在列表中嵌套结果(无论如何它们必须在列表之上).
# Evaluate each row separately
df %>% rowwise() %>%
# Make a new data.frame from the first four columns and the geocode results or existing lon/lat
do(bind_cols(.[1:4], if(any(is.na(c(.$lon, .$lat)))){
geocode(paste(.[1:4], collapse = ' '))
} else {
.[5:6]
}))
Run Code Online (Sandbox Code Playgroud)
返回与第一个版本完全相同的东西.
如果ifelse过于混乱,您可以只对一个子集进行地理编码,然后通过将行绑定到anti_join,即包含df但不是子集的所有行来重新组合.:
df %>% filter(is.na(lon) | is.na(lat)) %>%
select(1:4) %>%
bind_cols(geocode(paste(.$Street, .$City, .$State, .$Zip))) %>%
bind_rows(anti_join(df, ., by = c('Street', 'Zip')))
Run Code Online (Sandbox Code Playgroud)
返回相同的东西,但顶部有新的地理编码行.相同的方法适用于列列或do,但由于不需要组合两组列,只需要bind_cols做到这一点.
mutate_geocodeggmap实际上包含一个mutate_geocode函数,它将在传递data.frame和一列地址时添加lon和lat列.它有一个问题:它不能接受多于地址的列名,因此需要一个包含整个地址的列.因此,虽然这个版本可能非常好,但它需要使用整个地址创建和删除一个额外的列,这使得它更加令人不安:
df %>% filter(is.na(lon) | is.na(lat)) %>%
select(1:4) %>%
mutate(address = paste(Street, City, State, Zip)) %>% # make an address column
mutate_geocode(address) %>%
select(-address) %>% # get rid of address column
bind_rows(anti_join(df, ., by = c('Street', 'Zip')))
## Street City State Zip lon lat
## 1 5th Ave New York New York 10022 -73.97491 40.76167
## 2 20 Sagamore Hill Rd Oyster Bay New York 11771 -73.50538 40.88259
## 3 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
## 4 350 5th Ave New York New York 10118 -73.98566 40.74871
## 5 75 Broadway New York New York 10006 -74.01205 40.70814
## 6 226 W 46th St New York New York 10036 -73.98670 40.75902
Run Code Online (Sandbox Code Playgroud)
Base R可以直接分配给一个子集,这使得这里的习语更简单,即使它需要大量的子集:
df[is.na(df$lon) | is.na(df$lat), c('lon', 'lat')] <- geocode(paste(df$Street, df$City, df$State, df$Zip)[is.na(df$lon) | is.na(df$lat)])
Run Code Online (Sandbox Code Playgroud)
结果与第一个版本相同.
所有版本只调用geocode两次.
请注意,虽然您可以使用purrr该作业,但它并不比常规更适合dplyr.purrr擅长处理列表,虽然列表列是一个选项,但它实际上不必被操纵.