rbayesian-networksbnlearnr-graphviz

bnlearn + Rgraphviz: double arrows instead of undirected edges when customizing plots


I am trying to customize a plot of a graph learned with bnlearn using RGraphviz. When I have undirected edges, RGraphviz turns them into directed edges to both directions when I try to customize the appearance of the graph.

A reproducible example could be:

set.seed(1)
x1 = rnorm(50, 0, 1)
x2 = rnorm(50, 0, 1)
x3 = x2 + rnorm(50, 0, 1)
x4 = -2*x1 + x3 + rnorm(50, 0, 1)
graph = data.frame(x1, x2, x3, x4)

library(bnlearn)
library(Rgraphviz)

res = gs(graph)
options(repr.plot.width=3, repr.plot.height=3)
g1 <- graphviz.plot(res)

Graph not customized:

enter image description here

So far so good. But if I try to customize it:

plot(g1, attrs = list(node = list(fontsize=4, fillcolor = "lightgreen")))

Customized graph

enter image description here

The undirected edge is transformed.

I get this problem even if I just use plot(g1). The issue is that this (saving g1 and then using plot) seems to change the appearance of the graphs.


Solution

  • You can change some of the attributes using the highlight argument of graphviz.plot, however, it doesn't seem to allow the label size to be altered. You could set this globally, but there is a loss of control doing it this way.

    par(cex=0.05)
    graphviz.plot(res, highlight = 
                    list(nodes=nodes(res), fill="lightgreen", col="black"))
    

    As shown in your question, you can get a bit more control, using the attrs argument for the plot method for the graphNEL object, but again there is the problem of direction.

    g <- bnlearn::as.graphNEL(res) # use this to avoid printing of graphviz.plot
    plot(g,  attrs=list(node = list(fillcolor = "lightgreen", fontsize=4)))
    

    Also try to set the graph plot parameters globally using graph.par, however, when I try this, the fontsize changes but the colours don't render

    graph.par(list(nodes=list(fill="lightgreen", fontsize=10)))
    renderGraph( Rgraphviz::layoutGraph(bnlearn::as.graphNEL(res)))
    

    So you can alter the nodes returned by graphviz.plot using the function nodeRenderInfo:

    g1 <- graphviz.plot(res)
    graph::nodeRenderInfo(g1) <- list(fill="lightgreen", fontsize=8)
    Rgraphviz::renderGraph(g1)
    

    This does however, plot the network when assigning graphviz.plot. So as an alternative you can change the graphNEL object (returned by graphviz.plot). By default, a graphNEL object is either all directed or all undirected, however you can tweak the edges manually. This is what graphviz.plot does. By looking at the code of bnlearn:::graphviz.backend, it identifies undirected edges, and the renders them using graph::edgeRenderInfo(graph.plot). You can use similar methods to do what you want - use the function nodeRenderInfo to update node attributes.

    So all together:

    # this sets all edges to directed
    g <- Rgraphviz::layoutGraph(bnlearn::as.graphNEL(res))
    
    # set undirected edges
    u <- names(which(graph::edgeRenderInfo(g)[["direction"]] == "both"))
    graph::edgeRenderInfo(g)[["arrowhead"]][u] = "none"
    graph::edgeRenderInfo(g)[["arrowtail"]][u] = "none"
    
    # update node attributes: fill colour and label fontsize
    graph::nodeRenderInfo(g) <- list(fill="lightgreen", fontsize=8)
    
    # render
    Rgraphviz::renderGraph(g)