用于处理对齐数据帧组的高效 tidy R 技术
efficient tidy R technique for processing aligned data frame groups
我正在尝试找到一种有效(理想情况下整洁)的方式来处理一对分组 data_frames。设置看起来或多或少是这样的:
A = crossing(idx=1:1e5, asdf=seq(1:rpois(1,50))
B = tbl(idx=sample(1:1e5, replace=TRUE), yet_more_stuff='whatever')
proc_one_group <- function(one_A, one_b) { ... }
# example:
proc_one_group(filter(A, idx==50), filter(B, idx==50))
因此,我的处理操作相当复杂,一次对来自两个独立数据帧的一个 idx
进行操作,其中一个数据帧每个 idx
,另一个每个 idx
.
可以有零行、一行或多行
我知道我可以做到这一点的方法是这样,但它非常慢,因为对每个值的 filter
操作需要完整的 table 扫描和子集。
map_df(unique(A$idx), ~ proc_one_group(filter(A, idx==.), filter(B, idx==.)))
我也知道我可以使用 split
相对高效地创建 data_frames 的子帧列表,但我不知道然后按索引进行 O(1) 查找的好方法两个 data_frame
中的一个。
我想要的是 left_join
的第一步,它计算出每个组的索引子组,而不是实际创建单个 data_frame
笛卡尔组合在每个组中,它只给我一对我可以根据需要处理的子组。 (完整的 left_join
在这里对我没有帮助。)
有什么想法吗?
一种可能是先嵌套两个数据框,然后再加入:
library(tidyverse)
set.seed(1234)
A = crossing(idx = 1:1e5, asdf = seq(1:rpois(1, 50)))
B = data_frame(idx = sample(1:1e5, replace = TRUE), yet_more_stuff = "whatever")
proc_one_group <- function(one_A, one_B) { ... }
nest_A <- A %>%
group_by(idx) %>%
nest(.key = "data_a")
nest_B <- B %>%
group_by(idx) %>%
nest(.key = "data_b")
all_data <- full_join(nest_A, nest_B, by = "idx")
all_data
#> # A tibble: 100,000 x 3
#> idx data_a data_b
#> <int> <list> <list>
#> 1 1 <tibble [41 x 1]> <NULL>
#> 2 2 <tibble [41 x 1]> <tibble [2 x 1]>
#> 3 3 <tibble [41 x 1]> <tibble [2 x 1]>
#> 4 4 <tibble [41 x 1]> <tibble [1 x 1]>
#> 5 5 <tibble [41 x 1]> <NULL>
#> 6 6 <tibble [41 x 1]> <NULL>
#> 7 7 <tibble [41 x 1]> <tibble [2 x 1]>
#> 8 8 <tibble [41 x 1]> <NULL>
#> 9 9 <tibble [41 x 1]> <tibble [1 x 1]>
#> 10 10 <tibble [41 x 1]> <tibble [1 x 1]>
#> # ... with 99,990 more rows
这会产生一个数据帧,每个 idx
的数据来自数据帧 A
,数据来自 data_a
,数据帧 B
的数据位于data_b
。完成此操作后,不必在 map_df
调用中针对每个案例过滤大数据框。
all_data %>%
map2_df(data_a, data_b, proc_one_group)
这是一些基准测试结果:
A = crossing(idx=1:1e3, asdf=seq(1:rpois(1,50)))
B = tibble(idx=sample(1:1e3, replace=TRUE), yet_more_stuff='whatever')
第一个想法是按照您的建议使用 split
,保持 split.A
和 split.B
的顺序相同。您可以使用 map2
遍历匹配列表:
myfun <- function(A,B) {
split.A <- split(A, A$idx)
splitsort.A <- split.A[order(names(split.A))]
splitsort.B <- map(names(splitsort.A), ~B[as.character(B$idx) == .x,])
ans <- map2(splitsort.A, splitsort.B, ~unique(.x$idx) == unique(.y$idx))
return(ans)
}
这是您当前使用的方法,使用dplyr::filter
OP <- function(A,B) {
ans <- map(unique(A$idx), ~unique(filter(A, idx==.x)$idx) == unique(filter(B, idx==.x)$idx))
return(ans)
}
这是相同的逻辑,但避免了 dplyr::filter
,与基础 R 子集
相比 更慢
OP2 <- function(A,B) {
ans <- map(unique(A$idx), ~unique(A[A$idx==.x,]$idx) == unique(B[B$idx==.x,]$idx))
return(ans)
}
这使用了@JakeThompson 的方法(它似乎是当前方法中的赢家)
JT <- function(A,B) {
nest.A <- A %>% group_by(idx) %>% nest()
nest.B <- B %>% group_by(idx) %>% nest()
ans <- full_join(nest.A, nest.B, by="idx")
}
一些验证以确保某些函数的结果有意义
identical(OP(A,B), OP2(A,B))
# TRUE
E <- myfun(A,B)
any(E==FALSE)
# NA
F <- myfun(A,B)
any(F==FALSE)
# NA
identical(sum(E==TRUE, na.rm=TRUE), sum(F==TRUE, na.rm=TRUE))
# TRUE
基准测试结果
library(microbenchmark)
microbenchmark(myfun(A,B), OP(A,B), OP2(A,B), JT(A,B), times=2L)
# Unit: seconds
# expr min lq mean median uq max neval
# myfun(A, B) 3.164046 3.164046 3.254588 3.254588 3.345129 3.345129 2
# OP(A, B) 14.926431 14.926431 15.053662 15.053662 15.180893 15.180893 2
# OP2(A, B) 3.202414 3.202414 3.728423 3.728423 4.254432 4.254432 2
# JT(A, B) 1.330278 1.330278 1.378241 1.378241 1.426203 1.426203 2
我正在尝试找到一种有效(理想情况下整洁)的方式来处理一对分组 data_frames。设置看起来或多或少是这样的:
A = crossing(idx=1:1e5, asdf=seq(1:rpois(1,50))
B = tbl(idx=sample(1:1e5, replace=TRUE), yet_more_stuff='whatever')
proc_one_group <- function(one_A, one_b) { ... }
# example:
proc_one_group(filter(A, idx==50), filter(B, idx==50))
因此,我的处理操作相当复杂,一次对来自两个独立数据帧的一个 idx
进行操作,其中一个数据帧每个 idx
,另一个每个 idx
.
我知道我可以做到这一点的方法是这样,但它非常慢,因为对每个值的 filter
操作需要完整的 table 扫描和子集。
map_df(unique(A$idx), ~ proc_one_group(filter(A, idx==.), filter(B, idx==.)))
我也知道我可以使用 split
相对高效地创建 data_frames 的子帧列表,但我不知道然后按索引进行 O(1) 查找的好方法两个 data_frame
中的一个。
我想要的是 left_join
的第一步,它计算出每个组的索引子组,而不是实际创建单个 data_frame
笛卡尔组合在每个组中,它只给我一对我可以根据需要处理的子组。 (完整的 left_join
在这里对我没有帮助。)
有什么想法吗?
一种可能是先嵌套两个数据框,然后再加入:
library(tidyverse)
set.seed(1234)
A = crossing(idx = 1:1e5, asdf = seq(1:rpois(1, 50)))
B = data_frame(idx = sample(1:1e5, replace = TRUE), yet_more_stuff = "whatever")
proc_one_group <- function(one_A, one_B) { ... }
nest_A <- A %>%
group_by(idx) %>%
nest(.key = "data_a")
nest_B <- B %>%
group_by(idx) %>%
nest(.key = "data_b")
all_data <- full_join(nest_A, nest_B, by = "idx")
all_data
#> # A tibble: 100,000 x 3
#> idx data_a data_b
#> <int> <list> <list>
#> 1 1 <tibble [41 x 1]> <NULL>
#> 2 2 <tibble [41 x 1]> <tibble [2 x 1]>
#> 3 3 <tibble [41 x 1]> <tibble [2 x 1]>
#> 4 4 <tibble [41 x 1]> <tibble [1 x 1]>
#> 5 5 <tibble [41 x 1]> <NULL>
#> 6 6 <tibble [41 x 1]> <NULL>
#> 7 7 <tibble [41 x 1]> <tibble [2 x 1]>
#> 8 8 <tibble [41 x 1]> <NULL>
#> 9 9 <tibble [41 x 1]> <tibble [1 x 1]>
#> 10 10 <tibble [41 x 1]> <tibble [1 x 1]>
#> # ... with 99,990 more rows
这会产生一个数据帧,每个 idx
的数据来自数据帧 A
,数据来自 data_a
,数据帧 B
的数据位于data_b
。完成此操作后,不必在 map_df
调用中针对每个案例过滤大数据框。
all_data %>%
map2_df(data_a, data_b, proc_one_group)
这是一些基准测试结果:
A = crossing(idx=1:1e3, asdf=seq(1:rpois(1,50)))
B = tibble(idx=sample(1:1e3, replace=TRUE), yet_more_stuff='whatever')
第一个想法是按照您的建议使用 split
,保持 split.A
和 split.B
的顺序相同。您可以使用 map2
遍历匹配列表:
myfun <- function(A,B) {
split.A <- split(A, A$idx)
splitsort.A <- split.A[order(names(split.A))]
splitsort.B <- map(names(splitsort.A), ~B[as.character(B$idx) == .x,])
ans <- map2(splitsort.A, splitsort.B, ~unique(.x$idx) == unique(.y$idx))
return(ans)
}
这是您当前使用的方法,使用dplyr::filter
OP <- function(A,B) {
ans <- map(unique(A$idx), ~unique(filter(A, idx==.x)$idx) == unique(filter(B, idx==.x)$idx))
return(ans)
}
这是相同的逻辑,但避免了 dplyr::filter
,与基础 R 子集
OP2 <- function(A,B) {
ans <- map(unique(A$idx), ~unique(A[A$idx==.x,]$idx) == unique(B[B$idx==.x,]$idx))
return(ans)
}
这使用了@JakeThompson 的方法(它似乎是当前方法中的赢家)
JT <- function(A,B) {
nest.A <- A %>% group_by(idx) %>% nest()
nest.B <- B %>% group_by(idx) %>% nest()
ans <- full_join(nest.A, nest.B, by="idx")
}
一些验证以确保某些函数的结果有意义
identical(OP(A,B), OP2(A,B))
# TRUE
E <- myfun(A,B)
any(E==FALSE)
# NA
F <- myfun(A,B)
any(F==FALSE)
# NA
identical(sum(E==TRUE, na.rm=TRUE), sum(F==TRUE, na.rm=TRUE))
# TRUE
基准测试结果
library(microbenchmark)
microbenchmark(myfun(A,B), OP(A,B), OP2(A,B), JT(A,B), times=2L)
# Unit: seconds
# expr min lq mean median uq max neval
# myfun(A, B) 3.164046 3.164046 3.254588 3.254588 3.345129 3.345129 2
# OP(A, B) 14.926431 14.926431 15.053662 15.053662 15.180893 15.180893 2
# OP2(A, B) 3.202414 3.202414 3.728423 3.728423 4.254432 4.254432 2
# JT(A, B) 1.330278 1.330278 1.378241 1.378241 1.426203 1.426203 2