Network Visualization II

Social Network Analysis

Termeh Shafie

From last time

# load and manipulate data
data("starwars")
sw1 <- starwars[[1]]
sw_palette <- c("#1A5878", "#C44237", "#AD8941", "#E99093", "#50594B")
V(sw1)$interactions <- graph.strength(sw1) 

#plot
ggraph(graph = sw1,layout = "stress") + 
  geom_edge_link0(edge_colour = "grey25",
                  aes(edge_linewidth = weight)) +
  geom_node_point(shape = 21, color = "black",stroke = 1,
                  aes(fill = sex,size = interactions)) +
  geom_node_text(color = "black", size = 4, repel = FALSE, 
                 aes(filter = (interactions>=65),label = name))+
  scale_edge_width(range = c(0.1,1.5),guide = "none")+
  scale_size(range = c(3,10),guide = "none")+
  scale_fill_manual(values = sw_palette, na.value = "grey",name = "")+
  coord_fixed()+
  theme_graph() +
  theme(legend.position = "bottom") +
  guides(fill = guide_legend(override.aes = list(size=6)))

From last time

Go beyond the standard layout

layout stress is sufficient for most network visualization tasks

Ego centric layout

Emphasize the position of certain nodes in the network.

ggraph(sw1,layout = "focus",focus = 19)+
  draw_circle(col = "#00BFFF", use = "focus",max.circle = 3)+
  geom_edge_link0(edge_colour = "grey25",edge_alpha = 0.5)+
  geom_node_point(shape = 21,size = 5,fill = "grey66")+
  geom_node_text(aes(filter = (name=="ANAKIN"),label = name))+
  theme_graph()+
  coord_fixed()


  • focus=... : id to be put in the center (other nodes are on concentric circles around it)
  • draw_circle: draw the concentric circles

Ego centric layout

Centrality layout


concentric circle layout according to a centrality index

strength <- graph.strength(sw1)
ggraph(sw1,layout = "centrality",cent = strength)+
  draw_circle(col = "#00BFFF", use = "cent")+
  geom_edge_link0(edge_colour = "grey25",edge_alpha = 0.5)+
  geom_node_point(shape = 21,size = 5,fill = "grey66")+
  geom_node_text(aes(filter = (strength>=45),label = name),repel = TRUE)+
  theme_graph()+
  coord_fixed()

Centrality layout

Backbone layout

layout_as_backbone() can help emphasize hidden group structures

g <- sample_islands(9,40,0.4,15)
g <- simplify(g)
V(g)$grp <- as.character(rep(1:9,each = 40))


try the standard first

ggraph(g,layout = "stress")+
  geom_edge_link0(edge_colour = "black",edge_linewidth = 0.1, edge_alpha = 0.5)+
  geom_node_point(shape = 21, size = 3, aes(fill = grp))+
  scale_fill_brewer(palette = "Set1")+
  theme_graph()+
  theme(legend.position = "none")

Backbone layout

Backbone layout



try to reveal the hidden group structure with layout="backbone"

ggraph(g, layout = "backbone")+
  geom_edge_link0(edge_colour = "black",edge_linewidth = 0.1, edge_alpha = 0.5)+
  geom_node_point(shape = 21,size = 3, aes(fill = grp))+
  scale_fill_brewer(palette = "Set1")+
  theme_graph()+
  theme(legend.position = "none")

Backbone layout

Backbone layout

Facebook friendships of a university.
Node colour corresponds to dormitory of students

Large networks

layout="sparse_stress" and layout="pmds" work well for up to 50k nodes

graph stress sparsestress(50) sparsestress(100) drl(igraph) mds(igraph) pivotmds(50) pivotmds(100)
1138bus (n=1138, m=1458) 2.6s 0.4s 0.7s 1.7s 1.4s 0.05s 0.08s
3elt (n=4720, m=13722) 113s 3.4s 5.2s 8.4s 95s 1s 1.1s
power grid (n=4941, m=6594) 134s 3.4s 5.6s 6.9s 114s 1s 1.1s
pesa (n=11738 m=67828) - 18.3s 26.5s 21s - 6.3s 6.8s
cond-mat(n=13861 m=44619) - 22.9s 33.5s 24.8s - 8.0s 8.7s

Layout summary

most useful layouts
layout = "stress": all purpose layout algorithm
layout = "focus": ego-centric type layouts
layout = "centrality": concentric centrality layout
layout = "backbone": emphasize a group structure (if it exists)
layout = "sparse_stress": large networks

Miscellaneous

Do not recompute layout continuously

lay <- create_layout(g,"stress")

ggraph(lay) + 
  geom_edge_link0() +
  geom_node_point()

But need to recompute the layout if attributes change!

lay <- layout_with_stress(g)

ggraph(g, "manual", x = xy[,1],y = xy[,2]) + 
  geom_edge_link0() +
  geom_node_point()

Longitudinal networks

A series of snapshots visualized with individual nodes easy to trace


Example: 50 actor excerpt from the Teenage Friends and Lifestyle Study


layout_as_dynamic() is used to visualize with fixed node positions

xy <- layout_as_dynamic(s50, alpha = 0.2)


Use ggraph in conjunction with patchwork to produce static plots

Longitudinal networks

pList <- vector("list", length(s50))

for (i in 1:length(s50)) {
    pList[[i]] <- ggraph(s50[[i]], layout = "manual", x = xy[[i]][, 1], y = xy[[i]][, 2]) +
        geom_edge_link0(edge_linewidth = 0.6, edge_colour = "grey66") +
        geom_node_point(shape = 21, aes(fill = as.factor(smoke)), size = 6) +
        geom_node_text(label = 1:50, repel = FALSE, color = "white", size = 4) +
        scale_fill_manual(
            values = c("forestgreen", "grey25", "firebrick"),
            guide = ifelse(i != 2, "none", "legend"),
            name = "smoking",
            labels = c("never", "occasionally", "regularly")
        ) +
        theme_graph() +
        theme(legend.position = "bottom") +
        labs(title = paste0("Wave ", i))
}

wrap_plots(pList)

Longitudinal networks

Can we do better?

yes, we can animate the changes…

Longitudinal networks

Use the ggforce

The ggforce package works pretty nicely with ggraph

library(ggforce)

Use the ggforce

ggraph(g, #|
    layout = "manual",
    x = bb$xy[, 1],
    y = bb$xy[, 2]
) +
    geom_edge_link0(aes(col = col), width = 0.2) +
    geom_node_point(aes(fill = grp), shape = 21, size = 3) +
    geom_mark_hull(
        aes(x, y, group = grp, fill = grp),
        concavity = 4,
        expand = unit(2, "mm"),
        alpha = 0.25
    ) +
    scale_color_brewer(palette = "Set1") +
    scale_fill_brewer(palette = "Set1") +
    scale_edge_color_manual(values = c(rgb(0, 0, 0, 0.3), rgb(0, 0, 0, 1))) +
    theme_graph() +
    theme(legend.position = "none")

Use the ggforce

you can also add labels to your cluster

Use the ggforce

you can also add labels to your cluster

ggraph(g, #|
    layout = "manual",
    x = bb$xy[, 1],
    y = bb$xy[, 2]
) +
    geom_edge_link0(aes(col = col), width = 0.2) +
    geom_node_point(aes(fill = grp), shape = 21, size = 3) +
    geom_mark_hull(
        aes(x, y, group = grp, fill = grp, label = grp),
        concavity = 4,
        expand = unit(2, "mm"),
        alpha = 0.25
    ) +
    scale_color_brewer(palette = "Set1") +
    scale_fill_brewer(palette = "Set1") +
    scale_edge_color_manual(values = c(rgb(0, 0, 0, 0.3), rgb(0, 0, 0, 1))) +
    theme_graph() +
    theme(legend.position = "none")

Use the ggforce

want to avoid node overlaps?
use geom_node_voronoi() orginiated from geom_voronoi_tile()

Small tricks for common problems

How can I achieve that my directed edges stop at the node border, independent from the node size?

Small tricks for common problems

How can I achieve that my directed edges stop at the node border, independent from the node size?

Small tricks for common problems

## this function is borrowed from the ambient package
normalise <- function(x, from = range(x), to = c(0, 1)) {
    x <- (x - from[1]) / (from[2] - from[1])
    if (!identical(to, c(0, 1))) {
        x <- x * (to[2] - to[1]) + to[1]
    }
    x
}

## map to the range you want
V(g)$degree <- normalise(V(g)$degree, to = c(3, 11))

ggraph(g, "stress") +
    geom_edge_link(
        aes(end_cap = circle(node2.degree + 2, "pt")),
        edge_colour = "grey25",
        arrow = arrow(
            angle = 10,
            length = unit(0.15, "inches"),
            ends = "last",
            type = "closed"
        )
    ) +
    geom_node_point(aes(size = I(degree)), col = "grey66") +
    theme_graph()

Small tricks for common problems

How can I lower opacity of nodes without making edges visible underneath?

Small tricks for common problems

How can I lower opacity of nodes without making edges visible underneath?

Small tricks for common problems

ggraph(g, "stress") +
    geom_edge_link(edge_colour = "grey66") +
    geom_node_point(size = 10, col = "white") +
    geom_node_point(
        aes(alpha = degree),
        size = 10,
        col = "red",
        show.legend = FALSE
    ) +
    theme_graph()

Small tricks for common problems

How can I enhance readability of node labels in hairball graphs?

Small tricks for common problems

How can I enhance readability of node labels in hairball graphs?

Small tricks for common problems

ggraph(g, "stress") +
    geom_edge_link0(aes(edge_color = weight, edge_linewidth = weight), show.legend = FALSE) +
    geom_node_point(size = 8, color = "#44a6c6") +
    shadowtext::geom_shadowtext(aes(x, y, label = name), color = "black", size = 4, bg.colour = "white") +
    scale_edge_color_continuous(low = "grey66", high = "black") +
    scale_edge_width(range = c(0.1, 0.5)) +
    theme_graph() +
    coord_fixed()

Edgebundling & flow maps

Using the package edgebundle