Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Building a tree from a two columns data frame #63

Open
SamGG opened this issue Mar 5, 2019 · 11 comments
Open

Building a tree from a two columns data frame #63

SamGG opened this issue Mar 5, 2019 · 11 comments

Comments

@SamGG
Copy link

SamGG commented Mar 5, 2019

Hi,
I would like to build a tree from a data frame that looks like the following one. Is there a function or a piece of code? I would prefer not using a lot of dependencies. I already looked at ape and igraph, but didn't find my way. Any help is welcome.

data.frame(
    parent = c('Root', 'Root', 'Project A','Project A','Project B','Project B','Root'),
    child = c('Project A','Project B','Study A-A', 'Study A-B','Project B','Study B-A', 'Project C')
)
@trafficonese
Copy link
Contributor

This function should do the trick:

treelist <- vector("list", length(unique(df$parent)))
for (i in unique(df$parent)) {
  childs <- as.list(rep("", length(df[df$parent == i, "child"])))
  names(childs) <- df[df$parent == i, "child"]
  treelist[[i]] <- childs
}

Full Shiny Example:

df <- data.frame(
  parent = c('Root', 'Root', 'Project A','Project A','Project B','Project B','Root'),
  child = c('Project A','Project B','Study A-A', 'Study A-B','Project B','Study B-A', 'Project C')
)


treelist <- vector("list", length(unique(df$parent)))
for (i in unique(df$parent)) {
  childs <- as.list(rep("", length(df[df$parent == i, "child"])))
  names(childs) <- df[df$parent == i, "child"]
  treelist[[i]] <- childs
}


library(shiny)
library(shinyTree)

ui <- fluidPage(shinyTree("tree"))

server <- function(input, output, session) {
  output$tree <- renderTree({
    treelist
  })
}

shinyApp(ui, server)

@SamGG
Copy link
Author

SamGG commented Mar 5, 2019

Thanks for your reply.

The result is the following in my hands. There are 3 extra nodes at the top, and the result is not exactly indented as I am expecting.

image

Here is what I would like (Gimp edition).

image

I am stuck on the nested structure.

BTW I notice that I introduced an error in the example as Project B is repeated incorrectly. Please do find the correct example.

df <- data.frame(
  parent = c('Root', 'Root', 'Project A','Project A','Project B','Project B','Root'),
  child = c('Project A','Project B','Study A-A', 'Study A-B','Study B-A','Study B-B', 'Project C')
)

@trafficonese
Copy link
Contributor

trafficonese commented Mar 5, 2019

sry my bad, if you remove
treelist <- vector("list", length(unique(df$parent)))
with
treelist <- list()

you get the tree I intended. Apparently preallocating a list was no good idea. ;)

That still doesnt give you the tree you want, but with your new example, theres no easy logic to build the tree you want. If you can transform your data.frame to this for example it would be much easier to build a function and it would make more sense.

NOTE: I included Study C-A for Project C. If you really want it blank, you will have to fill it with "" for example and filter that out in the loop.

df <- data.frame(stringsAsFactors = FALSE,
                 parent = c('Root',      'Root',       'Root',      'Root',       'Root'),
                 child1 = c('Project A', 'Project A',  'Project B', 'Project B',  'Project C'),
                 child2 = c('Study A-A', 'Study A-B',  'Study B-A',  'Study B-B', 'Study C-A')
)

Then the function would be quite similar:

library(shiny)
library(shinyTree)

treelist <- list()
for (i in unique(df$child1)) {
  childs <- as.list(rep("", length(df[df$child1 == i, 1])))
  names(childs) <- df[df$child1 == i, "child2"]
  treelist[[i]] <- childs
}
treelist <- list("Root" = treelist)


## Shiny Example ####
ui <- fluidPage(shinyTree("tree"))
server <- function(input, output, session) {
  output$tree <- renderTree({
    treelist
  })
}

shinyApp(ui, server)

@SamGG
Copy link
Author

SamGG commented Mar 5, 2019

Thanks for the update. Really helpful. I will work on that this evening.
Thanks for your time. I will update the issue when I find the exact solution.

@YsoSirius
Copy link
Contributor

If you want a more general approach, where "Project-C" can be an empty node and that works with multiple "root"-nodes you could use this approach:

## Project-C empty node
df <- data.frame(stringsAsFactors = FALSE,
                 parent = c('Root',      'Root',       'Root',      'Root',       'Root'),
                 child1 = c('Project A', 'Project A',  'Project B', 'Project B',  'Project C'),
                 child2 = c('Study A-A', 'Study A-B',  'Study B-A',  'Study B-B', '')
)
## Multiple root-nodes
# df <- data.frame(stringsAsFactors = FALSE,
#                  parent = c('Root',      'Root',       'Root',      'Root',       'Root',       'Root1',      'Root1',    'Root1',      'Root1'),
#                  child1 = c('Project A', 'Project A',  'Project B', 'Project B',  'Project C',  'Project A',  'Project A','Project B', 'Project B' ),
#                  child2 = c('Study A-A', 'Study A-B',  'Study B-A',  'Study B-B', '',           'Study A-A',  'Study A-B','Study B-A',  'Study B-B')
# )

treelist <- list()
for (j in unique(df$parent)) {
  tmp <- df[df$parent == j, ]
  subtreelist <- list()
  for (i in unique(tmp$child1)) {
    if (length(tmp[tmp$child1 == i, "child2"]) == 1 && tmp[tmp$child1 == i, "child2"] == "") {
      childs <- i
    } else {
      childs <- as.list(rep("", length(tmp[tmp$child1 == i, 1])))
      names(childs) <- tmp[tmp$child1 == i, "child2"]    
    }
    subtreelist[[i]] <- childs
  }
  treelist[[j]] <- subtreelist
}

@SamGG
Copy link
Author

SamGG commented Mar 10, 2019

Thanks for your reply. In fact, I want the inverse transformation of the get_FlatList function, without any constraint. Here is how I exploit this function to get a data.frame view of the tree. This allows me to link the tree to some other information. Related to question #64, I would to use this data.frame structure to update the tree presented by shinyTree.

# This is the toy tree
> dput(tree)
list(root1 = structure(0, id = "1"), root2 = structure(list(SubListA = structure(list(
    leaf1 = structure(0, id = "4"), leaf2 = structure(0, id = "5"), 
    leaf3 = structure(0, id = "6")), id = "3"), SubListB = structure(list(
    leafA = structure(0, id = "8"), leafB = structure(0, id = "9")), id = "7"), 
    SubListC = structure(list(leaf1 = structure(0, id = "11"), 
        leaf2 = structure(0, stselected = TRUE, id = "12"), leaf3 = structure(0, id = "13")), stopened = TRUE, id = "10")), stopened = TRUE, id = "2"))
# One line conversion to data.frame
> as.data.frame(t(sapply(shinyTree:::get_flatList(tree), unlist)), stringsAsFactors = FALSE)
   id     text parent state.opened state.selected data.id
1   1    root1      #        FALSE          FALSE       1
2   2    root2      #         TRUE          FALSE       2
3   3 SubListA      2        FALSE          FALSE       3
4   4    leaf1      3        FALSE          FALSE       4
5   5    leaf2      3        FALSE          FALSE       5
6   6    leaf3      3        FALSE          FALSE       6
7   7 SubListB      2        FALSE          FALSE       7
8   8    leafA      7        FALSE          FALSE       8
9   9    leafB      7        FALSE          FALSE       9
10 10 SubListC      2         TRUE          FALSE      10
11 11    leaf1     10        FALSE          FALSE      11
12 12    leaf2     10        FALSE           TRUE      12
13 13    leaf3     10        FALSE          FALSE      13
# Is there any function to do the reverse conversion?

@msgoussi
Copy link

msgoussi commented May 4, 2019

May I ask why you use :::: instead of :: and why there is no documentation help for this function?

@trafficonese
Copy link
Contributor

The function get_flatList is not exported and just used internally by updateTree. Therefore there is no documentation for it.

You can still access the internal package functions with :::.

@patzaw
Copy link

patzaw commented Aug 8, 2019

inBranch <- function(branch){
   if(identical(branch, "")){
      return(NULL)
   }else{
      toRet <- unique(unlist(lapply(branch, inBranch)))
      toRet <- unique(c(toRet, names(branch)))
      return(toRet)
   }
}

getTree <- function(children){
   stopifnot(
      all(c("parent", "child") %in% colnames(children)),
      is.character(children$parent),
      is.character(children$child),
      all(children$child != children$parent)
   )
   leaf <- children %>% filter(!child %in% parent)
   leaf <- split(leaf$child, leaf$parent)
   tree <- lapply(
      leaf,
      function(x){
         toRet <- as.list(rep("", length(x)))
         names(toRet) <- x
         return(toRet)
      }
   )
   children <- children %>% filter(child %in% parent)
   while(nrow(children)>0){
      leaf <- children %>% filter(!child %in% parent)
      leaf <- split(leaf$child, leaf$parent)
      newTree <- lapply(
         names(leaf),
         function(x) c(tree[leaf[[x]]], tree[[x]])
      )
      names(newTree) <- names(leaf)
      tree <- c(tree[setdiff(names(tree), names(newTree))], newTree)
      children <- children %>% filter(child %in% parent)
   }
   toRm <- unique(unlist(lapply(tree, inBranch)))
   tree <- tree[setdiff(names(tree),toRm)]
   return(tree)
}

This function works in my hand.
However, your original example has a loop (loop: an ancestor is also a descendant of its descendant): there is one row in which child=parent (Project B). This case is not handled by my function and I don't think it should be. The parent/child table should be a table of identifiers to avoid ambiguity that cannot be handled if you have complex nested trees with descendants that can have multiple ancestors.

@woec
Copy link

woec commented Oct 7, 2019

Is it possible, to use the saved result of get_Flatlist to restore the selections of a tree by means of R scripting?

@trafficonese
Copy link
Contributor

It is not possible with R only, but with some JavaScript it would be.
I wrote down one option here: https://stackoverflow.com/questions/57952813/save-and-restore-selections-in-a-shinytree/57959221#57959221

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants