jena-js
v0.6.2
Published
Standalone TypeScript/JavaScript library for Epistemic Network Analysis, ported from rENA for browser and Node runtimes.
Maintainers
Readme
jENA
jena-js is a standalone TypeScript/JavaScript implementation of Epistemic Network Analysis (ENA) for browser and Node runtimes, ported from and verified against the rENA R package (0.3.1).
The runtime is pure JavaScript with zero runtime dependencies. It does not require R, Rserve, OpenCPU, or a server process to run ENA models. R is used only at development time to generate golden fixtures and verify numerical parity with rENA.
Install
npm install jena-jsor the development version straight from main:
npm install github:HUDongpin/jENAESM only. The package ships ES modules and requires Node ≥ 18 or a bundler; require("jena-js") is not supported.
Usage
import { ena } from "jena-js";
const set = ena({
rows: [
{ unit: "u1", conv: "c1", A: 1, B: 0, C: 0 },
{ unit: "u1", conv: "c1", A: 0, B: 1, C: 0 },
{ unit: "u2", conv: "c1", A: 1, B: 1, C: 0 }
],
units: ["unit"],
conversation: ["conv"],
codes: ["A", "B", "C"],
model: "EndPoint",
window: "MovingStanzaWindow",
weightBy: "binary",
windowSizeBack: 2,
dimensions: 2
});
console.log(set.points); // projected unit points (SVD1, SVD2)
console.log(set.rotation.nodes); // code node positions
console.log(set.variance); // variance explained per rotated dimensionNote on variance: shares are normalized across all rotated dimensions (rENA semantics), so SVD1 + SVD2 generally sums to less than 1. Dimension signs are arbitrary (SVD sign indeterminacy) — compare axes up to sign. See NUMERICS.md.
Entrypoints (API tiers)
The root export is the stable tier — the verified pipeline, types, stats, and accumulation kernels only (~two dozen names, semver-guarded). Everything else lives behind subpaths:
import { ena, accumulateData, makeSet, projectIn, enaCorrelations, cohensD } from "jena-js";
import { rotateByMean, svdRotation, lwsLeastSquaresPositions } from "jena-js/rotation";
import { createENAPlotModel, toPlotly, renderENAPlot } from "jena-js/plot";
import { createENAWorkerClient } from "jena-js/browser"; // worker bundle: "jena-js/browser/worker"
import { multiGaussianElasticNet } from "jena-js/experimental"; // may change in any release
import { solveLinearSystem } from "jena-js/core"; // internal tier: no semver guaranteesRotationOptions is a discriminated union — each rotation method's parameter shape is enforced at compile time.
Migrating from ≤0.5.x: the root previously re-exported the entire codebase. Rotation functions moved to jena-js/rotation, plot helpers to jena-js/plot, the worker client to jena-js/browser, the elastic net and typed-array table helpers to jena-js/experimental, and numerical internals (linear solvers, Gram-Schmidt, matrix utilities) are only available from jena-js/core. Code that used ena/accumulateData/makeSet/projectIn/stats from the root is unaffected.
Verified vs experimental surface
Everything in the verified tier is tested against golden outputs generated from rENA 0.3.1 (see fixtures/goldens/, version-stamped; tolerances documented in NUMERICS.md).
| Area | Status | Notes |
|---|---|---|
| Accumulation: EndPoint, AccumulatedTrajectory, SeparateTrajectory | ✅ Verified | All 16 golden configs, both fixture datasets |
| Windows: moving stanza (back/forward/infinite), conversation | ✅ Verified | Line-faithful port of rENA's C++ ref_window_df |
| Weighting: binary, sum, custom functions | ✅ Verified | Custom functions golden-tested (sqrt configs); applied once per windowed co-occurrence cell before unit accumulation — matches rENA's implementation (its docstring says otherwise; noted in rENA#48) |
| Sphere normalization, centering | ✅ Verified | Includes rENA's zero-row semantics |
| SVD rotation, means rotation | ✅ Verified | Points at ~1e-9, rotation matrices up to sign |
| Variance explained | ✅ Verified | Normalized over all dimensions, 1e-9 vs rENA |
| Undirected node positions + centroids | ✅ Verified | See NUMERICS.md for singular-system tolerance |
| projectIn (rotation-set reuse) | ✅ Verified | Self-projection invariant + shared machinery |
| Stats: enaCorrelations, cohensD | ✅ Verified | vs rENA::ena.correlations / fun_cohens.d |
| Stats: Welch t, one-way ANOVA (enaStats) | ✅ Verified | vs R t.test / aov; no p-values — statistic + df only |
| Rotations: regression, regression2 | ✅ Verified | vs ena.rotate.by.hena.regression/_2 — x-only, x+y, and multi-term formulas (12 golden configs) |
| Rotation: generalized (single covariate) | ✅ Verified | vs ena.rotate.by.generalized incl. select2Groups; variance compared junk-aware (see NUMERICS.md) |
| Rotation: hena | ✅ Verified | vs ena.rotation.h — x/y, factor-expanded controls, interaction, centering on/off (6 golden configs), incl. rENA's run-length dummy coding |
| Elastic-net solver (multiGaussianElasticNet) | ✅ Verified | vs glmnet mgaussian at fixed lambdas (1e-6): group lasso, standardization, penalty factors, α-mixing |
| Streaming/chunked accumulation | ✅ Verified | Single engine since 0.6.0 (batch delegates to it): all rENA golden parity suites run through it, plus chunk-size equivalence and a 120-case randomized property suite with an independent window oracle |
| Rotation: generalized with multiple covariates | ⚠️ Solver-verified | The elastic-net solver matches glmnet at equal lambda (1e-6). Full-pipeline parity is out of reach for two reasons: rENA selects lambda via cv.glmnet, whose fold assignment is randomized (irreproducible across runs unless the caller seeds R's RNG — rENA does not), and rENA's design matrix (model.matrix(~ .^2): factor expansion, all pairwise interactions) differs from jena's simplified design. jena is deterministic end-to-end (round-robin folds over a glmnet-style path) — see NUMERICS.md |
| Rotation: spherical | 🔷 jena extension | No rENA counterpart exists — anchors axes at chosen adjacency directions; spec-tested (orthonormality, anchor semantics) |
| Directed node positions | 🧪 Experimental | Throws on this pipeline's undirected models; only usable with external n×n directed adjacency data |
| Plot model, plotly adapter, SVG renderer | ✅ Tested (jena-specific) | 16 contract tests pin every builder and the plotly trace/layout mapping; SVG renderer smoke-tested in real Chromium. No rENA golden — rENA's R-plotly stack is structurally incomparable |
| Worker client + protocol | ✅ Protocol + browser tested | Versioned protocol v1: chunked progress, cooperative cancel, crash/timeout/abort rejection — 12 protocol tests on an in-memory channel plus real-Chromium Worker round-trip tests (result parity, progress, mid-flight cancel) |
If a rotation or statistic will end up in a publication, stay on the ✅ tier or independently validate your configuration against rENA first.
Input validation
Malformed inputs throw descriptive errors instead of producing quietly wrong numbers: empty rows, fewer than 2 codes, negative/fractional window sizes, wrong-shape masks, misspelled model/window/weightBy/rotation.method/nodePositionMethod values, dimensions < 1, and unitsUsed filters that match nothing are all rejected.
Development
npm install
npm run lint
npm run typecheck
npm test
npm run build
npm run pack:checkGolden fixtures are committed, so tests run without R. To regenerate them (requires R with rENA installed):
npm run goldens:r # model fixtures -> sena-configs.regenerated.json
npm run goldens:diff # compare regenerated vs committed at 1e-9
npm run goldens:stats # stats fixtures (correlations, cohen's d, t/F tests)See fixtures/goldens/README.md for the regenerate → diff → adopt workflow. CI runs lint, typecheck, tests, build, a packaging check, and a packed-tarball consumer smoke test on Node 18/20/22.
Provenance and license
jena-js is a TypeScript translation of core rENA behavior — rENA 0.3.1 (GPL-3) by Cody L Marquart, Zachari Swiecki, Wesley Collier, Brendan Eagan, Roman Woodward, and David Williamson Shaffer. This package is distributed under GPL-3.0-only, preserving the upstream license. See PROVENANCE.md for the upstream version pin, source history, and file-level attribution, and NUMERICS.md for documented numerical deviations and agreement bounds.
