Gaussian Bayesian Networks
Material
Exercises
These are answers and solutions to the exercises at the end of Part 2 in Bayesian Networks with Examples in R by M. Scutari and J.-B. Denis. Much of my inspiration for these solutions, where necessary, by consulting the solutions provided by the authors themselves as in the appendix.
R
Environment
For today’s exercise, I load the following packages:
library(bnlearn)
library(ggplot2)
library(tidyr)
library(tidybayes)
Scutari 2.1
Prove that Equation (2.2) implies Equation (2.3).
Equation 2.2 reads:
Equation 2.3 reads:
So how do we go about demonstrating that the first implies the latter? Well, we are using Bayesian theory here so why not use the Bayes' theorem? So let’s start by rewriting equation 2.2:
Scutari 2.2
Within the context of the DAG shown in Figure 2.1, prove that Equation (2.5) is true using Equation (2.6).
This is the DAG in question:

The equation to prove (2.5) is:
and we use this equation (2.6) for our proof:
Let’s start the proof by integrating over all variables that aren’t
We do this to remove all but the variables we are after from our equation so let’s follow this rationale:
Simplifying this mess, we arrive at:
Finally, we can obtain our original formula:
Another case of the quod erat demonstrandums.
Scutari 2.3
Compute the marginal variance of the two nodes with two parents from the local distributions proposed in Table 2.1. Why is it much more complicated for C than for V?
Table 2.1 is hardly a table at all, but I did locate it. Basically, it is an amalgamation of the probability distributions proposed for the DAG from the previous exercise:

Note that the parameter
The two nodes we are after are
- Computation for
Simply translating the probability distribution into a linear model, we receive:
with the variances of our independent variables
- Computation for
For
this time, however the predictors variables are not independent since they share node
So we actually needed to calculate the variance for
Now, I simply plug the values into the formula and arrive at:
Curiously, the book suggest this as the solution:
I am not sure where the values for VAR(N) and VAR(W) have gone here. If anyone who is reading this knows the answer to it, please contact me and let me know as well.
Scutari 2.4
Write an R script using only the
rnorm
andcbind
functions to create a 100 × 6 matrix of 100 observations simulated from the BN defined in Table 2.1. Compare the result with those produced by a call tocpdist
function.
To simulate a table of observation using the formulae in the probability distribution collection from the previous question (Table 1), we simply select random values for all parent nodes according to their distributions and let the distributions for all offspring nodes do the rest. One important note here, is that the rnorm()
function in R
takes as an argument of variation the standard deviation
set.seed(42) # making things reproducible
n <- 1e2 # number of replicates
G <- rnorm(n, 50, 10)
E <- rnorm(n, 50, 10)
V <- rnorm(n, -10.35534 + 0.5 * G + 0.70711 * E, 5)
N <- rnorm(n, 45 + 0.1 * V, 9.949874)
W <- rnorm(n, 15 + 0.7 * V, 7.141428)
C <- rnorm(n, 0.3 * N + 0.7 * W, 6.25)
sim1 <- data.frame(cbind(G, E, V, N, W, C))
Now we do this using the cpdist()
function. To do so, we first have to create our Bayesian Network:
dag.bnlearn <- model2network("[G][E][V|G:E][N|V][W|V][C|N:W]")
disE <- list(coef = c("(Intercept)" = 50), sd = 10)
disG <- list(coef = c("(Intercept)" = 50), sd = 10)
disV <- list(coef = c("(Intercept)" = -10.35534, E = 0.70711, G = 0.5), sd = 5)
disN <- list(coef = c("(Intercept)" = 45, V = 0.1), sd = 9.949874)
disW <- list(coef = c("(Intercept)" = 15, V = 0.7), sd = 7.141428)
disC <- list(coef = c("(Intercept)" = 0, N = 0.3, W = 0.7), sd = 6.25)
dis.list <- list(E = disE, G = disG, V = disV, N = disN, W = disW, C = disC)
gbn.bnlearn <- custom.fit(dag.bnlearn, dist = dis.list)
sim2 <- data.frame(cpdist(gbn.bnlearn, nodes = nodes(gbn.bnlearn), evidence = TRUE))
this is pretty much exactly what is done in the chapter.
So let’s compare these simulation outputs:
# preparing all data together in one data frame for plotting
sim1$sim <- 1
sim2$sim <- 2
Plot_df <- rbind(sim1, sim2[, match(colnames(sim1), colnames(sim2))])
Plot_df <- gather(data = Plot_df, key = "node", value = "value", G:C)
Plot_df$sim <- as.factor(Plot_df$sim)
## plotting
ggplot(Plot_df, aes(x = value, y = sim)) +
stat_halfeye() +
facet_wrap(~node, scales = "free") +
theme_bw()
As is apparent from this, all results fall close to the expected values of roughly 50. There are noticeable differences between the simulations. I would suggest that these are due to the fairly low sample size for sim1
.
Scutari 2.5
Imagine two ways other than changing the size of the points (as in Section 2.7.2) to introduce a third variable in the plot.
The plot in question is this one:

this plot is aimed at showing the distribution of
So how else could we add information of
- Make three scatter plots. One for each pairing of our variables.
- Represent the values of
with a colour saturation gradient.
Scutari 2.6
Can GBNs be extended to log-normal distributions? If so how, if not, why?
GBNs are Gaussian Bayesian Networks - Bayesian Networks where each node follows a Gaussian distribution.
Yes, absolutely they can! We can simply take the logarithm of all initial variables and apply the GBN right away. Of course, all values that shall be transformed using the logarithm have to be positive.
Scutari 2.7
How can we generalise GBNs as defined in Section 2.3 in order to make each node’s variance depend on the node’s parents?
I see absolutely no problem here. Let’s say we have two nodes:
; parent node with a constant variance ; child node with a variance dependant the parent node
Then we can easily define the variance of
Scutari 2.8
From the first three lines of Table 2.1, prove that the joint distribution of E, G and V is trivariate normal.
This one is a doozy and I really needed to consult the solutions in the book for this one. Let’s first remind ourselves of the first lines of said table:

To approach this problem it is useful to point out that the logarithm of the density of a multivariate normal distribution is defined as such:
with
Simplifying this, we can transform our variables
I have to honestly that I don’t quite understand how this happened and if anyone reading this has intuition for this solution, please let me know.
Now, we can compute the joint density distribution of these three normalised variables:
I have to admit that most of this is, as of right now, beyond me as I came to this book for the “applications in R” in the first place. The book concludes that this results in:
which results in our proof.
Session Info
sessionInfo()
## R version 4.2.1 (2022-06-23 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19044)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=English_Germany.utf8 LC_CTYPE=English_Germany.utf8 LC_MONETARY=English_Germany.utf8 LC_NUMERIC=C LC_TIME=English_Germany.utf8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] tidybayes_3.0.2 tidyr_1.2.0 ggplot2_3.3.6 bnlearn_4.8.1
##
## loaded via a namespace (and not attached):
## [1] styler_1.8.0 tidyselect_1.1.2 xfun_0.33 bslib_0.4.0 purrr_0.3.4 lattice_0.20-45 colorspace_2.0-3 vctrs_0.4.1 generics_0.1.3
## [10] htmltools_0.5.3 yaml_2.3.5 utf8_1.2.2 rlang_1.0.5 R.oo_1.25.0 jquerylib_0.1.4 pillar_1.8.1 glue_1.6.2 withr_2.5.0
## [19] DBI_1.1.3 R.utils_2.12.0 distributional_0.3.1 R.cache_0.16.0 lifecycle_1.0.2 stringr_1.4.1 posterior_1.3.1 munsell_0.5.0 blogdown_1.13
## [28] gtable_0.3.1 R.methodsS3_1.8.2 coda_0.19-4 evaluate_0.16 labeling_0.4.2 knitr_1.40 fastmap_1.1.0 parallel_4.2.1 fansi_1.0.3
## [37] highr_0.9 arrayhelpers_1.1-0 backports_1.4.1 checkmate_2.1.0 scales_1.2.1 cachem_1.0.6 jsonlite_1.8.0 abind_1.4-5 farver_2.1.1
## [46] tensorA_0.36.2 digest_0.6.29 svUnit_1.0.6 stringi_1.7.8 bookdown_0.29 dplyr_1.0.9 grid_4.2.1 ggdist_3.2.0 cli_3.3.0
## [55] tools_4.2.1 magrittr_2.0.3 sass_0.4.2 tibble_3.1.8 pkgconfig_2.0.3 ellipsis_0.3.2 assertthat_0.2.1 rmarkdown_2.16 rstudioapi_0.14
## [64] R6_2.5.1 compiler_4.2.1