Purrr-Fection:寻找利用 Purrr 的条件数据帧操作的优雅解决方案
Purrr-Fection: In Search of An Elegant Solution to Conditional Data Frame Operations Leveraging 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
)
挑战
我想对 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
.
有条件地对位置进行地理标记
我在构建完整地址时试图并行遍历多个列(以及 rowwise()
和 by_row()
).然而,我未能构建任何符合优雅解决方案条件的东西。
如有任何见解,我们将不胜感激。
我不确定 purrr
但下面是使用管道的内容:
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
)
df2<-df %>%
filter(is.na(lon) | is.na(lat)) %>%
group_by(Street, City, State) %>% #not really necessary but it suppresses a warning
mutate(lon=ifelse(is.na(lon) | is.na(lat),
geocode(paste(Street, City,State, sep=" ")), 0)) %>%
mutate(lat=ifelse(is.na(lon) | is.na(lat),
rev(geocode(paste(Street, City,State, sep=" "))), 0))
如果您想要上面示例代码中的部分输出:
as.data.frame(df2)[,5:6]
lon lat
1 40.77505 -73.96515
2 40.88259 -73.50538
或包括所有列:
as.data.frame(df2)
Street City State Zip lon lat
1 5th Ave New York New York 10022 40.77505 -73.96515
2 20 Sagamore Hill Rd Oyster Bay New York 11771 40.88259 -73.50538
如果您想将原始数据与新数据结合起来,您可以执行以下操作:
as.data.frame(rbind(filter(df, !is.na(lon) | !is.na(lat)),df2 ))
Street City State Zip lon lat
1 226 W 46th St New York New York 10036 -73.98670 40.75902
2 75 Broadway New York New York 10006 -74.01205 40.70814
3 350 5th Ave New York New York 10118 -73.98566 40.74871
4 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
5 5th Ave New York New York 10022 40.77505 -73.96515
6 20 Sagamore Hill Rd Oyster Bay New York 11771 -73.96515 40.77505
...或者您可以像下面这样将其全部简化(保持原始顺序):
df2<-df %>%
#group_by(Street, City, State) %>% # unescape if you want to suppress warning
mutate(lon=ifelse(is.na(lon) | is.na(lat),
geocode(paste(Street, City,State, sep=" ")), lon)) %>%
mutate(lat=ifelse(is.na(lon) | is.na(lat),
rev(geocode(paste(Street, City,State, sep=" "))), lat))
as.data.frame(df2)
Street City State Zip lon lat
1 226 W 46th St New York New York 10036 -73.98670 40.75902
2 5th Ave New York New York 10022 -73.98670 40.75902
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 40.75902 -73.98670
6 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
真的,您要避免调用 geocode
不必要的次数,因为它很慢,而且如果您使用 Google,您每天只有 2500 个查询。因此,最好从同一个调用中创建两列,这可以通过列表列来完成,使用 do
或自连接创建 data.frame 的新版本。
1。带有列表列
使用列表列,您可以使用 ifelse
创建 lon
和 lat
的新版本,如果有 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]
}))
returns与第一个版本完全一样。
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')))
与 returns 相同,但新地理编码的行位于顶部。同样的方法适用于列表列或 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
擅长处理列表,虽然列表列是一种选择,但实际上并不需要对其进行操作。
使用 dplyr:
df %>% mutate( lon = case_when( is.na(lon) ~ geocode(paste(Street, City, State, Zip))[,1],
TRUE ~ lon),
lat = case_when( is.na(lat) ~ geocode(paste(Street, City, State, Zip))[,2],
TRUE ~ lat )
)
背景
我有一个问题,可能有多种解决方案,但我相信有一个尚未发现的利用 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
.
我在构建完整地址时试图并行遍历多个列(以及 rowwise()
和 by_row()
).然而,我未能构建任何符合优雅解决方案条件的东西。
如有任何见解,我们将不胜感激。
我不确定 purrr
但下面是使用管道的内容:
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
)
df2<-df %>%
filter(is.na(lon) | is.na(lat)) %>%
group_by(Street, City, State) %>% #not really necessary but it suppresses a warning
mutate(lon=ifelse(is.na(lon) | is.na(lat),
geocode(paste(Street, City,State, sep=" ")), 0)) %>%
mutate(lat=ifelse(is.na(lon) | is.na(lat),
rev(geocode(paste(Street, City,State, sep=" "))), 0))
如果您想要上面示例代码中的部分输出:
as.data.frame(df2)[,5:6]
lon lat
1 40.77505 -73.96515
2 40.88259 -73.50538
或包括所有列:
as.data.frame(df2)
Street City State Zip lon lat
1 5th Ave New York New York 10022 40.77505 -73.96515
2 20 Sagamore Hill Rd Oyster Bay New York 11771 40.88259 -73.50538
如果您想将原始数据与新数据结合起来,您可以执行以下操作:
as.data.frame(rbind(filter(df, !is.na(lon) | !is.na(lat)),df2 ))
Street City State Zip lon lat
1 226 W 46th St New York New York 10036 -73.98670 40.75902
2 75 Broadway New York New York 10006 -74.01205 40.70814
3 350 5th Ave New York New York 10118 -73.98566 40.74871
4 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
5 5th Ave New York New York 10022 40.77505 -73.96515
6 20 Sagamore Hill Rd Oyster Bay New York 11771 -73.96515 40.77505
...或者您可以像下面这样将其全部简化(保持原始顺序):
df2<-df %>%
#group_by(Street, City, State) %>% # unescape if you want to suppress warning
mutate(lon=ifelse(is.na(lon) | is.na(lat),
geocode(paste(Street, City,State, sep=" ")), lon)) %>%
mutate(lat=ifelse(is.na(lon) | is.na(lat),
rev(geocode(paste(Street, City,State, sep=" "))), lat))
as.data.frame(df2)
Street City State Zip lon lat
1 226 W 46th St New York New York 10036 -73.98670 40.75902
2 5th Ave New York New York 10022 -73.98670 40.75902
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 40.75902 -73.98670
6 45 Rockefeller Plaza New York New York 10111 -73.97771 40.75915
真的,您要避免调用 geocode
不必要的次数,因为它很慢,而且如果您使用 Google,您每天只有 2500 个查询。因此,最好从同一个调用中创建两列,这可以通过列表列来完成,使用 do
或自连接创建 data.frame 的新版本。
1。带有列表列
使用列表列,您可以使用 ifelse
创建 lon
和 lat
的新版本,如果有 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]
}))
returns与第一个版本完全一样。
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')))
与 returns 相同,但新地理编码的行位于顶部。同样的方法适用于列表列或 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
擅长处理列表,虽然列表列是一种选择,但实际上并不需要对其进行操作。
使用 dplyr:
df %>% mutate( lon = case_when( is.na(lon) ~ geocode(paste(Street, City, State, Zip))[,1],
TRUE ~ lon),
lat = case_when( is.na(lat) ~ geocode(paste(Street, City, State, Zip))[,2],
TRUE ~ lat )
)