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

Multiple/nested morph() calls? #145

Open
chadbot opened this issue Sep 7, 2021 · 5 comments
Open

Multiple/nested morph() calls? #145

chadbot opened this issue Sep 7, 2021 · 5 comments
Labels
feature a feature request or enhancement

Comments

@chadbot
Copy link

chadbot commented Sep 7, 2021

Hello,

I'd like to temporarily convert a directed graph to a simple, undirected graph. I attempted this with the following code:

g %>%
  morph(to_undirected) %>%
  morph(to_simple) %>%
  activate(nodes) %>%
  mutate(community = group_fast_greedy()) %>%
  unmorph() %>%
  unmorph() 

However, this doesn't seem to work. (The documentation does not indicate that it should work, but it seemed worth a try.)

The code below does the trick by using convert twice. But since this is the kind of use case morph specifically addresses, it feels more like a workaround than a proper solution.

g %>%
  convert(to_undirected) %>%
  morph(to_simple) %>%
  activate(nodes) %>%
  mutate(community = group_fast_greedy()) %>%
  unmorph()  %>%
  convert(to_directed)

Is there a better way to conduct multiple morphs? And if not, might this be considered in a future update?

@gregleleu
Copy link

If it's any help, morphed graph are lists (in most cases at least), so I tried running the nested morph on the list elements. However this fails when unmorphing because of the way the merge back to the original data is made (multiple/overwritten .tidygraph_node_index / .tidygraph_edge_index aren't handled)

g %>%
  morph(to_undirected) %>%
  map(~{
    .x %>%
    morph(to_simple) %>%
    activate(nodes) %>%
    mutate(community = group_fast_greedy()) %>%
    unmorph()
  }) %>%
  unmorph() 

@gregleleu
Copy link

gregleleu commented Nov 29, 2021

Also tried the following approach in my case – nested morph to subgraph filtering on edges and then on nodes – by making my own morpher. In theory the morpher uses multiple calls to convert, which apply the filters (losing info but that's okay), wrapped in a morph/unmorph which makes sure we don't actually losing info.
But the merge back creates duplicates I think because internally the index columns get messed up

to_my_morpher <- function(graph) {
  subset <- 
    graph %>% 
    activate(nodes) %>% 
    convert(to_subgraph, [nodes filter]) %>% 
    activate(edges) %>% 
    convert(to_subgraph, [edges filter]) 
    
  list(
    subgraph = subset
  )
}


my_graph %>% 
  activate(nodes) %>% 
  morph(to_my_morpher) %>% 
  mutate(
    group_id = group_components() 
  ) %>% 
  unmorph()

@gregleleu
Copy link

Get it to work by manually protecting the index columns. Not very pretty.

to_my_morpher <- function(graph) {
  subset <- 
    graph %>% 

    ## protecting the indexes
    activate(nodes) %>% 
    mutate(.tidygraph_node_index_protect = .tidygraph_node_index) %>% 
    activate(edges) %>% 
    mutate(.tidygraph_edge_index_protect = .tidygraph_edge_index) %>% 

    ## doing the morph
    convert(to_subgraph, [nodes filter], subset_by = "nodes") %>% 
    convert(to_subgraph, [edges filter], subset_by = "edges") %>% 

    ## putting the indexes back in place
    activate(nodes) %>% 
    mutate(.tidygraph_node_index = .tidygraph_node_index_protect,
           .tidygraph_node_index_protect = NULL
           ) %>% 
    activate(edges) %>% 
    mutate(.tidygraph_edge_index = .tidygraph_edge_index_protect,
           .tidygraph_edge_index_protect = NULL
           )
    
  list(
    subgraph = subset
  )
}

@chadbot
Copy link
Author

chadbot commented Nov 29, 2021

Not exactly pretty, but you've provided some nice insight into how to think about this. Thanks for taking the time to explain, @gregleleu!

@gregleleu
Copy link

gregleleu commented Nov 29, 2021

Sure.
A more general solution could be added to the package, by merging the two approaches:

  • When morphing on a morphed graph, passing the morpher to a purrr::modify_depth, with depth 1 for the first, 2 for a lower depth etc.
  • Keeping this depth in the attributes
  • Changing mutate.morphed_tbl_graph and others to modify_depth to be applied on the lowest level
  • Using indexes which keep track of the "level" of nested morphs (.tidygraph_edge_index_1, .tidygraph_edge_index_2 ...) and unmorphing one level at a time with these indexes

On that last point, maybe just not letting subsequent morphs overwrite indexes is enough (and would make sense) but I'm not familiar enough with the internals of the package to know if that 100% works.

One caveat: the initial graph stays as an attribute to subsequent morphs, so it could get very big.

@thomasp85 thomasp85 added the feature a feature request or enhancement label Nov 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement
Projects
None yet
Development

No branches or pull requests

3 participants