背景
我有一个可能有多种解决方案途径的问题,但我深信有一种利用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
)
挑战
我想对
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()
问题
虽然我可以使上述工作正常进行,甚至将新识别的
lon
和lat
坐标纠缠到原始数据帧中,但整个方案感觉很脏。我相信,有一种优雅的方法可以利用管道和purrr来遍历数据框,并根据lon
和lat
的缺失有条件地对位置进行地理标记。在构造完整的地址(以及
purrr::pmap
和rowwise()
)时,我尝试了许多尝试,包括by_row()
的并行处理,以尝试并行浏览多个列。尽管如此,我在构建任何可以作为一种优雅解决方案的方面都达不到要求。提供的任何见解将不胜感激。
最佳答案
确实,您希望避免不必要地调用geocode
,因为它很慢,而且如果您使用的是Google,则每天只能进行2500个查询。因此,最好从同一调用中创建两个列,这可以使用列表列来完成,使用do
或自连接来创建data.frame的新版本。
1.带有列表列
在列表列中,使用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
2.使用
do
另一方面,
do
从旧数据块创建一个全新的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]
}))
返回与第一个版本完全相同的内容。
3.在一个子集上,与自联接重新组合
如果
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')))
会返回相同的内容,但新编码的行位于顶部。列表列或
do
也可以使用相同的方法,但是由于不需要合并两组列,因此只需bind_cols
即可解决问题。4.在带有
mutate_geocode
的子集上ggmap
实际上包括一个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
5.基数R
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)])
结果与第一个版本相同。
所有版本仅调用
geocode
两次。请注意,虽然您可以使用
purrr
进行作业,但它并不比常规dplyr
特别适合。 purrr
擅长处理列表,虽然列表列是一种选择,但实际上并不需要对其进行操作。