Node.js Examples¶
Complete Node.js examples demonstrating fastlowess with native N-API bindings.
Batch Smoothing¶
Process complete datasets with confidence intervals and diagnostics.
const fastlowess = require('../../bindings/nodejs');
/**
* fastlowess Batch Smoothing Example
*
* This example demonstrates batch LOWESS smoothing features:
* - Basic smoothing with different parameters
* - Robustness iterations for outlier handling
* - Confidence and prediction intervals
* - Diagnostics and cross-validation
*
* The Lowess class is the primary interface for
* processing complete datasets that fit in memory.
*/
function generateSampleData(nPoints = 1000) {
// Generate complex sample data with a trend, seasonality, and outliers.
const x = new Float64Array(nPoints);
const y = new Float64Array(nPoints);
const yTrue = new Float64Array(nPoints);
for (let i = 0; i < nPoints; i++) {
x[i] = (i / (nPoints - 1)) * 50; // range 0 to 50
// Trend + Seasonality
yTrue[i] = 0.5 * x[i] + 5 * Math.sin(x[i] * 0.5);
// Gaussian noise (approx)
let u1 = Math.random();
let u2 = Math.random();
let z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
y[i] = yTrue[i] + z * 1.5;
}
// Add significant outliers (10% of data)
const nOutliers = Math.round(nPoints * 0.1);
for (let k = 0; k < nOutliers; k++) {
const idx = Math.floor(Math.random() * nPoints);
const sign = Math.random() < 0.5 ? -1 : 1;
y[idx] += (10 + Math.random() * 10) * sign;
}
return { x, y, yTrue };
}
function main() {
console.log("=== fastlowess Batch Smoothing Example ===");
// 1. Generate Data
const { x, y } = generateSampleData(1000);
console.log(`Generated ${x.length} data points with outliers.`);
// 2. Basic Smoothing (Default parameters)
console.log("Running basic smoothing...");
// Use a smaller fraction (0.05) to capture the sine wave seasonality
const _resBasic = new fastlowess.Lowess({ iterations: 0, fraction: 0.05 }).fit(x, y);
// 3. Robust Smoothing (IRLS)
console.log("Running robust smoothing (3 iterations)...");
const _resRobust = new fastlowess.Lowess({
fraction: 0.05,
iterations: 3,
robustnessMethod: "bisquare",
returnRobustnessWeights: true
}).fit(x, y);
// 4. Uncertainty Quantification
console.log("Computing confidence and prediction intervals...");
const resIntervals = new fastlowess.Lowess({
fraction: 0.05,
confidenceIntervals: 0.95,
predictionIntervals: 0.95,
returnDiagnostics: true
}).fit(x, y);
// 5. Cross-Validation for optimal fraction
console.log("Running cross-validation to find optimal fraction...");
const cvFractions = [0.05, 0.1, 0.2, 0.4];
// We pass the fractions and CV method to the smooth function.
// The main result returned will be the fit using the BEST fraction.
// We can also retrieve the score for each fraction.
const resCV = new fastlowess.Lowess({
cvFractions,
cvMethod: "kfold",
cvK: 5,
// We can request robustness weights or diagnostics for the final best model too
returnDiagnostics: true
}).fit(x, y);
console.log(`Optimal fraction selected: ${resCV.fractionUsed}`);
// Check scores if available
const scores = resCV.cvScores;
// Note: cvScores array corresponds to the input fractions order.
if (scores) {
console.log("CV Scores (RMSE):");
for (let i = 0; i < cvFractions.length; i++) {
console.log(` - Fraction ${cvFractions[i]}: ${scores[i].toFixed(4)}`);
}
}
// Diagnostics Printout
if (resIntervals.diagnostics) {
const diag = resIntervals.diagnostics;
console.log("\nFit Statistics (Intervals Model):");
console.log(` - R²: ${diag.rSquared.toFixed(4)}`);
console.log(` - RMSE: ${diag.rmse.toFixed(4)}`);
console.log(` - MAE: ${diag.mae.toFixed(4)}`);
}
// 6. Boundary Policy Comparison
console.log("\nDemonstrating boundary policy effects on linear data...");
const xl = new Float64Array(50);
const yl = new Float64Array(50);
for (let i = 0; i < 50; i++) {
xl[i] = (i / 49) * 10;
yl[i] = 2 * xl[i] + 1;
}
const rExt = new fastlowess.Lowess({ fraction: 0.6, boundaryPolicy: "extend" }).fit(xl, yl);
const rRef = new fastlowess.Lowess({ fraction: 0.6, boundaryPolicy: "reflect" }).fit(xl, yl);
const rZr = new fastlowess.Lowess({ fraction: 0.6, boundaryPolicy: "zero" }).fit(xl, yl);
console.log("Boundary policy comparison:");
console.log(` - Extend (Default): first=${rExt.y[0].toFixed(2)}, last=${rExt.y[49].toFixed(2)}`);
console.log(` - Reflect: first=${rRef.y[0].toFixed(2)}, last=${rRef.y[49].toFixed(2)}`);
console.log(` - Zero: first=${rZr.y[0].toFixed(2)}, last=${rZr.y[49].toFixed(2)}`);
console.log("\n=== Batch Smoothing Example Complete ===");
}
main();
Streaming Smoothing¶
Process large datasets in memory-efficient chunks.
const fastlowess = require('../../bindings/nodejs');
/**
* fastlowess Streaming Smoothing Example
*
* This example demonstrates streaming LOWESS smoothing for large datasets:
* - Basic chunked processing
* - Handling datasets that don't fit in memory
* - Parallel execution for extreme speed
*/
function main() {
console.log("=== fastlowess Streaming Mode Example ===");
// 1. Generate Very Large Dataset
// 100,000 points
const nPoints = 100000;
console.log(`Generating large dataset: ${nPoints} points...`);
// Pre-allocate arrays
const x = new Float64Array(nPoints);
const y = new Float64Array(nPoints);
for (let i = 0; i < nPoints; i++) {
x[i] = (i / (nPoints - 1)) * 100; // range 0 to 100
// Gaussian noise
let u1 = Math.random();
let u2 = Math.random();
let z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
y[i] = Math.cos(x[i] * 0.1) + z * 0.5;
}
// 2. Regular Batch Smoothing (for comparison)
console.log("Running Batch LOWESS (Parallel)...");
const batchStart = process.hrtime.bigint();
const resBatch = new fastlowess.Lowess({ fraction: 0.01 }).fit(x, y);
const batchEnd = process.hrtime.bigint();
const batchTime = Number(batchEnd - batchStart) / 1e9;
console.log(`Batch took: ${batchTime.toFixed(4)} seconds`);
// 3. Streaming Mode
// Divide the data into chunks of 2,000 for low memory usage
console.log("Running Streaming LOWESS (Chunked)...");
const streamStart = process.hrtime.bigint();
const streamer = new fastlowess.StreamingLowess(
{ fraction: 0.01 },
{ chunkSize: 2000, overlap: 200 }
);
// Simulate reading chunks from a stream/file
// In a real app we wouldn't load all x/y into memory first
const chunkSize = 2000;
const resChunks = [];
for (let i = 0; i < nPoints; i += chunkSize) {
// Slice creates a view or copy depending on usage;
// Float64Array.subarray creates a view (zero copy), but the binding expects TypedArray.
// If the binding makes a copy anyway, subarray is fine.
const chunkX = x.subarray(i, Math.min(i + chunkSize, nPoints));
const chunkY = y.subarray(i, Math.min(i + chunkSize, nPoints));
const chunkRes = streamer.processChunk(chunkX, chunkY);
if (chunkRes) resChunks.push(chunkRes.y);
}
const finalChunk = streamer.finalize();
if (finalChunk) resChunks.push(finalChunk.y);
// Combine chunks into one array for comparison
const totalLen = resChunks.reduce((acc, c) => acc + c.length, 0);
const streamY = new Float64Array(totalLen);
let offset = 0;
for (const c of resChunks) {
streamY.set(c, offset);
offset += c.length;
}
const streamEnd = process.hrtime.bigint();
const streamTime = Number(streamEnd - streamStart) / 1e9;
console.log(`Streaming took: ${streamTime.toFixed(4)} seconds`);
// 4. Verify Accuracy
let mse = 0;
// Lengths might differ slightly if streaming dropped points or handled boundaries differently,
// but usually they should match if configured right.
// Let's assume matches for this example or truncate to min.
const cmpLen = Math.min(resBatch.y.length, streamY.length);
const batchY = resBatch.y;
for (let i = 0; i < cmpLen; i++) {
mse += Math.pow(batchY[i] - streamY[i], 2);
}
mse /= cmpLen;
console.log(`Mean Squared Difference (Batch vs Stream): ${mse.toExponential(2)}`);
// Show sample of results
console.log("\nSample comparison (indices 1000-1005):");
console.log("Index\tBatch\t\tStreaming\tDiff");
for (let i = 1000; i <= 1005; i++) {
if (i < cmpLen) {
const diff = Math.abs(batchY[i] - streamY[i]);
console.log(`${i}\t${batchY[i].toFixed(6)}\t${streamY[i].toFixed(6)}\t${diff.toFixed(6)}`);
}
}
console.log("\n=== Streaming Smoothing Example Complete ===");
}
main();
Download streaming_smoothing.js
Online Smoothing¶
Real-time smoothing with sliding window for streaming data.
const fastlowess = require('../../bindings/nodejs');
/**
* fastlowess Online Smoothing Example
*
* This example demonstrates online LOWESS smoothing for real-time data:
* - Basic incremental processing with streaming data
* - Real-time sensor data smoothing
* - Different update modes (Full vs Incremental)
* - Memory-bounded processing with sliding window
*/
function main() {
console.log("=== fastlowess Online Smoothing Example ===");
// 1. Simulate a real-time signal
// A sine wave with changing frequency and random noise
const nPoints = 1000;
const x = new Float64Array(nPoints);
const yTrue = new Float64Array(nPoints);
const y = new Float64Array(nPoints);
for (let i = 0; i < nPoints; i++) {
x[i] = i;
yTrue[i] = 20.0 + 5.0 * Math.sin(x[i] * 0.1) + 2.0 * Math.sin(x[i] * 0.02);
// Gaussian noise
let u1 = Math.random();
let u2 = Math.random();
let z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
y[i] = yTrue[i] + z * 1.2;
}
// Add some sudden spikes (sensor glitches)
for (let i = 200; i < 205; i++) y[i] += 15.0;
for (let i = 600; i < 610; i++) y[i] -= 10.0;
console.log(`Simulating ${nPoints} real-time data points...`);
// 2. Sequential Online Processing
// Full Update Mode (higher accuracy)
console.log("Processing with 'full' update mode...");
const onlineFull = new fastlowess.OnlineLowess(
{ fraction: 0.3, iterations: 3 },
{ windowCapacity: 50, updateMode: "full" }
);
const resFull = new Float64Array(nPoints);
for (let i = 0; i < nPoints; i++) {
const chunkX = new Float64Array([x[i]]);
const chunkY = new Float64Array([y[i]]);
const res = onlineFull.addPoints(chunkX, chunkY);
resFull[i] = res.y[0];
}
// Incremental Update Mode (faster for large windows)
console.log("Processing with 'incremental' update mode...");
const onlineInc = new fastlowess.OnlineLowess(
{ fraction: 0.3, iterations: 3 },
{ windowCapacity: 50, updateMode: "incremental" }
);
const resInc = new Float64Array(nPoints);
for (let i = 0; i < nPoints; i++) {
const chunkX = new Float64Array([x[i]]);
const chunkY = new Float64Array([y[i]]);
const res = onlineInc.addPoints(chunkX, chunkY);
resInc[i] = res.y[0];
}
// Compare results
console.log("\nResults Comparison:");
// Show sample around spike area
console.log("\nSample around spike (indices 198-208):");
console.log("Index\tRaw\t\tTrue\t\tFull\t\tIncremental");
for (let i = 198; i <= 208; i++) {
// Handle initial points where output might be null (though by 198 it should be fine)
const fullVal = isNaN(resFull[i]) ? "NaN" : resFull[i].toFixed(2);
const incVal = isNaN(resInc[i]) ? "NaN" : resInc[i].toFixed(2);
console.log(`${i}\t${y[i].toFixed(2)}\t\t${yTrue[i].toFixed(2)}\t\t${fullVal}\t\t${incVal}`);
}
// Calculate overall statistics
// Filter out initial NaNs
let mseFull = 0;
let mseInc = 0;
let count = 0;
for (let i = 0; i < nPoints; i++) {
if (!isNaN(resFull[i]) && !isNaN(resInc[i])) {
mseFull += Math.pow(resFull[i] - yTrue[i], 2);
mseInc += Math.pow(resInc[i] - yTrue[i], 2);
count++;
}
}
mseFull /= count;
mseInc /= count;
console.log("\nMean Squared Error vs True Signal:");
console.log(` - Full Update: ${mseFull.toFixed(4)}`);
console.log(` - Incremental Update: ${mseInc.toFixed(4)}`);
console.log("\n=== Online Smoothing Example Complete ===");
}
main();
Running the Examples¶
# Install the package
cd bindings/nodejs
npm install
npm run build
# Run examples
node examples/nodejs/batch_smoothing.js
node examples/nodejs/streaming_smoothing.js
node examples/nodejs/online_smoothing.js
Quick Start¶
const { smooth } = require('fastlowess');
// Generate sample data
const x = Array.from({ length: 100 }, (_, i) => i * 0.1);
const y = x.map(xi => Math.sin(xi) + Math.random() * 0.2);
// Basic smoothing
const result = smooth(x, y, { fraction: 0.3 });
console.log('Smoothed values:', result.y);
// With options
const resultWithOptions = smooth(x, y, {
fraction: 0.3,
iterations: 3,
confidenceIntervals: 0.95,
returnDiagnostics: true
});
console.log('R²:', resultWithOptions.diagnostics.rSquared);
TypeScript Support¶
The package includes TypeScript definitions: