1

I would like to make a small multiples plot of a network using ggraph. For each node in my network, I have two features, feat1 and feat2. I would like to visualize the feat1 for the entire network in one panel, and feat2 for the entire network in another panel. Each panel should contain all the nodes and edges in the network, in the same position. Eventually I will want to do this for 10-20 features, say.

To solve this kind of problem for tabular data, I would use pivot_longer() or gather to get the data into long format, and then I would pass the long data to ggplot() and use facet_wrap() or facet_grid(). ggraph and tidygraph don't offer graph-variants of pivot_longer() to my knowledge, so I'm not sure how to solve this problem. I'm looking for an idiomatic ggraph/tidygraph solution if there is one.

Here is a hacky solution that I would like to improve on.

library(ggraph)
#> Loading required package: ggplot2
library(tidygraph)
#> 
#> Attaching package: 'tidygraph'
#> The following object is masked from 'package:stats':
#> 
#>     filter
library(patchwork)
library(tidyr)

gr <- as_tbl_graph(highschool) |>
  mutate(
    feat1 = sample(LETTERS[1:3], n(), replace = TRUE),
    feat2 = sample(LETTERS[1:8], n(), replace = TRUE)
  ) |>
  mutate_at(vars(feat1, feat2), as.factor)

p1 <- ggraph(gr) +
  geom_edge_link(alpha = 0.1) +
  geom_node_point(aes(color = feat1)) +
  scale_color_brewer(type = "qual") +
  theme_graph()
#> Using "stress" as default layout

p2 <- ggraph(gr) +
  geom_edge_link(alpha = 0.1) +
  geom_node_point(aes(color = feat2)) +
  scale_color_brewer(type = "qual") +
  theme_graph()
#> Using "stress" as default layout

p1 + p2
#> Warning: Using the `size` aesthetic in this geom was deprecated in ggplot2 3.4.0.
#> ℹ Please use `linewidth` in the `default_aes` field and elsewhere instead.

I also tried to do solve my problem by using create_layout() to work with data in tabular data space, where I can use pivot_longer(). That attempt is as follows:

layout <- gr|>
  create_layout(layout = 'stress')

# it seems i can pass layouts to ggraph instead of graph objects
layout |>
  ggraph() +
  geom_edge_link(alpha = 0.1) +
  geom_node_point(aes(color = feat2)) +
  scale_color_brewer(type = "qual") +
  theme_graph()


# pivot the layout data
layout_long <- layout |>
  pivot_longer(
    contains("feat"),
    names_to = "feat_name",
    values_to = "feat_value"
  )

# pivoting messes up class and attribute information so fix it?
class(layout_long) <- class(layout)
attributes(layout_long) <- attributes(layout)

# this sadly doesn't work, presumably because the dimensions of the graph and the layout no longer match
layout_long |>
  ggraph() +
  geom_edge_link(alpha = 0.1) +
  geom_node_point() +
  scale_color_brewer(type = "qual") +
  theme_graph()
#> Error in `geom_node_point()`:
#> ! Problem while computing aesthetics.
#> ℹ Error occurred in the 2nd layer.
#> Caused by error in `check_aesthetics()`:
#> ! Aesthetics must be either length 1 or the same as the data (70)
#> ✖ Fix the following mappings: `x` and `y`

Created on 2023-02-28 with reprex v2.0.2.9000

alexpghayes
  • 673
  • 5
  • 17
  • I don't think your first example is hacky at all. You might want to put the code in an `lapply` or `Map` so that you can run the code for each node attribute without having to rewrite it several times, but the general approach looks good. – Allan Cameron Mar 01 '23 at 00:16

1 Answers1

0

My first thought was to set up a nodes data.frame, perform the pivot there, then feed that into a tbl_graph() call, which allows you to specify separate data frames for nodes and edges.

here follows your example.

grnodes <- data.frame(name=  unique(c(highschool$to,highschool$from))) |> mutate(
        feat1 = sample(LETTERS[1:3], n(), replace = TRUE),
        feat2 = sample(LETTERS[1:8], n(), replace = TRUE)
        ) |>
        #mutate_at(vars(feat1, feat2), as.factor) |> 
        pivot_longer(cols=c(feat1,feat2), names_to='feature', values_to='group')

gr <- tbl_graph(nodes=grnodes,edges=highschool)

ggraph(gr) +
  geom_edge_link(alpha=0.1) +
  geom_node_point(aes(color=group)) +
  scale_color_brewer(type = "qual") +
  theme_graph() + facet_nodes(.~feature) + geom_node_text(aes(label=name))

But this didn't work because ggraph + facet_nodes doesn't seem to work with a long-form data.frame of nodes - it seems to connect only some nodes by their corresponding edges even if the nodes have the same name. Might be worth opening an issue with the ggraph team