Skip to content

pgmpy/ci-tests

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

432 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Conditional Independence Testing

CI License: MIT

A fast, multi-language library for conditional independence (CI) testing: deciding whether two variables $X$ and $Y$ are independent given a (possibly empty) set of conditioning variables $Z$ — written $X \perp Y \mid Z$. CI tests are a fundamental building block of constraint-based causal discovery and of structure learning for probabilistic graphical models.

Every test is implemented once in a dependency-light Rust core (ci-core) and exposed through thin, idiomatic bindings for Python, R, and JavaScript/WebAssembly.

Quick Start

Each binding compiles the Rust core from source, so you need a Rust toolchain installed (via rustup). In every language a test takes the observation vectors x and y and a conditioning matrix z whose columns are the conditioning variables; pass a zero-column matrix for an unconditional test.

Python

Build and install the package from the repository root:

pip install maturin
maturin develop -m crates/ci-python/Cargo.toml
import numpy as np
from ci_python import ChiSquared

x = np.array([1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0])
y = np.array([1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0])

test = ChiSquared(boolean=False, significance_level=0.05)

# Unconditional: pass an (n, 0) matrix for an empty conditioning set
p_value, statistic, dof = test.run_test(x, y, np.empty((len(x), 0)))
print(f"p={p_value:.4f}, chi2={statistic:.4f}, df={dof}")

# Conditional: each column of z is one conditioning variable
z = np.array([[1.0]] * 4 + [[2.0]] * 4)
p_value, statistic, dof = test.run_test(x, y, z)

Continuous tests (PearsonCorrelation, PearsonEquivalence) share the same interface but return (p_value, coefficient). In boolean mode (boolean=True) every test returns just a bool independence verdict.

R

Install the package from the repository root:

# install.packages("devtools")
devtools::install("crates/ci-r")
library(cir)

x <- c(1, 1, 2, 2, 1, 1, 2, 2)
y <- c(1, 2, 1, 2, 1, 2, 1, 2)

# Unconditional: a 0-column matrix is an empty conditioning set
z <- matrix(nrow = length(x), ncol = 0)
result <- chi_squared_test(x, y, z, boolean = FALSE, significance_level = 0.05)
cat("p =", result$p_value, " chi2 =", result$statistic, " df =", result$df, "\n")

# Conditional: each column of z is one conditioning variable
z <- matrix(c(1, 1, 1, 1, 2, 2, 2, 2), ncol = 1)
result <- chi_squared_test(x, y, z, boolean = FALSE, significance_level = 0.05)

# Boolean mode returns only an independence verdict
verdict <- pearson_correlation_test(x, y, z, boolean = TRUE, significance_level = 0.05)
cat("independent:", verdict$independent, "\n")

Each function returns a named list with a kind field: "statistic" (with p_value, statistic, df), "pvalue" (with p_value, coefficient), or "boolean" (with independent).

JavaScript

Build the WebAssembly package with wasm-pack; the output is written to crates/ci-js/pkg:

wasm-pack build crates/ci-js --target web      # for browsers
wasm-pack build crates/ci-js --target nodejs   # for Node.js
import init, { chi_squared_test } from "./pkg/ci_js.js";

await init(); // load the WebAssembly module (web target)

const x = new Float64Array([1, 1, 2, 2, 1, 1, 2, 2]);
const y = new Float64Array([1, 2, 1, 2, 1, 2, 1, 2]);

// Unconditional: pass an empty Float64Array; z_rows = z_cols = 0
const [pValue, statistic, dof] = chi_squared_test(new Float64Array(0), 0, 0, x, y, false, 0.05);
console.log(`p=${pValue.toFixed(4)}, chi2=${statistic.toFixed(4)}, df=${dof}`);

// Conditional: z is a row-major flattened (z_rows x z_cols) matrix
const z = new Float64Array([1, 1, 1, 1, 2, 2, 2, 2]);
const [pCond] = chi_squared_test(z, 8, 1, x, y, false, 0.05);

The conditioning matrix is passed flattened (z_flat, z_rows, z_cols) because WebAssembly has no native 2-D array type. Discrete tests return [p_value, statistic, dof], continuous tests return [p_value, coefficient], and boolean mode returns a bool.

Available Tests

Test Data type Numeric output
chi_squared Discrete (p_value, statistic, dof)
log_likelihood (G-test) Discrete (p_value, statistic, dof)
cressie_read Discrete (p_value, statistic, dof)
freeman_tukey Discrete (p_value, statistic, dof)
modified_likelihood Discrete (p_value, statistic, dof)
pearson_correlation Continuous (p_value, coefficient)
pearson_equivalence Continuous (p_value, coefficient)
  • Conditioning. Every test accepts a conditioning matrix Z (each column is one conditioning variable). For conditional discrete tests the statistic is summed over the strata defined by Z; for continuous tests the partial correlation is taken on the regression residuals.
  • Discrete family. The discrete tests are members of the power-divergence family and differ only in the $\lambda$ parameter: chi_squared ($1$), log_likelihood ($0$), cressie_read ($2/3$), freeman_tukey ($-1/2$), modified_likelihood ($-1$).
  • Boolean mode. With boolean=true, a test returns a single independence verdict at the given significance_level instead of the numeric tuple.
  • Equivalence test. pearson_equivalence is an equivalence (TOST) test: it additionally takes a delta_threshold and declares independence when the partial correlation lies within that margin of zero.
  • Naming. Python exposes these as classes (ChiSquared, LogLikelihood, …); R and JavaScript expose them as functions with a _test suffix (chi_squared_test, …).

Package Structure & Contributing

ci-core     Rust core: all test implementations and the CITest trait
ci-python   Python bindings (PyO3)        -> import ci_python
ci-r        R package (extendr)           -> library(cir)
ci-js       JavaScript / WASM (wasm-pack)

All bindings are thin wrappers that depend only on ci-core, so the statistics live in a single place. The Rust core can also be used directly as a crate. Full API documentation is published at https://giphouse.github.io/Conditional-Independence-Testing/.

Contributions are welcome. See CONTRIBUTING.md for environment setup, coding standards, and how to add a new test. Before opening a PR:

cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

License

Licensed under the MIT License.

About

High performance Rust implementation of conditional independence tests with extensions in Python, R, and JS

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors