Network visualizations become common in science in practice. But as networks are increasing in size, common visualizations become less useful. There are simply too many nodes/vertices and links/edges. Often visualization efforts end up in producing "hairballs".
Some new approaches have been proposed to overcome this issue, e.g.:
Edge bundling:
http://vis.stanford.edu/papers/divided-edge-bundling or
https://gephi.org/tag/edge-bundling/
Hierarchial edge bundling:
http://graphics.cs.illinois.edu/sites/graphics.dev.engr.illinois.edu/files/edgebundles.pdf
Group Attributes Layout:
http://wiki.cytoscape.org/Cytoscape_3/UserManual
How to make grouped layout in igraph?
I am sure that there are many more approaches. Thus, my question is:
How to overcome the hairball issue, i.e. how to visualize large networks by using R?
Here is some code that simulates an exemplary network:
# Load packages
lapply(c("devtools", "sna", "intergraph", "igraph", "network"), install.packages)
library(devtools)
devtools::install_github(repo="ggally", username="ggobi")
lapply(c("sna", "intergraph", "GGally", "igraph", "network"),
require, character.only=T)
# Set up data
set.seed(123)
g <- barabasi.game(1000)
# Plot data
g.plot <- ggnet(g, mode = "fruchtermanreingold")
g.plot
This questions is related to
Visualizing Undirected Graph That's Too Large for GraphViz?. However, here I am searching not for general software recommendations but for concrete examples (using the data provided above) which techniques help to make a good visualization of a large network by using R (comparable to the examples in this thread: R: Scatterplot with too many points).
Another way to visualize very large networks is with BioFabric (www.BioFabric.org), which uses horizontal lines instead of points to represent the nodes. Edges are then shown using vertical line segments. A quick D3 demo of this technique is shown at: http://www.biofabric.org/gallery/pages/SuperQuickBioFabric.html.
BioFabric is a Java application, but a simple R version is available at: https://github.com/wjrl/RBioFabric.
Here is a snippet of R code:
# You need 'devtools':
install.packages("devtools")
library(devtools)
# you need igraph:
install.packages("igraph")
library(igraph)
# install and load 'RBioFabric' from GitHub
install_github('RBioFabric', username='wjrl')
library(RBioFabric)
#
# This is the example provided in the question:
#
set.seed(123)
bfGraph = barabasi.game(1000)
# This example has 1000 nodes, just like the provided example, but it
# adds 6 edges in each step, making for an interesting shape; play
# around with different values.
# bfGraph = barabasi.game(1000, m=6, directed=FALSE)
# Plot it up! For best results, make the PDF in the same
# aspect ratio as the network, though a little extra height
# covers the top labels. Given the size of the network,
# a PDF width of 100 gives us good resolution.
height <- vcount(bfGraph)
width <- ecount(bfGraph)
aspect <- height / width;
plotWidth <- 100.0
plotHeight <- plotWidth * (aspect * 1.2)
pdf("myBioFabricOutput.pdf", width=plotWidth, height=plotHeight)
bioFabric(bfGraph)
dev.off()
Here is a shot of the BioFabric version of the data provided by the questioner, though networks created with values of m > 1 are more interesting. The inset detail shows a close-up of the upper left corner of the network; node BF4 is the highest-degree node in the network, and the default layout is a breadth-first search of the network (ignoring edge directions) starting from that node, with neighboring nodes traversed in order of decreasing node degree. Note that we can immediately see that, for example, about 60% of node BF4's neighbors are degree 1. We can also see from the strict 45-degree lower edge that this 1000-node network has 999 edges, and is therefore a tree.
Full disclosure: BioFabric is a tool that I wrote.
That's an interesting question, I didn't know most of the tools you listed, so thanks. You can add HivePlot to the list. It's a deterministic method consisting in projecting nodes on a fixed number of axes (usually 2 or 3). Look a the linked page, there're many visual examples.
It works better if you have a categorical nodal attribute in your dataset, so that you can use it to select which axis a node goes to. For instance, when studying the social network of a university: students on one axis, teachers on another and administrative staff on the third. But of course, it can also work with a discretized numerical attribute (eg. young, middle-aged and older people on their respective axes).
Then you need another attribute, and it has to be numerical (or at least ordinal) this time. It is used to determine the position of a node on its axis. You can also use some topological measure, such as degree or transitivity (clustering coefficient).
(source: hiveplot.net)
The fact the method is deterministic is interesting, because it allows comparing different networks representing distinct (but comparable) systems. For example, you can compare two universities (provided you use the same attributes/measures to determine axes and position). It also allows describing the same network in various ways, by choosing different combinations of attributes/measures to generate the visualization. This is the recommanded way of visualizing a network, actually, thanks to a so-called hive panel.
Several softwares able of generating those hive plots are listed in the page I mentioned at the beginning of this post, including implementations in Java and R.
I've been dealing with this problem recently. As a result, I've come up with another solution. Collapse the graph by communities/clusters. This approach is similar to the third option outlined by the OP above. As a word of warning, this approach will work best with undirected graphs. For example:
library(igraph)
set.seed(123)
g <- barabasi.game(1000) %>%
as.undirected()
#Choose your favorite algorithm to find communities. The algorithm below is great for large networks but only works with undirected graphs
c_g <- fastgreedy.community(g)
#Collapse the graph by communities. This insight is due to this post http://stackoverflow.com/questions/35000554/collapsing-graph-by-clusters-in-igraph/35000823#35000823
res_g <- simplify(contract(g, membership(c_g)))
The result of this process is the below figure, where the vertices' names represent community membership.
plot(g, margin = -.5)
The above is clearly nicer than this hideous mess
plot(r_g, margin = -.5)
To link communities to original vertices you will need something akin to the following
mem <- data.frame(vertices = 1:vcount(g), memeber = as.numeric(membership(c_g)))
IMO this is a nice approach for two reasons. First, it can in theory deal with any size graph. The process of finding communities can be continuously repeated on collapsed graphs. Second, adopting a interactive approach would yield very readable results. For example, one can imagine the user being able to click on a vertex in the collapsed graph to expand that community revealing all of its original vertices.
I have looked around and found no good solution. My approach has been to remove nodes and play with edge transparency. It is more of a design solution rather than a technical one, but I've been able to plot gephi-like networks of up to 50,000 edges without much complications on my laptop.
with your example:
plot(simplify(g), vertex.size= 0.01,edge.arrow.size=0.001,vertex.label.cex = 0.75,vertex.label.color = "black" ,vertex.frame.color = adjustcolor("white", alpha.f = 0),vertex.color = adjustcolor("white", alpha.f = 0),edge.color=adjustcolor(1, alpha.f = 0.15),display.isolates=FALSE,vertex.label=ifelse(page_rank(g)$vector > 0.1 , "important nodes", NA))
Example of twitter mentions network with 30,000 edges:
Yet another interesting package is networkD3. There are a myriad of means of representing graphs within this library. In particular, I find the forceNetwork an interesting option. It is interactive and therefore allows you to really explore your network. It is great for EDA, but it maybe too "wiggly" for final work.
I tired this pacakge. It's very fast.
if (!require("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("netbiov")
https://www.bioconductor.org/packages/release/bioc/html/netbiov.html
Examples:
https://www.bioconductor.org/packages/release/bioc/vignettes/netbiov/inst/doc/netbiov-intro.pdf
Related
I am trying to find a way to plot a disease transmission tree that allows me to:
plot the tree over a timeline (a timeline spanning 2 months)
specify the shape and colour of the nodes in the tree (so that you can easily identify which nodes belong to the same household for example)
format the link between the nodes (dashed lines, two way arrows, solid lines...etc.)
plot "stray branches" that aren't linked to the root/parent node.
The dataset I am working with is relatively small (22 nodes) so I don't mind working with a package that is a bit fiddly!
I have thought about using phylogeny trees, but I'm uncertain whether they will allow me to plot stray nodes. Which package would be most suitable for this task?
Thanks!
Try DiagrammeR. I don't have much experience, but it did what I needed to do and I know I barely scratched the surface of what it can do.
I am starting a new project in python (to be used through jupyter-notebooks), where I'll need to visualise some hierarchically clustered graphs.
I have looked for existing packages, but so far I am not convinced by what I have seen.
I am not interested in the clustering process in itself, because this will be another part of the project and I know (roughly) how the graphs will be built up progressivelly.
What I am looking for are:
an appropriate data structure for storing hierarchically clustered graphs,
visualisation tools that would allow to represent the graph on a map (based on X and Y coordinates of the nodes) and either represent the subparts of the clusters, or simplify the clusters depending on their type or depth in the graph structure,
ideally, bring some interactivity, for example the ability to zoom-in or-out, or click on some clustered nodes to expand the nodes that were hidden in the cluster.
It looks pretty specific and despite some cool packages I have seen I am not sure which one would help without having too much to reimplement. So far, NetworkX looks like a cool starting point, especially with some D3.js (as shown here), but it is still far from what I have in mind.
Any advice about where to start digging?
Thanks a lot.
Gautier
For Python, Seaborn's clustermaps are nice. Seaborn is mainly meant to be used with Pandas dataframes; however, the documentation for clustermap says it can be rectangular data, and so I think it means other arrays will wor.
See also:
Dendrogram with heat map
SciPy Hierarchical Clustering and Dendrogram Tutorial
Hierarchical Clustering in Python
Problem definition
I need to produce a number of specific graphs, and on these graphs, highlight subsets of vertices (nodes) by drawing a contour/polygon/range around or over them (see image below).
A graph may have multiple of these contours/ranges, and they may overlap, iff one or more vertices belong to multiple subsets.
Given a graph of N vertices, any subset may be of size 1..N.
However, vertices not belonging to a subset must not be inside the contour (as that would be misleading, so that's priority no. 1). This is gist of my problem.
All these graphs happen to have the property that the ranges are continuous, as the data they represent covers only directly connected subsets of vertices.
All graphs will be undirected and connected (no unconnected vertices will ever be plotted).
Reproducible attempts
I am using R and the igraph package. I have already tried some solutions, but none of them work well enough.
First attempt, mark.groups in plot.igraph:
library(igraph)
g = make_graph("Frucht")
l = layout.reingold.tilford(g,1)
plot(g, layout=l, mark.groups = c(1,3,6,12,5), mark.shape=1)
# bad, vertex 11 should not be inside the contour
plot(g, layout=l, mark.groups = c(1,6,12,5,11), mark.shape=1)
# 3 should not be in; image below
# just choosing another layout here is not a generalizable solution
The plot.igraph calls igraph.polygon, which calls convex_hull (also igraph), which calls xspline. The results is, from what I understand, something called a convex hull (which otherwise looks very nice!), but for my purposes that is not precise enough, covering vertices that should not be covered.
Second attempt with contour. So I tried implementing my own version, based on the solution suggested here:
library(MASS)
xx <- runif(5, 0, 1);yy <- abs(xx)+rnorm(5,0,0.2)
plot(xx,yy, xlim=c( min(xx)-sd(xx),max(xx)+sd(xx)), ylim =c( min(yy)-sd(yy), max(yy)+sd(yy)))
dens2 <- kde2d(xx, yy, lims=c(min(xx)-sd(xx), max(xx)+sd(xx), min(yy)- sd(yy), max(yy)+sd(yy) ),h=c(bandwidth.nrd(xx)/1.5, bandwidth.nrd(xx)/ 1.5), n=50 )
contour(dens2, level=0.001, col="red", add=TRUE, drawlabels=F)
The contour plot looks in principle like something I could use, given enough tweaking of the bandwidth and level values (to make the contour snug enough so it doesn't cover any points outside the group). However, this solution has the drawback that when the level value is too small, the contour breaks (doesn't produce a continuous area) - so if I would go that way, controlling for continuity (and determining good bandwidth/level values on the fly) automatically should be implemented. Another problem is, I cannot quite see how could I plot the contour over the plots produced by igraph: the layout.* commands produce what looks like a coordinate matrix, but the coordinates do not match the axis coordinates on the plot:
# compare:
layout.reingold.tilford(g,1)
plot(g, layout=l, axes=T)
The question:
What would be a better way to achieve the plotting of such ranges on graphs (ideally igraphs) in R that would meet the criteria outlined above - ranges that include only the vertices that belong to their subset and exclude all else - while being continous ranges?
The solution I am looking for should be scalable to graphs of different sizes and layouts that I may need to create (so hand-tweaking each graph by hand using e.g. tkplot is not a good solution). I am aware that on some graphs with some vertex groups, meeting both the criteria will indeed be impossible in practise, but intuitively it should be possible to implement something that still works most of the time with smallish (10..20 vertices) and not-too-complex graphs (ideally it would be possible to detect and give a warning if a perfectly fitting range could not be plotted). Either an improvement of the mark.groups approach (not necessarily within the package, but using the hull-idea mentioned above), or something with contour or a similar suitable function, or suggesting something else entirely would be welcome, as long as it works (most of the time).
Update stemming from the discussion: a solution that only utilizes functions of core R or CRAN packages (not external software) is desirable, since I will eventually want to incorporate this functionality in a package.
Edit: specified the last paragraph as per the comments.
The comment area is not long enough to fit my answer there, so I'm putting this here, although I'd rather post it as a comment as it is not a full solution.
Quite a long throw, but the first thing that popped into my mind is support vector machines. The idea would be that you construct a support vector machine classifier that classifies your points into two groups (in or out) based on the coordinates of the vertices, using some non-linear kernel function (I would try the radial basis function). Then you plot the separating hyperplane of the trained support vector machine. One drawback is that the area that you obtain this way might be unbounded (i.e. go to infinity in some directions), so this idea definitely requires some further thinking, but at least that's one possible direction to go.
I am trying to plot quite large and dense networks (dput here). All I end up with is a bunch of overlapping dots, which does not really give me a sense of the structure or density of the network:
library(sna)
plot(data, mode = "fruchtermanreingold")
However, I have seen plots which utilizes fading to visualize the degree to which points overlap, e.g.:
How can I implement this "fading" in a plot of a graph?
Here's one way:
library(sna)
library(network)
source("modifieddatafromgist.R")
plot.network(data,
vertex.col="#FF000020",
vertex.border="#FF000020",
edge.col="#FFFFFF")
First, I added a data <- to the gist so it could be sourced.
Second, you need to ensure the proper library calls so the object classes are assigned correctly and the proper plot function will be used.
Third, you should use the extra parameters for the fruchtermanreingold layout (which is the default one for plot.network) to expand the area and increase the # of iterations.
Fourth, you should do a set.seed before the plot so folks can reproduce the output example.
Fifth, I deliberately removed cruft so you can see the point overlap, but you can change the alpha for both edges & vertices (and you should change the edge width, too) to get the result you want.
There's a ton of help in ?plot.network to assist you in configuring these options.
I am trying to build graphs using tree-like data, where nodes typically split into >2 edges. I have tried various layouts, and I see that the layout.reingold.tilford parameter will generate tree-like graphs with non-bifurcating data. However the outputs are not particularly attractive. I would rather use something like the layout.lgl or layout.kamada.kawai since these produce more radial structures. I cannot see how to change the parameters in R such that these trees have no overlapping edges though. Is this possible?
I imported a simple data file in Pajek format, with 355 nodes and 354 edges. I'm currently printing it using:
plot.igraph(g,vertex.size=3,vertex.label=NA,layout=layout.lgl)
This gives me an output like this, which is nice, but still has overlapping edges. I have read that you can manually fix this using tkplot, or another program like cytoscape, however I have quite a few of these to build, and the size of them makes manual correction a hassle.
Many thanks.
Just want to add a comment but my rep is too low. The method that #bdemarest posted does not work on igraph version > 0.7. The newer version does not support the area parameter, so I cannot get the same effect. And getting the old version to build took me a while, so I though I'd share some insights. You can manually install igraph 0.7 from source if you download it from igraph nightly builds. On my machine (Mac OS 10.10), I encountered some problems building it, due to gfortran, so I found this link that solved the problem. Hope that helps anyone who wants to create similar graphs in R.
You may want to try layout.fruchterman.reingold(). It seems to do a good job keeping the edges from crossing. I've tested it with a 355 node version of the barabasi graph suggested by #Tamás.
library(igraph)
g = barabasi.game(355, directed=FALSE)
png("plot1.png", height=6, width=12, units="in", res=200)
par(mfrow=c(1, 2))
plot.igraph(g,vertex.size=3,vertex.label=NA,
layout=layout.fruchterman.reingold(g, niter=10000))
mtext("layout.fruchterman.reingold, area = vcount^2", side=1)
plot.igraph(g,vertex.size=3,vertex.label=NA,
layout=layout.fruchterman.reingold(g, niter=10000, area=30*vcount(g)^2))
mtext("layout.fruchterman.reingold, area = 30 * vcount^2", side=1)
dev.off()
layout.reingold.tilford has a parameter called circular. Setting this to TRUE will convert the final layout into a radial one by treating the X coordinate as the angle (after appropriate rescaling) and the Y coordinate as the radius. Ironically enough, this does not guarantee that there will be no edge crossings in the end, but it works nicely if your subtrees are not exceedingly wide compared to the rest of the graph:
> g <- barabasi.game(100, directed=F)
> layout <- layout.reingold.tilford(g, circular=T)
> plot(g, layout=layout)