将数据框转换为树形结构的列表

11

我有一个数据框,其中有两列代表分层树,包括父节点和子节点。

我想以一种方式转换数据结构,使其可以作为函数d3network的输入,该函数来自d3Network软件包。

这是我的数据框:

df <- data.frame(c("Canada","Canada","Quebec","Quebec","Ontario","Ontario"),c("Quebec","Ontario","Montreal","Quebec City","Toronto","Ottawa"))
names(df) <- c("parent","child")

我希望你能把它转换成这个结构

Canada_tree <- list(name = "Canada", children = list(
                                                list(name = "Quebec", 
                children = list(list(name = "Montreal"),list(name = "Quebec City"))),
                                                 list(name = "Ontario", 
                children = list(list(name = "Toronto"),list(name = "Ottawa")))))

我已经成功使用以下代码转换了这个特定的案例:

fill_list <- function(df,node) node <- as.character(node)if (is.leaf(df,node)==TRUE){
    return (list(name = node))
  }
  else {
    new_node = df[df[,1] == node,2]

    return (list(name = node, children =  list(fill_list(df,new_node[1]),fill_list(df,new_node[2]))))
  }

问题在于它仅适用于每个父节点恰好有两个子节点的树。您可以看到,我已将两个子节点(new_node[1]和new_node[2])硬编码为递归函数的输入。

我正在尝试找出一种方法,可以根据父节点的子节点数量多次调用递归函数。例如:

fill_list(df,new_node[1]),...,fill_list(df,new_node[length(new_node)])
我尝试了这3种可能性,但都没有成功: 第一种方法:创建一个包含所有函数和参数的字符串,然后进行求值。它会返回这个错误“could not find function fill_functional(df,new_node[1])”。这是因为在我调用函数时,我的函数并没有被创建好。
fill_functional <- function(df,node) {
  node <- as.character(node)
  if (is.leaf(df,node)==TRUE){
    return (list(name = node))
  }
  else {
    new_node = df[df[,1] == node,2]
    level <- length(new_node)
    xxx <- paste0("(df,new_node[",seq(level),"])")
    lapply(xxx,function(x) eval(call(paste("fill_functional",x,sep=""))))

  }
}

第二步:使用for循环。但是我只得到了根节点的子元素。

L <- list()
fill_list <- function(df,node) {
  node <- as.character(node)
  if (is.leaf(df,node)==TRUE){
    return (list(name = node))
  }
  else {
    new_node = df[df[,1] == node,2]

    for (i in 1:length(new_node)){
      L[i] <- (fill_list(df,new_node[i]))
    }

    return (list(name = node, children = L))
  }
}

第三步:创建一个函数,该函数填充一个列表以包含函数元素,并仅更改参数。但是我没有能够实现任何有趣的东西,而且我担心我会遇到与上面描述的第一次尝试相同的问题。

1个回答

12

这里是一个递归定义:

maketreelist <- function(df, root = df[1, 1]) {
  if(is.factor(root)) root <- as.character(root)
  r <- list(name = root)
  children = df[df[, 1] == root, 2]
  if(is.factor(children)) children <- as.character(children)
  if(length(children) > 0) {
    r$children <- lapply(children, maketreelist, df = df)
    }
  r
  }

canadalist <- maketreelist(df)

这将生成您所需的结果。此函数假设您传入的data.frame(或matrix)的第一列包含parent列,第二列包含child列。它还接受一个root参数,允许您指定起始点。默认情况下,它将从列表中的第一个父节点开始。

但是,如果您真的对树结构感兴趣,那么igraph包可能会引起您的兴趣。

library(igraph)
g <- graph.data.frame(df)
plot(g)

igraph中的加拿大树


如果感兴趣,请查看这个新的类似帖子 - zx8754
如果我试图避免在我的树中拥有额外的“子节点”,该怎么办? - Matt
已解决我的问题,使用 purr 包将函数更改为返回 flatten(r) - Matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接