背景

我有一个可能有多种解决方案途径的问题,但我深信有一种利用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
)


挑战

我想对lonlat列当前为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()


问题

虽然我可以使上述工作正常进行,甚至将新识别的lonlat坐标纠缠到原始数据帧中,但整个方案感觉很脏。我相信,有一种优雅的方法可以利用管道和purrr来遍历数据框,并根据lonlat的缺失有条件地对位置进行地理标记。

在构造完整的地址(以及purrr::pmaprowwise())时,我尝试了许多尝试,包括by_row()的并行处理,以尝试并行浏览多个列。尽管如此,我在构建任何可以作为一种优雅解决方案的方面都达不到要求。

提供的任何见解将不胜感激。

最佳答案

确实,您希望避免不必要地调用geocode,因为它很慢,而且如果您使用的是Google,则每天只能进行2500个查询。因此,最好从同一调用中创建两个列,这可以使用列表列来完成,使用do或自连接来创建data.frame的新版本。



1.带有列表列

在列表列中,使用lon创建latifelse的新版本,如果存在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。使用ifelse而不是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擅长处理列表,虽然列表列是一种选择,但实际上并不需要对其进行操作。

09-06 08:10