///|
/// The tests of the conditional independence of the columns of a table.
///
/// This is the port of `dowhy.utils.cit`, the module of the upstream that the
/// graph refuter of the upstream builds on.
///
/// The upstream takes a `pandas.DataFrame` and the names of its columns. The
/// port takes the columns themselves and the index of every one of them,
/// because the `utils` package is below the `api` package, which owns the
/// frame of the library.
///|
/// The partial correlation of two columns, given the other columns.
///
/// This is the port of the dictionary `dowhy.utils.cit.partial_corr` returns.
pub struct PartialCorrelation {
/// The number of the rows the correlation was computed on.
n : Int32
/// The partial correlation coefficient.
r : Double
/// The lower bound of the confidence interval of the coefficient.
ci_lower : Double
/// The upper bound of the confidence interval of the coefficient.
ci_upper : Double
/// The p-value of the test of the independence of the two columns.
p_value : Double
}
///|
/// The confidence interval of a correlation coefficient.
///
/// This is the port of `dowhy.utils.cit.compute_ci`. The interval is the one
/// of the Fisher z transform: the coefficient is transformed, the interval is
/// computed around it and it is transformed back.
///
/// The upstream takes the number of the rows of the two columns, but it only
/// uses the number of the rows of the first one, so the port takes one number.
pub fn compute_ci(
r : Double,
nx : Int32,
confidence : Double,
) -> (Double, Double) {
let z = @math.atanh(r)
let se = 1.0 / (nx.to_double() - 3.0).sqrt()
let crit = normal_ppf((1.0 - confidence) / 2.0).abs()
let lower = @math.tanh(z - crit * se)
let upper = @math.tanh(z + crit * se)
(lower, upper)
}
///|
/// The partial correlation of two columns, given the other columns.
///
/// This is the port of `dowhy.utils.cit.partial_corr`. The correlation is the
/// correlation of the residuals of the two regressions of the columns on the
/// other ones, and the upstream computes it with the pseudo inverse of the
/// covariance matrix of the columns.
///
/// The `correlation` is `"spearman"` for the correlation of the ranks of the
/// columns and `"pearson"` for the correlation of the columns themselves.
/// The upstream treats every kind of correlation but `"spearman"` as the
/// Pearson correlation.
///
/// The rows that hold a missing value in one of the columns are dropped, like
/// the upstream drops them.
pub fn partial_corr(
columns : Array[Array[Double]],
x : Int32,
y : Int32,
z : Array[Int32],
correlation : String,
) -> PartialCorrelation {
let selected = selected_columns(x, y, z)
let kept = complete_columns(columns, selected)
let n = if kept.length() > 0 { kept[0].length() } else { 0 }
let k = selected.length() - 2
let values = if correlation == "spearman" {
let ranked : Array[Array[Double]] = []
for column in kept {
ranked.push(average_rank(column))
}
ranked
} else {
kept
}
let covariance = covariance_matrix(values)
let inverse = hermitian_pinv(covariance)
let scale : Array[Double] = []
for i in 0.. Double {
let counts : Map[String, Int32] = Map([])
for value in values {
let count = match counts.get(value) {
Some(number) => number
None => 0
}
counts.set(value, count + 1)
}
let total = values.length().to_double()
let mut result = 0.0
for value in counts.keys() {
let count = match counts.get(value) {
Some(number) => number
None => 0
}
let probability = count.to_double() / total
result = result - probability * (ln(probability) / ln(2.0))
}
result
}
///|
/// The conditional mutual information of the upstream.
///
/// This is the port of `dowhy.utils.cit.conditional_MI`.
///
/// The upstream reads the *names* of the columns of the two variables and not
/// their values: it indexes the frame with a list, which gives a frame and not
/// a column, and it walks the names of that frame. The entropy of the pairs
/// and of the triples is therefore the entropy of a single category, which is
/// zero, and the conditional mutual information of the upstream is the
/// negative entropy of the conditioning columns.
///
/// The port reproduces that number, because the decision of the graph refuter
/// of the upstream depends on it: the conditional independence of the discrete
/// data is always satisfied. The upstream also takes the two variables, but
/// its number does not depend on them, so the port takes the conditioning
/// columns alone.
pub fn conditional_mi(z : Array[Array[String]]) -> Double {
let h_xz = entropy([""])
let h_yz = entropy([""])
let h_xyz = entropy([""])
let keys : Array[String] = []
let rows = if z.length() > 0 { z[0].length() } else { 0 }
for row in 0.. String {
let mut key = ""
for i in 0.. 0 {
key = key + "/"
}
key = key + columns[i][row]
}
key
}
///|
/// The columns of the partial correlation, the two of them first.
fn selected_columns(x : Int32, y : Int32, z : Array[Int32]) -> Array[Int32] {
let selected = [x, y]
for index in z {
selected.push(index)
}
selected
}
///|
/// The given columns, without the rows that hold a missing value in one
/// of them.
fn complete_columns(
columns : Array[Array[Double]],
selected : Array[Int32],
) -> Array[Array[Double]] {
let rows = if selected.length() > 0 {
columns[selected[0]].length()
} else {
0
}
let mut complete = true
for index in selected {
let column = columns[index]
for row in 0.. Matrix {
let n = columns.length()
let matrix = Matrix::zeros(n, n)
let means : Array[Double] = []
for column in columns {
means.push(mean(column))
}
for i in 0.. Double {
let n = x.length()
if n < 2 {
return 0.0
}
let mut s = 0.0
for i in 0.. Matrix {
let (values, vectors) = jacobi_eigen(a)
let n = values.length()
let mut largest = 0.0
for i in 0.. largest {
largest = value
}
}
let tolerance = 1.0e-15 * largest
let inverse = Matrix::zeros(n, n)
for k in 0..