以下是与父子关系相关的数据框:
parent_id child_id
1 1 2
2 2 3
3 3 4
目标是实现以下内容,即扩展先前数据框的版本,其中所有后代(子女、孙子女等)都分配给每个父级(包括父/子本身):
parent_id child_id
1 1 1
2 1 2
3 1 3
4 1 4
5 2 2
6 2 3
7 2 4
8 3 3
9 3 4
10 4 4
我有一个问题:在R中,最快的实现方法是什么(或其中之一)? 我已经尝试了各种方法 - 从for循环、SQL递归到使用
igraph
(如此处所述)。它们都相当慢,有些在处理更多组合时也容易崩溃。下面是使用
sqldf
和igraph
的示例,对比了一个稍大的数据框。library(sqldf)
library(purrr)
library(dplyr)
library(igraph)
df <- data.frame(parent_id = 1:1000L)
df$child_id <- df$parent_id + 1L
# SQL recursion
sqlQuery <- 'with recursive
dfDescendants (parent_id, child_id)
as
(select parent_id, child_id from df
union all
select d.parent_id, s.child_id from dfDescendants d
join df s
on d.child_id = s.parent_id)
select distinct parent_id, parent_id as child_id from dfDescendants
union
select distinct child_id as parent_id, child_id from dfDescendants
union
select * from dfDescendants;'
sqldf(sqlQuery)
# igraph with purrr
df_g = graph_from_data_frame(df, directed = TRUE)
map(V(df_g), ~ names(subcomponent(df_g, .x, mode = "out"))) %>%
map_df(~ data.frame(child_id = .x), .id = "parent_id")
基准测试(不包括在 sqldf
中创建查询和在 igraph
中转换为图形):
set.seed(23423)
microbenchmark::microbenchmark(
sqldf = sqldf(sqlQuery),
tidyigraph = map(V(df_g), ~ names(subcomponent(df_g, .x, mode = "out"))) %>%
map_df(~ data.frame(child_id = .x), .id = "parent_id"),
times = 5
)
# Unit: seconds
# expr min lq mean median uq max neval
# sqldf 7.815179 8.002836 8.113392 8.084038 8.315207 8.349701 5
# tidyigraph 5.784239 5.806539 5.883241 5.889171 5.964906 5.971350 5
subcomponent()
一旦构建了图形,应该会很快,但目前任何图形遍历都有一个图形设置成本,因此对于每个顶点重复调用subcomponent
不是理想的选择。(3)您可以使用ego
函数一次完成,将“order”设置为顶点数。但我刚刚查看了C代码,它实际上并没有避免设置成本。 - Szabolcsas_edgelist(connect(df_g, order=length(V(df_g)), mode="in"))
看起来更快(但是在这些设置下不会得到自环)。 - user20650order=vcount(df_g)
。仔细查看了 C 源代码后,我非常惊讶它的速度能够快那么多。不过我已经为加速它而打开了一个问题:https://github.com/igraph/igraph/issues/1954 - Szabolcsigraph
配置选项可以解决它(实际上一开始我只是认为孤立的子循环没有被处理)。 - arg0naut91