## Preprocessing data with missing values

bnlearn provides two functions to carry out the most common preprocessing tasks in the Bayesian network literature: `discretize()` and `dedup()`.

### Discretizing data

The `discretize()` function (documented here) takes a data frame containing at least some continuous variables and returns a second data frame in which those continuous variables have been transformed into discrete variables. The end goal is to be able to use the returned data set to learn a discrete Bayesian network.

Any of the variables in the input data frame is allowed contain missing values, regardless of the `method` used to discretize them. In particular:

1. For marginal discretization methods (`"interval"` and `"quantile"`), the missing values in each variable are ignored when computing the boundaries of the intervals that will be used as levels. Furthermore, they will still appear as `NA`s in the discretized data. So:
```> library(bnlearn)
> complete = data.frame(A = rnorm(10))
> discretize(complete, method = "interval", breaks = 4)
```
```                      A
1   [-2.54391,-1.80538]
2   [-2.54391,-1.80538]
3  (-1.06685,-0.328315]
4  (-1.06685,-0.328315]
5  (-1.06685,-0.328315]
6  (-0.328315,0.410218]
7  (-0.328315,0.410218]
8  (-0.328315,0.410218]
9   (-1.80538,-1.06685]
10 (-0.328315,0.410218]
```
produces the same intervals (thus, a factor with same levels) as:
```> incomplete = data.frame(A = c(complete\$A, rep(NA, 3)))
> discretize(incomplete, method = "interval", breaks = 4)
```
```                      A
1   [-2.54391,-1.80538]
2   [-2.54391,-1.80538]
3  (-1.06685,-0.328315]
4  (-1.06685,-0.328315]
5  (-1.06685,-0.328315]
6  (-0.328315,0.410218]
7  (-0.328315,0.410218]
8  (-0.328315,0.410218]
9   (-1.80538,-1.06685]
10 (-0.328315,0.410218]
11                 <NA>
12                 <NA>
13                 <NA>
```
2. For joint discretization methods (`"hartemink"`), variables are considered in pairs. Observations that are not complete for each pair are ignored when computing the boundaries of the intervals that will be used as levels. Missing values are preserved as `NA`s in the discretized data. So:
```> complete = data.frame(A = rnorm(10), B = rnorm(10))
> discretize(complete, method = "interval", breaks = 4)
```
```                      A                    B
1     (0.378832,1.3859]   (0.549987,1.57774]
2  (-0.628234,0.378832] (-0.477764,0.549987]
3  (-0.628234,0.378832]   (0.549987,1.57774]
4   [-1.6353,-0.628234] [-1.50551,-0.477764]
5  (-0.628234,0.378832]    (1.57774,2.60549]
6     (0.378832,1.3859] (-0.477764,0.549987]
7   [-1.6353,-0.628234]   (0.549987,1.57774]
8   [-1.6353,-0.628234] (-0.477764,0.549987]
9      (1.3859,2.39296] [-1.50551,-0.477764]
10 (-0.628234,0.378832] (-0.477764,0.549987]
```
produces the same intervals (thus, a factor with same levels) as:
```> incomplete = data.frame(
+   A = c(complete\$A, rep(NA, 3)),
+   B = c(complete\$B, rnorm(3))
+ )
> discretize(incomplete, method = "interval", breaks = 4)
```
```                      A                    B
1     (0.378832,1.3859]   (0.549987,1.57774]
2  (-0.628234,0.378832] (-0.477764,0.549987]
3  (-0.628234,0.378832]   (0.549987,1.57774]
4   [-1.6353,-0.628234] [-1.50551,-0.477764]
5  (-0.628234,0.378832]    (1.57774,2.60549]
6     (0.378832,1.3859] (-0.477764,0.549987]
7   [-1.6353,-0.628234]   (0.549987,1.57774]
8   [-1.6353,-0.628234] (-0.477764,0.549987]
9      (1.3859,2.39296] [-1.50551,-0.477764]
10 (-0.628234,0.378832] (-0.477764,0.549987]
11                 <NA> (-0.477764,0.549987]
12                 <NA>   (0.549987,1.57774]
13                 <NA> (-0.477764,0.549987]
```

### Removing highly-correlated variables

The `dedup()` function (documented here) takes a data frame containing (only) continuous variables and looks for pairs of variables with strong correlation, regardless of the sign. It then removes one of variable in each such pair. The end goal is to avoid learning Gaussian Bayesian networks which clusters of highly-connected nodes, for both speed and interpretability.

Observations that are not complete for a pair of variables are ignored when computing the absolute correlation between those two variables. Missing values in the variables that are retained are preserved. So:

```> observations = rnorm(10)
> complete = data.frame(
+   A = observations,
+   B = 10 * observations + rnorm(10)
+ )
> dedup(complete, debug = TRUE)
```
```* caching means and variances.
* looking at A with 1 variables still to check.
A is collinear with B, dropping B with COR = 0.9921
```
```            A
1   0.2579441
2   0.7010045
3  -1.3947846
4  -0.2935555
5  -0.1863984
6   0.9037645
7   0.1047848
8   1.1744587
9  -0.6664913
10  0.5525382
```

will give the same output (modulo the missing values) as:

```> incomplete = data.frame(
+   A = c(complete\$A, rep(NA, 3)),
+   B = c(complete\$B, rnorm(3))
+ )
> dedup(incomplete, debug = TRUE)
```
```* caching means and variances.
* looking at A with 1 variables still to check.
A is collinear with B, dropping B with COR = 0.9921
```
```            A
1   0.2579441
2   0.7010045
3  -1.3947846
4  -0.2935555
5  -0.1863984
6   0.9037645
7   0.1047848
8   1.1744587
9  -0.6664913
10  0.5525382
11         NA
12         NA
13         NA
```
Last updated on `Fri Sep 3 21:18:59 2021` with bnlearn `4.7-20210901` and `R version 4.1.1 (2021-08-10)`.