https://github.com/pgmpy/ci-tests
High performance Rust implementation of conditional independence tests with extensions in Python, R, and JS
https://github.com/pgmpy/ci-tests
Last synced: about 1 month ago
JSON representation
High performance Rust implementation of conditional independence tests with extensions in Python, R, and JS
- Host: GitHub
- URL: https://github.com/pgmpy/ci-tests
- Owner: pgmpy
- License: mit
- Created: 2026-06-09T15:29:16.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-09T16:23:01.000Z (about 2 months ago)
- Last Synced: 2026-06-09T17:25:40.778Z (about 2 months ago)
- Language: Rust
- Size: 524 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# Conditional Independence Testing
[](https://github.com/GiPHouse/Conditional-Independence-Testing/actions)
[](https://github.com/GiPHouse/Conditional-Independence-Testing/blob/main/LICENSE)
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](https://rustup.rs)). 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:
```bash
pip install maturin
maturin develop -m crates/ci-python/Cargo.toml
```
```python
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:
```r
# install.packages("devtools")
devtools::install("crates/ci-r")
```
```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](https://rustwasm.github.io/wasm-pack/); the
output is written to `crates/ci-js/pkg`:
```bash
wasm-pack build crates/ci-js --target web # for browsers
wasm-pack build crates/ci-js --target nodejs # for Node.js
```
```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 .
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup,
coding standards, and how to add a new test. Before opening a PR:
```bash
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
```
## License
Licensed under the [MIT License](LICENSE).