{"_id":"@bubblyworld/xgboost-ts","name":"@bubblyworld/xgboost-ts","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@bubblyworld/xgboost-ts","version":"1.0.0","description":"A wasm build of XGBoost with a convenient typescript interface. Runs with zero dependencies in both node and web environments.","type":"module","main":"./dist/index.node.cjs","module":"./dist/index.node.js","types":"./dist/index.node.d.ts","publishConfig":{"access":"public"},"exports":{".":{"node":{"import":{"types":"./dist/index.node.d.ts","default":"./dist/index.node.js"},"require":{"types":"./dist/index.node.d.cts","default":"./dist/index.node.cjs"}},"browser":{"types":"./dist/index.browser.d.ts","default":"./dist/index.browser.js"},"default":{"types":"./dist/index.browser.d.ts","default":"./dist/index.browser.js"}}},"scripts":{"build":"npm run build:wasm && npm run build:ts","build:wasm":"cd build && ./build.sh","build:ts":"tsc && rollup -c && cp dist/index.node.d.ts dist/index.node.d.cts","serve":"node --import tsx web/server.ts","test":"npm run test:node && npm run test:browser","test:node":"vitest run","test:browser":"playwright test --config test/playwright.config.ts","test:watch":"vitest","prepublishOnly":"npm run build"},"keywords":["xgboost","gradient-boosting","quantile-regression","machine-learning","wasm","webassembly"],"author":{"name":"Guy Paterson-Jones"},"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/Bubblyworld/xgboost-ts.git"},"devDependencies":{"@playwright/test":"^1.40.0","@rollup/plugin-typescript":"^11.0.0","@types/node":"^20.0.0","rollup":"^4.0.0","tsx":"^4.0.0","typescript":"^5.0.0","vitest":"^1.0.0"},"gitHead":"0dc048e7b3e7854e9ecd08cfd27954efad1d9661","_id":"@bubblyworld/xgboost-ts@1.0.0","bugs":{"url":"https://github.com/Bubblyworld/xgboost-ts/issues"},"homepage":"https://github.com/Bubblyworld/xgboost-ts#readme","_nodeVersion":"22.16.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-U2A3t85ZLU1USOYkHTlO/+WQkqzSRuwVkKfGVPhHI94SJuWFY6Xh1v6qdVbf5mgf/AuCHKLwSH4xTM2uv6nJ6A==","shasum":"67a6534022fb20aa51e26292528a2e6735eb241d","tarball":"https://registry.npmjs.org/@bubblyworld/xgboost-ts/-/xgboost-ts-1.0.0.tgz","fileCount":57,"unpackedSize":3382906,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC/9YCTEVlToleoeP3FxS272JLjsD4GORoZgPJGCk02RwIgfJJn/lW9arRt8bD6y5OlZEM7VqboWh3fKKKXNXEeXk8="}]},"_npmUser":{"name":"bubblyworld","email":"guy.paterson.jones@gmail.com"},"directories":{},"maintainers":[{"name":"bubblyworld","email":"guy.paterson.jones@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/xgboost-ts_1.0.0_1785824698737_0.6456639932396164"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T06:24:58.567Z","1.0.0":"2026-08-04T06:24:58.908Z","modified":"2026-08-04T06:24:59.092Z"},"maintainers":[{"name":"bubblyworld","email":"guy.paterson.jones@gmail.com"}],"description":"A wasm build of XGBoost with a convenient typescript interface. Runs with zero dependencies in both node and web environments.","homepage":"https://github.com/Bubblyworld/xgboost-ts#readme","keywords":["xgboost","gradient-boosting","quantile-regression","machine-learning","wasm","webassembly"],"repository":{"type":"git","url":"git+https://github.com/Bubblyworld/xgboost-ts.git"},"author":{"name":"Guy Paterson-Jones"},"bugs":{"url":"https://github.com/Bubblyworld/xgboost-ts/issues"},"license":"Apache-2.0","readme":"# xgboost-ts\n\nA WASM build of [XGBoost](https://xgboost.readthedocs.io/) with a TypeScript interface. Runs with zero dependencies in both Node.js and browser environments, and supports quantile regression for predicting intervals rather than point estimates.\n\n## Installation\n\n```bash\nnpm install @bubblyworld/xgboost-ts\n```\n\n## Quick Start\n\nTraining is a single call. Features are a row-major matrix, labels are one value per row, and the model that comes back can predict, report feature importance and serialise itself.\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst features = [[1, 4], [2, 3], [3, 7], [4, 2], [5, 9], [6, 1]];\nconst labels = [10, 11, 19, 12, 26, 11];\n\nconst model = await train({\n  data: features,\n  label: labels,\n  rounds: 50,\n  objective: 'reg:squarederror',\n  maxDepth: 3,\n  eta: 0.3,\n});\n\nconst predictions = model.predict([[2, 3], [5, 9]]);\n// @expect: predictions.shape.length === 1\n// @expect: predictions.values.length === 2\n\nmodel.dispose();\n```\n\nModels hold WebAssembly memory, so call `dispose()` when you are done with one.\n\n## Quantile Regression\n\nSet `objective: 'reg:quantileerror'` and give `quantileAlpha` the quantiles you want. Passing several fits them all in a single model, and each prediction row gains one column per quantile, in the order you asked for.\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst features = [];\nconst labels = [];\nfor (let i = 0; i < 400; i++) {\n  const x = (i % 100) / 10;\n  const spread = 0.5 + x / 2;\n  features.push([x]);\n  labels.push(Math.sin(x) * 5 + ((i * 37) % 100 / 100 - 0.5) * 2 * spread);\n}\n\nconst model = await train({\n  data: features,\n  label: labels,\n  rounds: 200,\n  objective: 'reg:quantileerror',\n  quantileAlpha: [0.1, 0.5, 0.9],\n  maxDepth: 4,\n  eta: 0.1,\n});\n\nconst p = model.predict([[1], [9]]);\n// @expect: p.shape[1] === 3\n\nconst [low, median, high] = p.row(0);\n// @expect: low <= median && median <= high\n\n// The band is wider where the data is noisier.\nconst narrowBand = p.row(0)[2] - p.row(0)[0];\nconst wideBand = p.row(1)[2] - p.row(1)[0];\n// @expect: wideBand > narrowBand\n\nmodel.dispose();\n```\n\n`quantileAlpha` also accepts a single number, which trains an ordinary single-output model targeting that quantile.\n\nQuantile regression is only implemented by XGBoost's histogram algorithm, so `treeMethod` defaults to `'hist'` for this objective unless you set it yourself.\n\n## Predictions\n\n`predict()` returns a `Prediction` that carries the shape XGBoost reported, so multi-output models need no guesswork about the layout.\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst model = await train({\n  data: [[1], [2], [3], [4], [5], [6]],\n  label: [0, 0, 1, 1, 2, 2],\n  rounds: 20,\n  objective: 'multi:softprob',\n  numClass: 3,\n});\n\nconst p = model.predict([[1], [5]]);\n// @expect: p.rows === 2\n// @expect: p.columns === 3\n// @expect: p.shape[0] === 2 && p.shape[1] === 3\n\n// Class probabilities for the first row, as a view onto the flat buffer.\nconst probabilities = p.row(0);\n// @expect: Math.abs(Array.from(probabilities).reduce((a, b) => a + b, 0) - 1) < 1e-5\n\n// One output across every row.\nconst classOne = p.column(1);\n// @expect: classOne.length === 2\n\n// Or as plain arrays, one inner array per row.\nconst rows = p.toArray();\n// @expect: rows.length === 2 && rows[0].length === 3\n\nmodel.dispose();\n```\n\n| Member | Description |\n| --- | --- |\n| `values` | Flat `Float32Array`, row-major |\n| `shape` | `[rows]` for single-output, `[rows, columns]` for multi-output |\n| `rows` / `columns` | Dimensions, with `columns` of 1 for single-output models |\n| `row(i)` | One row's outputs, as a view rather than a copy |\n| `column(j)` | One output across every row |\n| `toArray()` | Plain `number[][]`, one inner array per row |\n\n`Prediction` is also iterable, yielding one row at a time.\n\n`predict()` takes options for `margin` (raw scores before the link function), `iterationRange` (which trees to use), `missing` and `ncol`.\n\n## Training Parameters\n\nParameters use the camelCase form of the [upstream names](https://xgboost.readthedocs.io/en/stable/parameter.html), and each is documented inline in the type. The ones you will reach for most:\n\n| Parameter | Default | Description |\n| --- | --- | --- |\n| `objective` | `reg:squarederror` | Loss function to optimise |\n| `rounds` | — | Number of boosting rounds |\n| `eta` | 0.3 | Step size shrinkage per round |\n| `maxDepth` | 6 | Maximum tree depth |\n| `minChildWeight` | 1 | Minimum hessian sum in a child |\n| `gamma` | 0 | Minimum loss reduction to split |\n| `subsample` | 1 | Fraction of rows sampled per round |\n| `colsampleBytree` | 1 | Fraction of features sampled per tree |\n| `lambda` / `alpha` | 1 / 0 | L2 and L1 regularisation |\n| `treeMethod` | `auto` | Split-finding algorithm |\n| `growPolicy` | `depthwise` | Level-by-level, or highest-loss-leaf first |\n| `maxLeaves` | 0 | Leaf cap, for `growPolicy: 'lossguide'` |\n| `maxBin` | 256 | Histogram buckets for `hist` |\n| `scalePosWeight` | 1 | Positive class weight for imbalanced data |\n| `numParallelTree` | 1 | Trees per round, for boosted random forests |\n| `monotoneConstraints` | — | Per-feature 1, 0 or -1 |\n| `interactionConstraints` | — | Feature groups allowed to interact |\n| `numClass` | — | Class count for `multi:*` objectives |\n| `quantileAlpha` | — | Target quantile(s) for `reg:quantileerror` |\n| `seed` | 0 | Random seed |\n\nDart parameters (`sampleType`, `normalizeType`, `rateDrop`, `skipDrop`) apply when `booster: 'dart'`. Anything not listed can be passed through `extra`:\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst model = await train({\n  data: [[1], [2], [3], [4]],\n  label: [1, 2, 3, 4],\n  rounds: 10,\n  monotoneConstraints: [1],\n  extra: { max_cached_hist_node: 32 },\n});\n// @expect: model.numFeatures === 1\n\nmodel.dispose();\n```\n\n## Evaluation and Early Stopping\n\nPass one or more held-out sets as `eval` to score every round. With `earlyStoppingRounds`, training stops once the last set's metric has not improved for that many rounds, and predictions then use only the trees up to the best round.\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst features = [];\nconst labels = [];\nfor (let i = 0; i < 200; i++) {\n  features.push([i % 20, (i * 7) % 13]);\n  labels.push((i % 20) * 2 + ((i * 7) % 13));\n}\n\nconst model = await train({\n  data: features.slice(0, 150),\n  label: labels.slice(0, 150),\n  rounds: 1000,\n  maxDepth: 3,\n  eta: 0.3,\n  eval: { data: features.slice(150), label: labels.slice(150), name: 'valid' },\n  earlyStoppingRounds: 10,\n  onIteration: (round, metrics) => {\n    if (round % 20 === 0) console.log(round, metrics['valid-rmse']);\n  },\n});\n\n// @expect: model.numRounds < 1000\n// @expect: model.bestIteration >= 0\n// @expect: model.evalHistory.length === model.numRounds\n// @expect: 'valid-rmse' in model.evalHistory[0]\n\nmodel.dispose();\n```\n\n`evalHistory` holds one metrics object per round, keyed `<set>-<metric>`. Use `evalMetric` to choose the metrics; it accepts a list.\n\n## Saving and Loading\n\n```typescript\nimport { train, loadModel } from '@bubblyworld/xgboost-ts';\n\nconst model = await train({\n  data: [[1], [2], [3], [4]],\n  label: [1, 2, 3, 4],\n  rounds: 10,\n});\n\nconst buffer = model.toBuffer('ubj');\n// @expect: buffer.byteLength > 0\n\nconst restored = await loadModel(buffer);\nconst before = model.predict([[2]]).values[0];\nconst after = restored.predict([[2]]).values[0];\n// @expect: before === after\n\nmodel.dispose();\nrestored.dispose();\n```\n\n`toBuffer()` takes `'json'` for a portable readable model or `'ubj'` for a compact binary one. Node also has `loadModelFromFile(path)`.\n\n## Feature Importance\n\n```typescript\nimport { train } from '@bubblyworld/xgboost-ts';\n\nconst model = await train({\n  data: [[1, 9], [2, 8], [3, 7], [4, 6], [5, 5], [6, 4]],\n  label: [1, 2, 3, 4, 5, 6],\n  rounds: 20,\n  maxDepth: 3,\n  featureNames: ['size', 'age'],\n});\n\nconst importance = model.featureImportance('gain');\n// @expect: [...importance.keys()].every(k => k === 'size' || k === 'age')\n\nmodel.dispose();\n```\n\nWithout `featureNames`, keys are XGBoost's positional `f0`, `f1`, ... labels. Features never used in a split are absent rather than zero. The importance type may be `weight`, `gain`, `cover`, `total_gain` or `total_cover`.\n\n## Browser Usage\n\nThe browser build is the same API. Bundlers need to see the emscripten glue import, which the published output keeps as a plain relative specifier, so webpack and vite pick up the `.wasm` automatically.\n\n```html\n<script type=\"module\">\n  import { train } from '@bubblyworld/xgboost-ts';\n\n  const model = await train({ data, label, rounds: 100 });\n  console.log(model.predict(testData).toArray());\n  model.dispose();\n</script>\n```\n\nServing the `.wasm` with `Content-Type: application/wasm` lets the browser compile it while it streams. See `web/` in this repository for a working demo.\n\n## Low-Level API\n\n`train()` is built on a thin binding over XGBoost's C API, which is exported for cases it does not cover: reusing one `DMatrix` across many models, custom boosting loops, or parameters that change mid-training.\n\n```typescript\nimport { XGBoost, DMatrix } from '@bubblyworld/xgboost-ts';\n\nconst xgb = await XGBoost.create();\nconst dtrain = DMatrix.create(xgb, {\n  data: [[1], [2], [3], [4]],\n  label: [1, 2, 3, 4],\n});\n\nconst booster = xgb.createBooster([dtrain.handle]);\nxgb.setParam(booster, 'max_depth', '3');\nfor (let i = 0; i < 10; i++) {\n  xgb.updateOneIter(booster, i, dtrain.handle);\n}\n\nconst result = xgb.predictFromDMatrix(booster, dtrain.handle);\n// @expect: result.values.length === 4\n\nxgb.freeBooster(booster);\ndtrain.dispose();\nxgb.free();\n```\n\n`XGBoost.create()` instantiates an isolated module; `XGBoost.shared()` returns the one `train()` uses. Console output is suppressed by default:\n\n```typescript\nconst xgb = await XGBoost.create({\n  console: {\n    log: (text) => console.log(text),\n    error: (text) => console.error(text),\n  },\n});\n```\n\n## Building From Source\n\nRequires [Emscripten](https://emscripten.org/docs/getting_started/downloads.html), CMake 3.10+ and git.\n\n```bash\nnpm install\nnpm run build:wasm   # clones and compiles XGBoost, ~10 minutes\nnpm run build:ts\n```\n\n`build/build.sh` pins the upstream version in `XGBOOST_VERSION`, shallow-clones it into a temporary directory, applies the patches in `build/patches/`, and links a static build against an explicit list of exported C API symbols.\n\n## Testing\n\n```bash\nnpm run test:node     # vitest\nnpm run test:browser  # playwright against the web/ demo\nnpm run serve         # dev server at http://localhost:3000\n```\n\nThe code blocks in this README are executed as part of the node test suite, so the examples above are known to run.\n\n## Licensing\n\nApache-2.0. The WASM bundle includes [XGBoost](https://github.com/dmlc/xgboost), also Apache-2.0, along with its dependencies; see `NOTICE` for attribution.\n\nThe vast majority of the credit here goes to the authors of [XGBoost](https://github.com/dmlc/xgboost). I've just packaged it nicely for use in the javascript/typescript world.\n","readmeFilename":"README.md","_rev":"1-0c2c4abe7f671fee7d0a4cd2ebb136b3"}