Skip to content

Kanaries/causal-js

Repository files navigation

causal.js

npm version npm downloads

A JavaScript and TypeScript toolkit for causal discovery and causal inference.

The current Step 3 workflow is production-scope within an explicitly bounded, DAG-first contract. It is not presented as a full causal inference framework.

For npm consumers, the public package is @kanaries/causal.

Install

npm install @kanaries/causal
pnpm add @kanaries/causal
yarn add @kanaries/causal
bun add @kanaries/causal

Quick Start

If you are new to the library, start with one of these three paths:

  1. Discovery only: use pc, ges, exactSearch, gin, grasp, cdnod, camuv, or rcd directly.
  2. DAG-first workflow: use discoverGraph, findAdjustmentSets, identifyEffect, falsifyGraph, and stabilityAnalysis.
  3. Runtime-specific integration: use @kanaries/causal/node or @kanaries/causal/web when you need runtime capability helpers.

The current task workflow is production-scope only within its explicit DAG-first boundary. It is appropriate when you already accept a DAG-first structural workflow and need real adjustment, identification, falsification, and stability helpers instead of mock orchestration.

Choose The Right API

Use these entry points based on the job you are trying to do:

  • pc, ges, exactSearch, gin, grasp, cdnod, camuv, rcd: run a concrete discovery algorithm directly.
  • discoverGraph(...): wrap an existing discovery algorithm behind a stable task result object.
  • findAdjustmentSets(...) / isAdjustmentSet(...): inspect valid DAG backdoor covariate sets.
  • identifyEffect(...): ask whether a singleton treatment effect is identifiable under the supported DAG-first rules.
  • falsifyGraph(...): run DAG sanity checks plus implied conditional independence validation against observed data.
  • stabilityAnalysis(...): bootstrap an existing discovery configuration and summarize robustness.

If you need full ID, richer graph classes such as PAG/MAG/ADMG, counterfactuals, permutation falsification, or estimator construction, this repository does not currently promise that surface.

Public Package

This repository is organized as a single Git repo with a pnpm workspace. The goal is to keep the portable causal core independent from runtime-specific integrations so the project can support:

  • Node.js-only algorithms or accelerators
  • browser-safe algorithms
  • shared algorithms with different runtime implementations

The publish target is a single npm package:

  • @kanaries/causal

Public entry points:

  • @kanaries/causal
  • @kanaries/causal/node
  • @kanaries/causal/web

The internal workspace packages remain private implementation units. They are bundled into the public facade package at build time and are not intended to be published separately.

Internal Workspace Packages

  • @causal-js/core: shared graph types, runtime capability metadata, and common algorithm contracts
  • @causal-js/discovery: runtime-agnostic causal discovery APIs and algorithm registry
  • @causal-js/tasks: DAG-first task-oriented workflow APIs for adjustment, identification, falsification, and stability
  • @causal-js/node: Node.js facade and Node-only capability surface
  • @causal-js/web: browser facade and browser/WebGPU capability surface

Design Principles

  • Keep graph structures and algorithm contracts portable
  • Treat runtime support as explicit metadata, not an afterthought
  • Avoid forcing Node.js and browser exports to stay identical
  • Add hardware-accelerated implementations behind separate packages or adapters

Current Baseline

The first requested algorithm wave is now runnable:

  • PC
  • CD_NOD
  • GES
  • ExactSearch
  • GIN
  • GRaSP
  • CAM_UV
  • RCD

This baseline has now passed the current V1 acceptance run:

  • pnpm typecheck
  • pnpm build
  • pnpm test
  • pnpm test:integration
  • external regression suite in Kanaries/causal-parity

V1 Status

Current algorithm status:

Algorithm Status
PC selected-path parity
CD_NOD selected-path parity
GES selected-path parity
ExactSearch selected-path parity
GIN selected-path parity
GRaSP selected-path parity
CAM_UV selected-fixture parity, accepted portable smoother boundary
RCD selected-path parity

The practical meaning is:

  • PC, CD_NOD, GES, ExactSearch, GIN, GRaSP, and RCD are now covered by the current V1 parity and comparison baseline
  • CAM_UV is accepted for V1 on the current comparison suite, while still keeping a portable smoother instead of a literal pygam.LinearGAM dependency

See v1-status.md for the exact boundaries. See external-regression.md for the heavy regression workflow.

Stable Entry Points

Current public package roles:

  • @kanaries/causal: portable graph primitives, matrix containers, CI tests, score functions, discovery algorithms, and DAG-first task workflows
  • @kanaries/causal/node: Node-oriented runtime facade
  • @kanaries/causal/web: browser-oriented runtime facade

At the current v1 stage, the Node and Web public facades intentionally overlap a lot. Runtime-specific divergence is currently limited to capability probes, execution planning, and worker-adapter scaffolding.

Task Workflow At A Glance

The recommended DAG-first order is:

  1. discoverGraph(...)
  2. findAdjustmentSets(...)
  3. identifyEffect(...)
  4. falsifyGraph(...)
  5. stabilityAnalysis(...)

See tasks/index.md for the full task surface. See tasks/backend-selection.md before choosing auto, dag-first-mvp, or dag-backdoor-only. See tasks/operational-readiness.md for release checks and interpretation boundaries.

Usage

PC

import { DenseMatrix, FisherZTest, pc } from "@kanaries/causal";

const data = new DenseMatrix(rows);
const result = pc({
  data,
  ciTest: new FisherZTest(data),
  alpha: 0.05,
  stable: true,
  ucRule: 0,
  ucPriority: 2
});

console.log(result.graph);

GES

import { DenseMatrix, GaussianBicScore, ges } from "@kanaries/causal";

const data = new DenseMatrix(rows);
const result = ges({
  data,
  score: new GaussianBicScore(data)
});

console.log(result.cpdag);

CD_NOD

import { DenseMatrix, FisherZTest, cdnod } from "@kanaries/causal";

const data = new DenseMatrix(rows);
const context = [1, 1, 1, 2, 2, 2];
const result = cdnod({
  data,
  context,
  alpha: 0.05,
  createCiTest: (augmentedData) => new FisherZTest(augmentedData),
  ucRule: 0,
  ucPriority: 2
});

console.log(result.contextNodeIndex);

ExactSearch

import { DenseMatrix, GaussianBicScore, exactSearch } from "@kanaries/causal";

const data = new DenseMatrix(rows);
const result = exactSearch({
  data,
  score: new GaussianBicScore(data),
  searchMethod: "astar"
});

console.log(result.dag);

GIN

import { DenseMatrix, gin } from "@kanaries/causal";

const data = new DenseMatrix(rows);
const result = gin({
  data,
  indepTestMethod: "kci",
  alpha: 0.05
});

console.log(result.causalOrder);

Task Workflow

import {
  CausalGraph,
  DenseMatrix,
  FisherZTest,
  GRAPH_KIND,
  discoverGraph,
  findAdjustmentSets,
  identifyEffect,
  falsifyGraph,
  stabilityAnalysis
} from "@kanaries/causal";

const graph = CausalGraph.fromNodeIds(["X", "Y", "Z"], { kind: GRAPH_KIND.dag });
graph.addDirectedEdge("Z", "X");
graph.addDirectedEdge("Z", "Y");
graph.addDirectedEdge("X", "Y");

const data = new DenseMatrix(
  Array.from({ length: 200 }, (_, index) => {
    const t = index + 1;
    const z = Math.sin(t / 8) + Math.cos(t / 13);
    const x = 0.9 * z + Math.sin(t / 5) * 0.03;
    const y = -0.8 * z + Math.cos(t / 7) * 0.03;
    return [x, y, z];
  })
);

const discovered = discoverGraph({
  algorithm: "pc",
  options: {
    data,
    ciTest: new FisherZTest(data),
    nodeLabels: ["X", "Y", "Z"]
  }
});

const adjustment = findAdjustmentSets({
  graph: graph.toShape(),
  treatment: "X",
  outcome: "Y"
});
const identified = identifyEffect({
  graph: graph.toShape(),
  treatment: "X",
  outcome: "Y"
});
const falsified = falsifyGraph({
  graph: graph.toShape(),
  data,
  observedNodeOrder: ["X", "Y", "Z"]
});
const stability = stabilityAnalysis({
  discovery: {
    algorithm: "pc",
    options: {
      data,
      ciTest: new FisherZTest(data),
      nodeLabels: ["X", "Y", "Z"]
    }
  },
  bootstrapSamples: 10,
  seed: 42
});

console.log(discovered.summary);
console.log(adjustment.candidateSets);
console.log(identified.method);
console.log(identified.estimandSpec?.expression);
console.log(falsified.overallSummary);
console.log(stability.edgeFrequency);

Current Step 3 task-workflow boundary:

  • graph-analysis tasks are DAG-first
  • identifyEffect() supports backdoor, core frontdoor, zero-effect, and current-MVP non-identifiable results
  • identifyEffect() now accepts backend?: "auto" | "dag-first-mvp" | "dag-backdoor-only"; auto resolves to dag-first-mvp, while dag-backdoor-only intentionally restricts identification to zero-effect and backdoor logic
  • listIdentificationBackends(), listIdentificationBackendsForGraphKind(), supportsIdentificationBackendGraphKind(), listIdentificationBackendDescriptors(), and getIdentificationBackendDescriptor() expose the currently available identification backends, graph-kind compatibility, and the auto selection contract
  • explicit backend selection now rejects incompatible graph kinds instead of silently falling back
  • falsifyGraph() supports sanity checks plus implied conditional independence validation, not full permutation falsification
  • stabilityAnalysis() is a bootstrap robustness wrapper, not a graph correctness proof
  • invalid falsification and stability input contracts now fail explicitly instead of producing silent fallback behavior

See tasks/index.md and tasks/step3-checklist.md for the current accepted task-workflow surface. See tasks/result-contract.md for the stable result-object contract. See tasks/end-to-end-workflow.md and examples/step3-task-workflow.ts for the current minimal public workflow example. See tasks/backend-selection.md and tasks/operational-readiness.md for backend pinning guidance, validation commands, and evidence boundaries.

Runtime Facades

import {
  DenseMatrix,
  FisherZTest,
  detectNodeRuntimeCapabilities,
  nodeRuntime,
  pc
} from "@kanaries/causal/node";

const data = new DenseMatrix(rows);
const result = pc({
  data,
  ciTest: new FisherZTest(data)
});

console.log(detectNodeRuntimeCapabilities());
console.log(nodeRuntime.supportsFileSystem);
import {
  DenseMatrix,
  FisherZTest,
  detectWebRuntimeCapabilities,
  webRuntime,
  pc
} from "@kanaries/causal/web";

const data = new DenseMatrix(rows);
const result = pc({
  data,
  ciTest: new FisherZTest(data)
});

console.log(detectWebRuntimeCapabilities());
console.log(webRuntime.supportsWebWorkers);

Runtime Capability Matrix

import { nodeAlgorithmCatalog, isNodeAlgorithmSupported } from "@kanaries/causal/node";

console.log(nodeAlgorithmCatalog.map((entry) => entry.id));
console.log(isNodeAlgorithmSupported("pc"));
import { webAlgorithmCatalog, isWebAlgorithmSupported } from "@kanaries/causal/web";

console.log(webAlgorithmCatalog.map((entry) => entry.id));
console.log(isWebAlgorithmSupported("calm"));

Current Boundaries

The accepted V1 surface is intentionally narrower than full causal-learn:

  • PC: parity is covered on the selected Fisher-Z, Chi-square, and G-square paths
  • GES: parity is covered on selected GaussianBicScore and BDeuScore paths
  • CD_NOD: committed path is deterministic domain-varying Fisher-Z
  • ExactSearch: committed path is Gaussian exact DAG search
  • GIN: includes standalone unconditional kci, not conditional-kernel discovery
  • CAM_UV: accepted V1 smoother is portable additive spline backfitting, not pygam.LinearGAM
  • RCD: accepted V1 optimizer is portable numeric MLHSICR, not SciPy fmin_l_bfgs_b

See:

Developer Workflow

For local development in this repository:

pnpm install
pnpm typecheck
pnpm build
pnpm test
pnpm test:integration
pnpm typecheck:examples

If you change public behavior, graph semantics, CI/score contracts, parity baselines, or regression expectations, also run the heavy external regression suite in causal-parity:

cd ../causal-parity
CAUSAL_JS_SOURCE_ROOT=../causal-js pnpm test

Use docs/tasks/operational-readiness.md as the release checklist for the DAG-first workflow surface.

If you want to inspect the publishable facade package locally:

pnpm pack:causal

License

Apache-2.0

About

Implementation of causal discovery algorithms in JS

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages