Getting started
Installation
The library is published on JSR as @paluigi/jsindexnum:
bash
# Deno
deno add jsr:@paluigi/jsindexnum
# Bun (fetches @jsr scopes from JSR automatically)
bunx jsr add @paluigi/jsindexnum
# npm — one-time registry mapping, then install
echo '@jsr:registry=https://npm.jsr.io' >> .npmrc
npm install @jsr/palugi__jsindexnumnpm consumers import from the @jsr scope:
ts
import { fisher } from "@jsr/palugi__jsindexnum";The pipeline
Like PyIndexNum, calculations run on a canonical panel schema produced by aggregateTime: one row per (product, period) with aggregated_price and (optionally) aggregated_quantity.
ts
import {
standardizeColumns,
aggregateTime,
removeUnbalanced,
geksFisher,
fisher,
} from "@jsr/palugi__jsindexnum";
// 1. Standardize raw columns (rename, parse dates, drop zero quantities)
const std = standardizeColumns(
{
date: ["2023-01-05", "2023-01-20", "2023-02-10"],
product_id: ["A", "A", "A"],
price: [9.99, 10.5, 10.2],
quantity: [100, 80, 120],
},
{ quantityCol: "quantity" },
);
// 2. Aggregate to monthly periods
const panel = aggregateTime(std, { aggType: "weighted_arithmetic", freq: "1mo" });
// 3. (optionally) balance the panel
const balanced = removeUnbalanced(panel);
// 4. Compute indices
const series = geksFisher(balanced);
// [{ period: "2023-01-01", indexValue: 1 }, { period: "2023-02-01", indexValue: ... }]
const twoPeriods = /* ... a panel with exactly two periods ... */;
const f = fisher(twoPeriods); // a number, 1.02 = +2 %Data model
Panel — the canonical panel — stores data columnar:
| column | storage | meaning |
|---|---|---|
productIds | string[] + Uint32Array codes | dictionary-encoded ids |
periods | Int32Array | UTC epoch days |
prices | Float64Array | aggregated prices |
quantities | Float64Array | null | aggregated quantities |
Build one directly from columns or rows:
ts
const panel = Panel.fromColumns({
productId: ["A", "B"],
period: ["2023-01-01", "2023-01-01"],
price: [100, 200],
quantity: [10, 20],
});Balancing and imputation
ts
import {
removeUnbalanced,
carryForwardImputation,
carryBackwardImputation,
} from "jsindexnum";
removeUnbalanced(panel); // keep products present in every period
carryForwardImputation(panel); // full grid, last observation carried forward
carryBackwardImputation(panel); // full grid, next observation carried backwardMultilateral methods accept balanced: false to compute each period comparison on the products matched between the periods involved, and an onWarning callback (the counterpart of Python's UserWarnings):
ts
const { onWarning, messages } = collectWarnings();
const series = geksFisher(panel, { balanced: false, onWarning });
console.log(messages); // e.g. "Unbalanced panel: 3 of 105 period pairs..."Export-quality numbers
ts
import { roundSignificant, formatSignificant } from "jsindexnum";
0.1 + 0.2; // 0.30000000000000004
roundSignificant(0.1 + 0.2); // 0.3
formatSignificant(1.2007597555242124); // "1.2007598"Every display/export helper in the library and on this site routes numbers through this 8-significant-digit convention — see numeric alignment.