B-sets and interfering v-structures

library(PCBN)

What are B-sets?

For a node \(v\) that has children, the B-sets are the intersections of the parents sets of the children of \(v\) with \(v\)’s own parents sets.

They can be obtained using the function

DAG = create_empty_DAG(6)
DAG = bnlearn::set.arc(DAG, 'U1', 'U5')
DAG = bnlearn::set.arc(DAG, 'U2', 'U5')
DAG = bnlearn::set.arc(DAG, 'U3', 'U5')
DAG = bnlearn::set.arc(DAG, 'U4', 'U5')

DAG = bnlearn::set.arc(DAG, 'U1', 'U6')
DAG = bnlearn::set.arc(DAG, 'U2', 'U6')
DAG = bnlearn::set.arc(DAG, 'U5', 'U6')
DAG |> bnlearn::as.igraph() |>
  igraph::plot.igraph(size = 10, label.cex = 1, layout = igraph::layout_as_tree
  )

find_B_sets_v(DAG, v = 'U5')
#>                U1    U2    U3    U4
#> Empty B-set FALSE FALSE FALSE FALSE
#> U6           TRUE  TRUE FALSE FALSE
#> Full B-set   TRUE  TRUE  TRUE  TRUE

This means that for v = U5, the B-sets corresponding to U6 is made up of U1 and U2. By convention, the empty set and the full set of parents of v are always added. To obtain all the B-sets of the nodes of the graph, the following code can be used:

B_sets = find_B_sets(DAG)
B_sets$B_sets
#> $U1
#>     
#> [1,]
#> [2,]
#> [3,]
#> [4,]
#> 
#> $U2
#>     
#> [1,]
#> [2,]
#> [3,]
#> [4,]
#> 
#> $U3
#>     
#> [1,]
#> [2,]
#> [3,]
#> 
#> $U4
#>     
#> [1,]
#> [2,]
#> [3,]
#> 
#> $U5
#>                U1    U2    U3    U4
#> Empty B-set FALSE FALSE FALSE FALSE
#> U6           TRUE  TRUE FALSE FALSE
#> Full B-set   TRUE  TRUE  TRUE  TRUE
#> 
#> $U6
#>                U1    U2    U5
#> Empty B-set FALSE FALSE FALSE
#> Full B-set   TRUE  TRUE  TRUE

Interfering v-structures

An interfering v-structure is by definition a case when two B-sets of the same node are not comparable (for the inclusion order, i.e. neither of them is included in the other one).

Here is an example of a graph with interfering v-structures.

DAG = create_empty_DAG(5)
DAG = bnlearn::set.arc(DAG, 'U1', 'U3')
DAG = bnlearn::set.arc(DAG, 'U2', 'U3')

DAG = bnlearn::set.arc(DAG, 'U1', 'U4')
DAG = bnlearn::set.arc(DAG, 'U3', 'U4')
DAG = bnlearn::set.arc(DAG, 'U2', 'U5')
DAG = bnlearn::set.arc(DAG, 'U3', 'U5')
DAG |> bnlearn::as.igraph() |> igraph::plot.igraph()

find_B_sets_v(DAG, v = 'U3')
#>                U1    U2
#> Empty B-set FALSE FALSE
#> U4           TRUE FALSE
#> U5          FALSE  TRUE
#> Full B-set   TRUE  TRUE

To check automatically that the B-sets are not comparable (i.e. the existence of a v-structure), we can do:

find_B_sets_v(DAG, v = 'U3') |>
  B_sets_are_increasing()
#> [1] FALSE

More generally, the presence of v-structures can be automatically checked using the following function:

has_interfering_vstrucs(DAG)
#> [1] TRUE

To find the interfering v-structures at a given node, we can use:

find_B_sets_v(DAG, v = 'U3') |>
  find_interfering_v_from_B_sets() |>
  print()
#>    A  B parents.A.but.not.parents.B parents.B.but.not.parents.A
#> 1 U4 U5                          U1                          U2

To fix this, we can add one of the missing arcs. This makes the interfering v-structure disappear.

DAG = bnlearn::set.arc(DAG, 'U1', 'U5')
has_interfering_vstrucs(DAG)
#> [1] FALSE

In this case, the B-sets are:

find_B_sets_v(DAG, v = 'U3')
#>                U1    U2
#> Empty B-set FALSE FALSE
#> U4           TRUE FALSE
#> U5           TRUE  TRUE
#> Full B-set   TRUE  TRUE

B-sets cuts

If there are no interfering v-structures, the B-sets are sorted. We can then get their increments as follows:

find_B_sets_v(DAG, v = 'U3') |>
  B_sets_cut_increments()
#> [[1]]
#> [1] "U1"
#> 
#> [[2]]
#> [1] "U2"
#> 
#> [[3]]
#> character(0)

In this case, the B-sets of U5 and the full B-set are identical. Therefore, the last increment is the empty character vector. It can be nice sometimes to make the B-sets unique so as to avoid this.

B_sets_unique = find_B_sets_v(DAG, v = 'U3') |>
  B_sets_make_unique()

print(B_sets_unique)
#>                    nodes    U1    U2
#> Empty B-set  Empty B-set FALSE FALSE
#> U4                    U4  TRUE FALSE
#> U5          U5, Full....  TRUE  TRUE
B_sets_cut_increments(B_sets_unique)
#> [[1]]
#> [1] "U1"
#> 
#> [[2]]
#> [1] "U2"
# Setting back the options to what they were before (following CRAN's policy)

options(old)