Customizing and exporting graphical models and CPTs as image (pdf, png)

Creative Commons License

aGrUM

interactive online version

In [1]:
from pylab import *
import matplotlib.pyplot as plt
In [2]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb
In [3]:
bn = gum.fastBN("a->b->c->d;b->e->d->f;g->c")
gnb.flow.row(bn, gnb.getInference(bn))
G a a b b a->b g g c c g->c f f d d d->f c->d e e e->d b->c b->e
structs Inference in   2.07ms a 2025-08-29T16:48:37.726800 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b 2025-08-29T16:48:37.746461 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ a->b c 2025-08-29T16:48:37.766944 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->c e 2025-08-29T16:48:37.826491 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->e d 2025-08-29T16:48:37.807430 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ c->d f 2025-08-29T16:48:37.844792 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ d->f e->d g 2025-08-29T16:48:37.863258 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ g->c

customizing colours and width for model and inference

In [4]:
def nodevalue(n):
  return 0.5 if n in "aeiou" else 0.7


def arcvalue(a):
  return (10 - a[0]) * a[1]


def arcvalue2(a):
  return (a[0] + a[1] + 5) / 22


gnb.showBN(
  bn,
  nodeColor={n: nodevalue(n) for n in bn.names()},
  arcWidth={a: arcvalue(a) for a in bn.arcs()},
  arcLabel={a: f"v={arcvalue(a):02d}" for a in bn.arcs()},
  arcColor={a: arcvalue2(a) for a in bn.arcs()},
)
../_images/notebooks_98-Tools_customizingAndExportingBNs_6_0.svg
In [5]:
gnb.showInference(
  bn,
  targets={"a", "g", "f", "b"},
  evs={"e": 0},
  nodeColor={n: nodevalue(n) for n in bn.names()},
  arcWidth={a: arcvalue(a) for a in bn.arcs()},
)
../_images/notebooks_98-Tools_customizingAndExportingBNs_7_0.svg
In [6]:
gnb.flow.row(
  gnb.getBN(bn, nodeColor={n: nodevalue(n) for n in bn.names()}, arcWidth={a: arcvalue(a) for a in bn.arcs()}),
  gnb.getInference(bn, nodeColor={n: nodevalue(n) for n in bn.names()}, arcWidth={a: arcvalue(a) for a in bn.arcs()}),
)
G a a b b a->b g g c c g->c f f d d d->f c->d e e e->d b->c b->e
structs Inference in   1.69ms a 2025-08-29T16:48:38.604847 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b 2025-08-29T16:48:38.625780 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ a->b c 2025-08-29T16:48:38.644454 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->c e 2025-08-29T16:48:38.682610 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->e d 2025-08-29T16:48:38.663696 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ c->d f 2025-08-29T16:48:38.701690 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ d->f e->d g 2025-08-29T16:48:38.720430 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ g->c
In [7]:
mycmap = plt.get_cmap("Reds")
formyarcs = plt.get_cmap("winter")
gnb.flow.row(
  gnb.getBN(
    bn,
    nodeColor={n: nodevalue(n) for n in bn.names()},
    arcColor={a: arcvalue2(a) for a in bn.arcs()},
    cmapNode=mycmap,
    cmapArc=formyarcs,
  ),
  gnb.getInference(
    bn,
    nodeColor={n: nodevalue(n) for n in bn.names()},
    arcColor={a: arcvalue2(a) for a in bn.arcs()},
    arcWidth={a: arcvalue(a) for a in bn.arcs()},
    cmapNode=mycmap,
    cmapArc=formyarcs,
  ),
)
G a a b b a->b g g c c g->c f f d d d->f c->d e e e->d b->c b->e
structs Inference in   1.67ms a 2025-08-29T16:48:38.961111 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b 2025-08-29T16:48:39.015797 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ a->b c 2025-08-29T16:48:39.034610 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->c e 2025-08-29T16:48:39.071634 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ b->e d 2025-08-29T16:48:39.053345 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ c->d f 2025-08-29T16:48:39.090252 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ d->f e->d g 2025-08-29T16:48:39.108397 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ g->c

Modifying graph’s layout

Every graph or graphical models can be translated into a pyDot’s representaton (a pydot.Dot object). In this graphical representation, it is possible to manipulate the positions of the node. pyAgrum proposes two functions gum.utils.dot_layout to help modifying this layout.

Layout for Bayesian network

In [8]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb
import pyagrum.lib.utils as gutils
import pyagrum.lib.bn2graph as gumb2g

bn = gum.fastBN("A->B<-C<-D")
bn2 = gum.fastBN("A->B->C<-D")

graph = gumb2g.BN2dot(bn)

graph2 = gumb2g.BN2dot(bn2)
l = gutils.dot_layout(graph)
print(f"Layout proposed by dot for BN :{l}")
gutils.apply_dot_layout(graph2, l)

graph3 = gumb2g.BN2dot(bn2)
# l["C"],l["A"]=l["A"],l["C"]
l["D"], l["C"], l["B"], l["A"] = (
  gutils.DotPoint(0, 0),
  gutils.DotPoint(1, 1),
  gutils.DotPoint(2, 2),
  gutils.DotPoint(3, 3),
)
gutils.apply_dot_layout(graph3, l)
gnb.flow.row(bn, bn2, graph2, graph3, captions=["BN", "BN2", "BN2 with the same layoutas BN", "Layout changed by hand"])
Layout proposed by dot for BN :{'D': DotPoint(x=0.375, y=2.25), 'A': DotPoint(x=1.375, y=1.25), 'B': DotPoint(x=0.875, y=0.25), 'C': DotPoint(x=0.375, y=1.25)}
G D D C C D->C A A B B A->B C->B
BN
G D D C C D->C A A B B A->B B->C
BN2
G D D C C D->C A A B B A->B B->C
BN2 with the same layoutas BN
G D D C C D->C A A B B A->B B->C
Layout changed by hand

Layout for other graphical models and for inference

In [9]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb
import pyagrum.lib.utils as gutils
import pyagrum.lib.id2graph as gum2gr

model = gum.fastID("*D->$L<-E<-H->L;E->D")
gnb.flow.add(model)
gum.config.push()
gum.config["influenceDiagram", "utility_shape"] = "diamond"

figure = gum2gr.ID2dot(model)
l = gutils.dot_layout(figure)

# changing LAYOUT
# making some horizontal space
for i, p in l.items():
  l[i] = gutils.DotPoint(1.5 * p.x, p.y)
# E at the vertical of L, at the horizontal of D
l["E"] = gutils.DotPoint(l["L"].x, l["D"].y)
# H symetric of D w.r.t (EL)
l["H"] = gutils.DotPoint(2 * l["E"].x - l["D"].x, l["D"].y)

gutils.apply_dot_layout(figure, l)
gnb.flow.add(figure)

gnb.flow.display()
gum.config.pop()
E E D D E->D L L E->L H H H->E H->L D->L
E E D D E->D L L E->L H H H->E H->L D->L
In [10]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb
import pyagrum.lib.utils as gutils
import pyagrum.lib.id2graph as gum2gr

model = gum.fastID("*D->$L<-E<-H->L;E->D")
gnb.flow.add(gnb.getInference(model))

figure = gum2gr.LIMIDinference2dot(model, evs={}, targets={}, size=None, engine=None)
l = gutils.dot_layout(figure)

# changing LAYOUT
# making some horizontal space
for i, p in l.items():
  l[i] = gutils.DotPoint(3 * p.x, p.y)
# E at the vertical of L, at the horizontal of D
l["E"] = gutils.DotPoint(l["L"].x, l["D"].y)
l["D"] = gutils.DotPoint(l["D"].x / 2, l["D"].y)
l["H"] = gutils.DotPoint(l["L"].x * 3 / 2, l["D"].y)

gutils.apply_dot_layout(figure, l)
gnb.flow.add(figure)

gnb.flow.display()
structs MEU 29.69 (stdev=8.22) Inference in   0.11ms D 2025-08-29T16:48:39.986031 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ L L : 29.69 (8.22) D->L E 2025-08-29T16:48:40.005629 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ E->D E->L H 2025-08-29T16:48:40.025693 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ H->L H->E
structs MEU 29.69 (stdev=8.22) Inference in   0.27ms D 2025-08-29T16:48:40.156105 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ L L : 29.69 (8.22) D->L E 2025-08-29T16:48:40.175377 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ E->D E->L H 2025-08-29T16:48:40.193767 image/svg+xml Matplotlib v3.10.5, https://matplotlib.org/ H->L H->E

Exporting model and inference as image

Exporting as image (pdf, png, etc.) has been gathered in 2 functions : pyagrum.lib.image.export() and pyagrum.lib.image.exportInference(). The argument are the same as for pyagrum.notebook.show{Model} and pyagrum.notebook.show{Inference}.

In [11]:
import pyagrum.lib.image as gumimage
from IPython.display import Image  # to display the exported images
In [12]:
gumimage.export(bn, "out/test_export.png")

Image(filename="out/test_export.png")
Out[12]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_17_0.png
In [13]:
bn = gum.fastBN("a->b->d;a->c->d[3]->e;f->b")
gumimage.export(
  bn,
  "out/test_export.png",
  nodeColor={"a": 1, "b": 0.3, "c": 0.4, "d": 0.1, "e": 0.2, "f": 0.5},
  arcColor={(0, 1): 0.2, (1, 2): 0.5},
  arcWidth={(0, 3): 0.4, (3, 2): 0.5, (2, 4): 0.6},
)

Image(filename="out/test_export.png")
Out[13]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_18_0.png
In [14]:
gumimage.exportInference(bn, "out/test_export.png")

Image(filename="out/test_export.png")
Out[14]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_19_0.png
In [15]:
gumimage.export(bn, "out/test_export.pdf")

Link to out/test_export.pdf

exporting inference with evidence

In [16]:
bn = gum.loadBN("res/alarm.dsl")
gumimage.exportInference(
  bn,
  "out/test_export.pdf",
  evs={"CO": 1, "VENTLUNG": 1},
  targets={
    "VENTALV",
    "CATECHOL",
    "HR",
    "MINVOLSET",
    "ANAPHYLAXIS",
    "STROKEVOLUME",
    "ERRLOWOUTPUT",
    "HBR",
    "PULMEMBOLUS",
    "HISTORY",
    "BP",
    "PRESS",
    "CO",
  },
  size="15!",
)

Link to out/test_export.pdf

Other models

Other models can also use these functions.

In [17]:
infdiag = gum.loadID("res/OilWildcatter.bifxml")
gumimage.export(infdiag, "out/test_export.pdf")

Link to out/test_export.pdf

In [18]:
gumimage.exportInference(infdiag, "out/test_export.pdf")

Link to out/test_export.pdf

Exporting any object with toDot() method

In [19]:
import pyagrum.causal as csl

obs1 = gum.fastBN("Smoking->Cancer")
modele3 = csl.CausalModel(obs1, [("Genotype", ["Smoking", "Cancer"])], True)
gumimage.export(modele3, "out/test_export.png")  # a causal model has a toDot method.
Image(filename="out/test_export.png")
Out[19]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_31_0.png
In [20]:
bn = gum.fastBN("a->b->c->d;b->e->d->f;g->c")
ie = gum.LazyPropagation(bn)
jt = ie.junctionTree()
gumimage.export(jt, "out/test_export.png")  # a JunctionTree has a method jt.toDot()
Image(filename="out/test_export.png")
Out[20]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_32_0.png

… or even a string in dot syntax

In [21]:
gumimage.export(
  jt.toDotWithNames(bn), "out/test_export.png"
)  # jt.toDotWithNames(bn) creates a dot-string for a junction tree with names of variables
Image(filename="out/test_export.png")
Out[21]:
../_images/notebooks_98-Tools_customizingAndExportingBNs_34_0.png

Exporting to pyplot

In [22]:
import matplotlib.pyplot as plt

bn = gum.fastBN("A->B->C<-D")

plt.imshow(gumimage.export(bn))
plt.show()

plt.imshow(gumimage.exportInference(bn, size="15!"))
plt.show()

plt.figure(figsize=(10, 10))
plt.imshow(gumimage.exportInference(bn, size="15!"))
plt.show()
../_images/notebooks_98-Tools_customizingAndExportingBNs_36_0.svg
../_images/notebooks_98-Tools_customizingAndExportingBNs_36_1.svg
../_images/notebooks_98-Tools_customizingAndExportingBNs_36_2.svg

Exporting CPTs, sideBySide, explain.Information (and other html strings)

pyAgrum uses the package playwright in order to export html string. It proposes a function pyagrum.utils.async_html2image. In pyagrum.notebook, the functions get... return HTML objects. The functions show... display the result.

As a result, every pyagrum.lib.notebook.get... can be exported as pdf, png … using pyagrum.utils.async_html2image.

In [23]:
bn = gum.fastBN("A->B<-C", 3)
await gutils.async_html2image(gnb.getTensor(bn.cpt("B")), "out/cpt_B_.pdf")
In [24]:
await gutils.async_html2image(gnb.getSideBySide(bn, bn.cpt("A"), bn.cpt("B"), bn.cpt("C")), "out/sideBySide.pdf")
In [25]:
import pyagrum.explain as gexplain

await gutils.async_html2image(gexplain.getInformation(bn), "out/informationBN.pdf")
In [ ]: