Skip to content

Node.js Examples

Complete Node.js examples demonstrating fastloess with native N-API bindings.

Batch Smoothing

Process complete datasets with confidence intervals and diagnostics.

const fastloess = require('../../bindings/nodejs');

/**
 * fastloess Batch Smoothing - Comprehensive Examples
 *
 * 17 examples covering the full Loess batch API:
 *  1. Basic smoothing
 *  2. Robust smoothing with outliers
 *  3. Uncertainty quantification (confidence/prediction intervals)
 *  4. Cross-validation (K-Fold)
 *  5. Complete diagnostic analysis
 *  6. Different weight functions (kernels)
 *  7. Robustness methods comparison
 *  8. Benchmark
 *  9. Scaling methods (MAR, MAD, Mean)
 * 10. Boundary policies
 * 11. Zero-weight fallback strategies
 * 12. Polynomial degrees + iterations_used
 * 13. Distance metrics
 * 14. Surface modes and standard errors
 * 15. Additional weight functions
 * 16. LOOCV and auto-converge
 * 17. Interpolation tuning (surface_mode effects)
 */

function makeLinear(n) {
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) { x[i] = i; y[i] = 2 * i + 1; }
    return { x, y };
}

// ── Example 1: Basic Smoothing ──────────────────────────────────────────────
function example_1_basic_smoothing() {
    console.log("Example 1: Basic Smoothing");

    const x = new Float64Array([1, 2, 3, 4, 5]);
    const y = new Float64Array([2.0, 4.1, 5.9, 8.2, 9.8]);

    const result = new fastloess.Loess({ fraction: 0.5, iterations: 3 }).fit(x, y);

    console.log(`  fraction_used=${result.fraction_used}`);
    console.log(`  Smoothed: [${Array.from(result.y).map(v => v.toFixed(3)).join(', ')}]`);
    console.log();
}

// ── Example 2: Robust Smoothing with Outliers ────────────────────────────────
function example_2_robust_with_outliers() {
    console.log("Example 2: Robust Smoothing with Outliers");

    const x = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]);
    const y = new Float64Array([2.1, 4.0, 5.9, 25.0, 10.1, 12.0, 14.1, 15.9]); // 25.0 is an outlier

    const result = new fastloess.Loess({
        fraction: 0.5,
        iterations: 5,
        robustness_method: "bisquare",
        return_robustness_weights: true,
        return_residuals: true,
    }).fit(x, y);

    const weights = result.robustness_weights;
    if (weights) {
        for (let i = 0; i < weights.length; i++) {
            if (weights[i] < 0.5) {
                console.log(`  Outlier at index ${i} (y=${y[i]}): weight=${weights[i].toFixed(3)}`);
            }
        }
    }
    console.log(`  Smoothed: [${Array.from(result.y).map(v => v.toFixed(2)).join(', ')}]`);
    console.log();
}

// ── Example 3: Uncertainty Quantification ───────────────────────────────────
function example_3_uncertainty_quantification() {
    console.log("Example 3: Uncertainty Quantification");

    const x = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]);
    const y = new Float64Array([2.1, 3.8, 6.2, 7.9, 10.3, 11.8, 14.1, 15.7]);

    const result = new fastloess.Loess({
        fraction: 0.5,
        iterations: 3,
        confidence_intervals: 0.95,
        prediction_intervals: 0.95,
    }).fit(x, y);

    const cLow = result.confidence_lower;
    const cHigh = result.confidence_upper;
    const pLow = result.prediction_lower;
    const pHigh = result.prediction_upper;

    console.log("  x\t  y_smooth\t  conf[low, high]\t  pred[low, high]");
    for (let i = 0; i < result.y.length; i++) {
        console.log(
            `  ${result.x[i].toFixed(0)}\t  ${result.y[i].toFixed(4)}\t` +
            `  [${cLow[i].toFixed(4)}, ${cHigh[i].toFixed(4)}]\t` +
            `  [${pLow[i].toFixed(4)}, ${pHigh[i].toFixed(4)}]`
        );
    }
    console.log();
}

// ── Example 4: Cross-Validation ──────────────────────────────────────────────
function example_4_cross_validation() {
    console.log("Example 4: Cross-Validation for Parameter Selection");

    const n = 20;
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i + 1;
        y[i] = 2 * x[i] + 1 + Math.sin(x[i] * 0.5);
    }

    const result = new fastloess.Loess({
        cv_fractions: [0.2, 0.3, 0.5, 0.7],
        cv_method: "kfold",
        cv_k: 5,
        iterations: 2,
        return_diagnostics: true,
    }).fit(x, y);

    console.log(`  Selected fraction: ${result.fraction_used}`);
    const scores = result.cv_scores;
    if (scores) {
        const fracs = [0.2, 0.3, 0.5, 0.7];
        console.log("  CV Scores (RMSE per fraction):");
        for (let i = 0; i < fracs.length; i++) {
            console.log(`    fraction=${fracs[i]}: ${scores[i].toFixed(4)}`);
        }
    }
    console.log();
}

// ── Example 5: Complete Diagnostic Analysis ──────────────────────────────────
function example_5_complete_diagnostics() {
    console.log("Example 5: Complete Diagnostic Analysis");

    const x = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]);
    const y = new Float64Array([2.1, 3.8, 6.2, 7.9, 10.3, 11.8, 14.1, 15.7]);

    const result = new fastloess.Loess({
        fraction: 0.5,
        iterations: 3,
        confidence_intervals: 0.95,
        prediction_intervals: 0.95,
        return_diagnostics: true,
        return_residuals: true,
        return_robustness_weights: true,
    }).fit(x, y);

    const diag = result.diagnostics;
    if (diag) {
        console.log("  Diagnostics:");
        console.log(`    RMSE:        ${diag.rmse.toFixed(6)}`);
        console.log(`    MAE:         ${diag.mae.toFixed(6)}`);
        console.log(`    R²:          ${diag.r_squared.toFixed(6)}`);
        console.log(`    Residual SD: ${diag.residual_sd.toFixed(6)}`);
        if (diag.aic != null) console.log(`    AIC:         ${diag.aic.toFixed(2)}`);
        if (diag.aicc != null) console.log(`    AICc:        ${diag.aicc.toFixed(2)}`);
        if (diag.effective_df != null) console.log(`    Eff. DF:     ${diag.effective_df.toFixed(2)}`);
    }
    console.log(`  Smoothed[0]: ${result.y[0].toFixed(5)}`);
    if (result.residuals) console.log(`  residuals[0]: ${result.residuals[0].toFixed(5)}`);
    if (result.robustness_weights) console.log(`  robWeight[0]: ${result.robustness_weights[0].toFixed(4)}`);
    console.log();
}

// ── Example 6: Different Weight Functions (Kernels) ──────────────────────────
function example_6_different_kernels() {
    console.log("Example 6: Different Weight Functions (Kernels)");

    const x = new Float64Array([1, 2, 3, 4, 5]);
    const y = new Float64Array([2.0, 4.1, 5.9, 8.2, 9.8]);

    for (const kernel of ["tricube", "epanechnikov", "gaussian", "biweight"]) {
        const result = new fastloess.Loess({ fraction: 0.5, weight_function: kernel }).fit(x, y);
        console.log(`  ${kernel}: [${Array.from(result.y).map(v => v.toFixed(3)).join(', ')}]`);
    }
    console.log();
}

// ── Example 7: Robustness Methods Comparison ─────────────────────────────────
function example_7_robustness_methods() {
    console.log("Example 7: Robustness Methods Comparison");

    const x = new Float64Array([1, 2, 3, 4, 5]);
    const y = new Float64Array([2.0, 4.1, 20.0, 8.2, 9.8]); // 20.0 is an outlier

    for (const method of ["bisquare", "huber", "talwar"]) {
        const result = new fastloess.Loess({
            fraction: 0.5,
            iterations: 5,
            robustness_method: method,
            return_robustness_weights: true,
        }).fit(x, y);
        const wStr = result.robustness_weights
            ? Array.from(result.robustness_weights).map(v => v.toFixed(3)).join(', ')
            : 'N/A';
        console.log(`  ${method}:`);
        console.log(`    Smoothed: [${Array.from(result.y).map(v => v.toFixed(2)).join(', ')}]`);
        console.log(`    Weights:  [${wStr}]`);
    }
    console.log();
}

// ── Example 8: Benchmark ─────────────────────────────────────────────────────
function example_8_benchmark() {
    console.log("Example 8: Benchmark");

    const n = 1000;
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i;
        y[i] = Math.sin(i * 0.1) + Math.cos(i * 0.01);
    }

    const t0 = process.hrtime.bigint();
    const result = new fastloess.Loess({ parallel: true }).fit(x, y);
    const ms = Number(process.hrtime.bigint() - t0) / 1e6;

    console.log(`  ${n} points in ${ms.toFixed(2)}ms`);
    console.log(`  fraction_used=${result.fraction_used}, y[0]=${result.y[0].toFixed(4)}`);
    console.log();
}

// ── Example 9: Scaling Methods (MAR, MAD, Mean) ──────────────────────────────
function example_9_scaling_methods() {
    console.log("Example 9: Scaling Methods");

    const { x, y } = makeLinear(20);

    for (const method of ["mar", "mad", "mean"]) {
        const result = new fastloess.Loess({ fraction: 0.5, scaling_method: method }).fit(x, y);
        console.log(`  ${method}: y[0]=${result.y[0].toFixed(3)}`);
    }
    console.log();
}

// ── Example 10: Boundary Policies ────────────────────────────────────────────
function example_10_boundary_policies() {
    console.log("Example 10: Boundary Policies");

    const { x, y } = makeLinear(30);

    for (const policy of ["extend", "reflect", "zero", "noboundary"]) {
        const result = new fastloess.Loess({ fraction: 0.5, boundary_policy: policy }).fit(x, y);
        console.log(
            `  ${policy}: first=${result.y[0].toFixed(2)}, last=${result.y[result.y.length - 1].toFixed(2)}`
        );
    }
    console.log();
}

// ── Example 11: Zero-Weight Fallback Strategies ───────────────────────────────
function example_11_zero_weight_fallback() {
    console.log("Example 11: Zero-Weight Fallback Strategies");

    const { x, y } = makeLinear(20);

    for (const fb of ["use_local_mean", "return_original", "return_none"]) {
        const result = new fastloess.Loess({ fraction: 0.5, zero_weight_fallback: fb }).fit(x, y);
        console.log(`  ${fb}: y[0]=${result.y[0].toFixed(3)}`);
    }
    console.log();
}

// ── Example 12: Polynomial Degrees + iterations_used ──────────────────────────
function example_12_polynomial_degrees() {
    console.log("Example 12: Polynomial Degrees");

    const { x, y } = makeLinear(30);

    for (const deg of ["constant", "linear", "quadratic", "cubic", "quartic"]) {
        const result = new fastloess.Loess({
            fraction: 0.5,
            iterations: 2,
            degree: deg,
        }).fit(x, y);
        console.log(
            `  ${deg}: y[0]=${result.y[0].toFixed(3)}, iterations_used=${result.iterations_used}`
        );
    }
    console.log();
}

// ── Example 13: Distance Metrics ─────────────────────────────────────────────
function example_13_distance_metrics() {
    console.log("Example 13: Distance Metrics");

    const { x, y } = makeLinear(20);

    for (const metric of ["euclidean", "normalized", "manhattan", "chebyshev"]) {
        const result = new fastloess.Loess({ fraction: 0.5, distance_metric: metric }).fit(x, y);
        console.log(`  ${metric}: y[0]=${result.y[0].toFixed(3)}`);
    }

    // Minkowski with custom p via "minkowski:p" format
    const rMink = new fastloess.Loess({ fraction: 0.5, distance_metric: "minkowski:3" }).fit(x, y);
    console.log(`  minkowski(p=3): y[0]=${rMink.y[0].toFixed(3)}`);

    console.log();
}

// ── Example 14: Surface Modes and Standard Errors ────────────────────────────
function example_14_surface_modes_and_se() {
    console.log("Example 14: Surface Modes and Standard Errors");

    const { x, y } = makeLinear(30);

    // Direct surface — fits every point exactly; SE fields fully populated
    const rDirect = new fastloess.Loess({
        fraction: 0.5,
        surface_mode: "direct",
        return_se: true,
        confidence_intervals: 0.95,
        prediction_intervals: 0.95,
    }).fit(x, y);

    console.log("  surface_mode=direct:");
    console.log(`    confidence_lower non-null: ${rDirect.confidence_lower != null}`);
    console.log(`    prediction_lower non-null: ${rDirect.prediction_lower != null}`);
    if (rDirect.standard_errors) console.log(`    standard_errors[0]: ${rDirect.standard_errors[0].toFixed(4)}`);
    if (rDirect.enp != null) console.log(`    enp: ${rDirect.enp.toFixed(3)}`);
    if (rDirect.trace_hat != null) console.log(`    trace_hat: ${rDirect.trace_hat.toFixed(3)}`);
    if (rDirect.delta1 != null) console.log(`    delta1: ${rDirect.delta1.toFixed(3)}`);
    if (rDirect.delta2 != null) console.log(`    delta2: ${rDirect.delta2.toFixed(3)}`);
    if (rDirect.residual_scale != null) console.log(`    residual_scale: ${rDirect.residual_scale.toFixed(4)}`);
    if (rDirect.leverage) console.log(`    leverage[0]: ${rDirect.leverage[0].toFixed(4)}`);

    // Interpolation surface — faster, approximate
    const rInterp = new fastloess.Loess({
        fraction: 0.5,
        surface_mode: "interpolation",
        return_se: true,
    }).fit(x, y);

    console.log("  surface_mode=interpolation:");
    console.log(`    y[0]: ${rInterp.y[0].toFixed(3)}`);
    if (rInterp.standard_errors) console.log(`    standard_errors[0]: ${rInterp.standard_errors[0].toFixed(4)}`);
    console.log();
}

// ── Example 15: Additional Weight Functions (Uniform, Triangle, Cosine) ───────
function example_15_additional_kernels() {
    console.log("Example 15: Additional Weight Functions (Uniform, Triangle, Cosine)");

    const x = new Float64Array([1, 2, 3, 4, 5]);
    const y = new Float64Array([2.0, 4.1, 5.9, 8.2, 9.8]);

    for (const kernel of ["uniform", "triangle", "cosine"]) {
        const result = new fastloess.Loess({ fraction: 0.5, weight_function: kernel }).fit(x, y);
        console.log(`  ${kernel}: [${Array.from(result.y).map(v => v.toFixed(3)).join(', ')}]`);
    }
    console.log();
}

// ── Example 16: LOOCV, K-Fold, and Auto-Converge ─────────────────────────────
function example_16_loocv_and_auto_converge() {
    console.log("Example 16: LOOCV, K-Fold, and Auto-Converge");

    const n = 20;
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i + 1;
        y[i] = 2 * x[i] + 1 + Math.sin(x[i] * 0.5);
    }

    // Leave-one-out cross-validation
    const rLoocv = new fastloess.Loess({
        cv_fractions: [0.3, 0.5, 0.7],
        cv_method: "loocv",
    }).fit(x, y);
    console.log(`  LOOCV selected fraction: ${rLoocv.fraction_used}`);
    if (rLoocv.cv_scores) {
        console.log(`  LOOCV scores: [${Array.from(rLoocv.cv_scores).map(v => v.toFixed(4)).join(', ')}]`);
    }

    // K-Fold cross-validation
    const rKfold = new fastloess.Loess({
        cv_fractions: [0.2, 0.4, 0.6],
        cv_method: "kfold",
        cv_k: 5,
    }).fit(x, y);
    console.log(`  KFold(k=5) selected fraction: ${rKfold.fraction_used}`);
    if (rKfold.cv_scores) {
        console.log(`  KFold scores: [${Array.from(rKfold.cv_scores).map(v => v.toFixed(4)).join(', ')}]`);
    }

    // Auto-converge: stop robustness iterations when change < tolerance
    const rAc = new fastloess.Loess({
        fraction: 0.5,
        auto_converge: 1e-4,
    }).fit(x, y);
    console.log(`  auto_converge=1e-4: iterations_used=${rAc.iterations_used}`);
    console.log();
}

// ── Example 17: Interpolation Tuning (surface_mode effects) ───────────────────
function example_17_interpolation_tuning() {
    console.log("Example 17: Interpolation Tuning (surface_mode effects)");

    const n = 50;
    const { x, y } = makeLinear(n);

    // Default (interpolation) — fastest, uses a spatial grid
    const rInterp = new fastloess.Loess({
        fraction: 0.5,
        surface_mode: "interpolation",
    }).fit(x, y);
    console.log(`  interpolation: y[0]=${rInterp.y[0].toFixed(3)}, y[-1]=${rInterp.y[n - 1].toFixed(3)}`);

    // Direct — fits every point exactly, more accurate but slower
    const rDirect = new fastloess.Loess({
        fraction: 0.5,
        surface_mode: "direct",
    }).fit(x, y);
    console.log(`  direct:        y[0]=${rDirect.y[0].toFixed(3)}, y[-1]=${rDirect.y[n - 1].toFixed(3)}`);

    // Fraction sweep with direct surface
    for (const frac of [0.2, 0.5, 0.8]) {
        const r = new fastloess.Loess({ fraction: frac, surface_mode: "direct" }).fit(x, y);
        console.log(`  direct fraction=${frac}: y[0]=${r.y[0].toFixed(3)}`);
    }

    // Interpolation + SE for hat-matrix statistics
    const rSe = new fastloess.Loess({
        fraction: 0.5,
        surface_mode: "interpolation",
        return_se: true,
    }).fit(x, y);
    if (rSe.enp != null) console.log(`  interpolation+SE enp: ${rSe.enp.toFixed(3)}`);
    console.log();
}

// ── Main ──────────────────────────────────────────────────────────────────────
function main() {
    console.log("=".repeat(60));
    console.log("fastloess Batch Smoothing - Comprehensive Examples");
    console.log("=".repeat(60));
    console.log();

    example_1_basic_smoothing();
    example_2_robust_with_outliers();
    example_3_uncertainty_quantification();
    example_4_cross_validation();
    example_5_complete_diagnostics();
    example_6_different_kernels();
    example_7_robustness_methods();
    example_8_benchmark();
    example_9_scaling_methods();
    example_10_boundary_policies();
    example_11_zero_weight_fallback();
    example_12_polynomial_degrees();
    example_13_distance_metrics();
    example_14_surface_modes_and_se();
    example_15_additional_kernels();
    example_16_loocv_and_auto_converge();
    example_17_interpolation_tuning();

    console.log("=== Batch Smoothing Examples Complete ===");
}

main();

Download batch_smoothing.js


Streaming Smoothing

Process large datasets in memory-efficient chunks.

const fastloess = require('../../bindings/nodejs');

/**
 * fastloess Streaming Smoothing - Comprehensive Examples
 *
 * 9 examples covering the full StreamingLoess API:
 *  1. Basic chunked processing
 *  2. Chunk size comparison
 *  3. Overlap strategies
 *  4. Large dataset processing
 *  5. Outlier handling in streaming mode
 *  6. File-based streaming simulation
 *  7. Benchmark (sequential streaming)
 *  8. Merge strategies (average, weighted_average, take_first, take_last)
 *  9. Advanced streaming options
 */

function makeLinear(n) {
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) { x[i] = i; y[i] = 2 * i + 1; }
    return { x, y };
}

// ── Example 1: Basic Chunked Processing ─────────────────────────────────────
function example_1_basic_chunked_processing() {
    console.log("Example 1: Basic Chunked Processing");

    const n = 50;
    const { x, y } = makeLinear(n);
    const chunk_size = 15;
    const overlap = 5;

    const streamer = new fastloess.StreamingLoess(
        { fraction: 0.5, iterations: 2, return_residuals: true },
        { chunk_size, overlap }
    );

    console.log(`  Dataset: ${n} points, chunk=${chunk_size}, overlap=${overlap}`);

    let totalProcessed = 0;
    let chunkIdx = 0;
    for (let start = 0; start < n; start += chunk_size - overlap) {
        const end = Math.min(start + chunk_size, n);
        const res = streamer.process_chunk(x.subarray(start, end), y.subarray(start, end));
        if (res.x.length > 0) {
            totalProcessed += res.x.length;
            console.log(`  Chunk ${chunkIdx}: ${res.x.length} pts (x: ${res.x[0].toFixed(0)}..${res.x[res.x.length - 1].toFixed(0)})`);
        }
        chunkIdx++;
    }
    const fin = streamer.finalize();
    if (fin.x.length > 0) {
        totalProcessed += fin.x.length;
        console.log(`  Finalize: ${fin.x.length} remaining pts`);
    }
    console.log(`  Total: ${totalProcessed}/${n}`);
    console.log();
}

// ── Example 2: Chunk Size Comparison ────────────────────────────────────────
function example_2_chunk_size_comparison() {
    console.log("Example 2: Chunk Size Comparison");

    const n = 100;
    const { x, y } = makeLinear(n);

    for (const [chunk_size, overlap, label] of [[20, 5, "Small"], [50, 10, "Medium"], [80, 15, "Large"]]) {
        const streamer = new fastloess.StreamingLoess(
            { fraction: 0.5, iterations: 1 },
            { chunk_size, overlap }
        );
        let chunks = 0, total = 0;
        for (let start = 0; start < n; start += chunk_size - overlap) {
            const end = Math.min(start + chunk_size, n);
            const res = streamer.process_chunk(x.subarray(start, end), y.subarray(start, end));
            if (res.x.length > 0) { chunks++; total += res.x.length; }
        }
        const fin = streamer.finalize();
        if (fin.x.length > 0) { chunks++; total += fin.x.length; }
        console.log(`  ${label} (size=${chunk_size}, overlap=${overlap}): chunks=${chunks}, total=${total}`);
    }
    console.log();
}

// ── Example 3: Overlap Strategies ────────────────────────────────────────────
function example_3_overlap_strategies() {
    console.log("Example 3: Overlap Strategies");

    const n = 100;
    const { x, y } = makeLinear(n);

    for (const [overlap, label] of [[0, "No overlap"], [10, "10-pt overlap"], [20, "20-pt overlap"]]) {
        const chunk_size = 40;
        const streamer = new fastloess.StreamingLoess(
            { fraction: 0.5 },
            { chunk_size, overlap }
        );
        let total = 0;
        const step = chunk_size - overlap;
        // Feed only full-size chunks; finalize() handles remaining data
        for (let start = 0; start + chunk_size <= n; start += step) {
            const res = streamer.process_chunk(x.subarray(start, start + chunk_size), y.subarray(start, start + chunk_size));
            total += res.x.length;
        }
        total += streamer.finalize().x.length;
        console.log(`  ${label} (overlap=${overlap}): total points output=${total}`);
    }
    console.log();
}

// ── Example 4: Large Dataset Processing ──────────────────────────────────────
function example_4_large_dataset_processing() {
    console.log("Example 4: Large Dataset Processing");

    const n = 10000;
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) { x[i] = i; y[i] = Math.sin(i * 0.01) + i * 0.001; }

    const chunk_size = 500;
    const overlap = 50;

    const streamer = new fastloess.StreamingLoess(
        { fraction: 0.05, iterations: 2 },
        { chunk_size, overlap }
    );

    let total = 0;
    const step = chunk_size - overlap;
    for (let start = 0; start < n; start += step) {
        const end = Math.min(start + chunk_size, n);
        const res = streamer.process_chunk(x.subarray(start, end), y.subarray(start, end));
        total += res.x.length;
        if (total > 0 && total % 2000 < step) {
            console.log(`  Progress: ~${total} points smoothed`);
        }
    }
    total += streamer.finalize().x.length;
    console.log(`  Total processed: ${total}/${n}`);
    console.log(`  Memory efficiency: constant (chunk=${chunk_size})`);
    console.log();
}

// ── Example 5: Outlier Handling in Streaming Mode ─────────────────────────────
function example_5_outlier_handling() {
    console.log("Example 5: Outlier Handling in Streaming Mode");

    const n = 100;
    const x = new Float64Array(n);
    const y = new Float64Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i;
        y[i] = 2 * i + 1 + Math.sin(i * 0.2) * 2;
        if (i === 25 || i === 50 || i === 75) y[i] += 50; // Outliers
    }

    for (const method of ["bisquare", "huber", "talwar"]) {
        const streamer = new fastloess.StreamingLoess(
            { fraction: 0.5, iterations: 5, robustness_method: method, return_residuals: true },
            { chunk_size: 30, overlap: 10 }
        );
        let largeResiduals = 0;
        for (let start = 0; start < n; start += 20) {
            const end = Math.min(start + 30, n);
            const res = streamer.process_chunk(x.subarray(start, end), y.subarray(start, end));
            if (res.residuals) {
                for (const r of res.residuals) { if (Math.abs(r) > 10) largeResiduals++; }
            }
        }
        const fin = streamer.finalize();
        if (fin.residuals) {
            for (const r of fin.residuals) { if (Math.abs(r) > 10) largeResiduals++; }
        }
        console.log(`  ${method}: points with |residual|>10: ${largeResiduals}`);
    }
    console.log();
}

// ── Example 6: File-Based Streaming Simulation ───────────────────────────────
function example_6_file_simulation() {
    console.log("Example 6: File-Based Streaming Simulation");
    console.log("  Simulating: Read from input.csv -> Smooth -> Write to output.csv");

    const totalLines = 200;
    const chunk_size = 50;
    const overlap = 10;

    const streamer = new fastloess.StreamingLoess(
        { fraction: 0.5, iterations: 2, return_residuals: true },
        { chunk_size, overlap }
    );

    let outputLines = 0;
    for (let ci = 0; ci < Math.ceil(totalLines / (chunk_size - overlap)); ci++) {
        const start = ci * (chunk_size - overlap);
        const end = Math.min(start + chunk_size, totalLines);

        // Simulate reading a chunk from a file
        const xChunk = new Float64Array(end - start);
        const yChunk = new Float64Array(end - start);
        for (let j = 0; j < end - start; j++) {
            xChunk[j] = start + j;
            yChunk[j] = 2 * xChunk[j] + 1 + Math.sin(xChunk[j] * 0.1) * 3;
        }

        console.log(`  Reading chunk ${ci} (lines ${start}..${end - 1})`);
        const res = streamer.process_chunk(xChunk, yChunk);
        if (res.x.length > 0) {
            outputLines += res.x.length;
            console.log(`    -> Writing ${res.x.length} smoothed pts (total: ${outputLines})`);
        }
    }

    const fin = streamer.finalize();
    if (fin.x.length > 0) {
        outputLines += fin.x.length;
        console.log(`  Finalizing: Writing ${fin.x.length} remaining pts`);
    }
    console.log(`  Input: ${totalLines}, Output: ${outputLines}`);
    console.log();
}

// ── Example 7: Benchmark (Sequential Streaming) ───────────────────────────────
function example_7_benchmark() {
    console.log("Example 7: Benchmark (Sequential Streaming)");

    const n = 1000;
    const chunk_size = 100;
    const overlap = 10;

    const streamer = new fastloess.StreamingLoess(
        { fraction: 0.5, iterations: 3 },
        { chunk_size, overlap }
    );

    const t0 = process.hrtime.bigint();
    let total = 0;
    const step = chunk_size - overlap;
    for (let start = 0; start < n; start += step) {
        const end = Math.min(start + chunk_size, n);
        const xc = new Float64Array(end - start);
        const yc = new Float64Array(end - start);
        for (let j = 0; j < end - start; j++) {
            xc[j] = start + j;
            yc[j] = Math.sin(xc[j] * 0.1) + Math.cos(xc[j] * 0.01);
        }
        total += streamer.process_chunk(xc, yc).x.length;
    }
    total += streamer.finalize().x.length;
    const ms = Number(process.hrtime.bigint() - t0) / 1e6;

    console.log(`  ${total} points in ${ms.toFixed(2)}ms`);
    console.log(`  chunk=${chunk_size}, overlap=${overlap}`);
    console.log();
}

// ── Example 8: Merge Strategies ──────────────────────────────────────────────
function example_8_merge_strategies() {
    console.log("Example 8: Merge Strategies");

    const n = 50;
    const { x, y } = makeLinear(n);

    for (const strategy of ["average", "weighted_average", "take_first", "take_last"]) {
        const streamer = new fastloess.StreamingLoess(
            { fraction: 0.5, iterations: 2 },
            { chunk_size: 20, overlap: 5, merge_strategy: strategy }
        );
        let total = 0;
        for (let start = 0; start < n; start += 15) {
            const end = Math.min(start + 20, n);
            total += streamer.process_chunk(x.subarray(start, end), y.subarray(start, end)).x.length;
        }
        total += streamer.finalize().x.length;
        console.log(`  ${strategy}: total=${total}`);
    }
    console.log();
}

// ── Example 9: Advanced Streaming Options ─────────────────────────────────────
function example_9_advanced_options() {
    console.log("Example 9: Advanced Streaming Options");

    const n = 50;
    const { x, y } = makeLinear(n);

    const streamer = new fastloess.StreamingLoess(
        {
            fraction: 0.5,
            iterations: 2,
            degree: "quadratic",
            scaling_method: "mar",
            boundary_policy: "reflect",
            zero_weight_fallback: "return_original",
            distance_metric: "manhattan",
            surface_mode: "direct",
            return_se: true,
            return_diagnostics: true,
            return_robustness_weights: true,
            auto_converge: 1e-3,
        },
        { chunk_size: 20, overlap: 5 }
    );

    let total = 0;
    for (let start = 0; start < n; start += 15) {
        const end = Math.min(start + 20, n);
        total += streamer.process_chunk(x.subarray(start, end), y.subarray(start, end)).x.length;
    }
    const fin = streamer.finalize();
    total += fin.x.length;

    console.log(`  total points: ${total}`);
    if (fin.standard_errors && fin.standard_errors.length > 0) {
        console.log(`  standard_errors[0]: ${fin.standard_errors[0].toFixed(4)}`);
    }
    if (fin.diagnostics) {
        console.log(`  diagnostics.rmse: ${fin.diagnostics.rmse.toFixed(3)}`);
        console.log(`  diagnostics.r_squared: ${fin.diagnostics.r_squared.toFixed(3)}`);
        if (fin.diagnostics.aic != null) console.log(`  diagnostics.aic: ${fin.diagnostics.aic.toFixed(3)}`);
    }
    if (fin.robustness_weights && fin.robustness_weights.length > 0) {
        console.log(`  robustness_weights[0]: ${fin.robustness_weights[0].toFixed(4)}`);
    }
    console.log();
}

// ── Main ──────────────────────────────────────────────────────────────────────
function main() {
    console.log("=".repeat(60));
    console.log("fastloess Streaming Smoothing - Comprehensive Examples");
    console.log("=".repeat(60));
    console.log();

    example_1_basic_chunked_processing();
    example_2_chunk_size_comparison();
    example_3_overlap_strategies();
    example_4_large_dataset_processing();
    example_5_outlier_handling();
    example_6_file_simulation();
    example_7_benchmark();
    example_8_merge_strategies();
    example_9_advanced_options();

    console.log("=== Streaming Smoothing Examples Complete ===");
}

main();

Download streaming_smoothing.js


Online Smoothing

Real-time smoothing with sliding window for streaming data.

const fastloess = require('../../bindings/nodejs');

/**
 * fastloess Online Smoothing - Comprehensive Examples
 *
 * 9 examples covering the full OnlineLoess API:
 *  1. Basic incremental processing
 *  2. Real-time sensor data simulation
 *  3. Outlier handling in online mode
 *  4. Window size comparison
 *  5. Memory-bounded processing (embedded systems)
 *  6. Sliding window behavior
 *  7. Benchmark (sequential online)
 *  8. Update modes (Full vs Incremental) and min_points
 *  9. Advanced online options
 */

// ── Example 1: Basic Incremental Processing ──────────────────────────────────
function example_1_basic_streaming() {
    console.log("Example 1: Basic Incremental Processing");

    const data = [
        [1, 3.1], [2, 5.0], [3, 7.2], [4, 8.9], [5, 11.1],
        [6, 13.0], [7, 15.2], [8, 16.8], [9, 19.1], [10, 21.0],
    ];

    const model = new fastloess.OnlineLoess(
        { fraction: 0.5, iterations: 2, return_residuals: true },
        { window_capacity: 5 }
    );

    console.log(`  ${"X".padStart(8)} ${"Y_obs".padStart(12)} ${"Y_smooth".padStart(12)}`);
    for (const [x, y] of data) {
        const res = model.add_point(x, y);
        const smoothed = res !== null ? res.smoothed.toFixed(2) : "(buffering)";
        console.log(`  ${x.toFixed(2).padStart(8)} ${y.toFixed(2).padStart(12)} ${smoothed.padStart(12)}`);
    }
    console.log();
}

// ── Example 2: Real-Time Sensor Data Simulation ───────────────────────────────
function example_2_sensor_data_simulation() {
    console.log("Example 2: Real-Time Sensor Data Simulation");
    console.log("  Simulating temperature sensor readings with noise...");

    const n = 24; // 24 hours
    const model = new fastloess.OnlineLoess(
        { fraction: 0.4, iterations: 3, robustness_method: "bisquare", return_residuals: true },
        { window_capacity: 12 }
    );

    console.log(`  ${"Hour".padStart(6)} ${"Raw".padStart(12)} ${"Smoothed".padStart(12)}`);
    for (let hour = 0; hour < n; hour++) {
        const baseTemp = 20.0;
        const cycle = 5.0 * Math.sin(hour * Math.PI / 12.0);
        const noise = ((hour * 7) % 11) * 0.3 - 1.5;
        const temp = baseTemp + cycle + noise;

        const res = model.add_point(hour, temp);
        if (res !== null) {
            console.log(
                `  ${hour.toString().padStart(6)} ${temp.toFixed(2).padStart(12)}°C ${res.smoothed.toFixed(2).padStart(12)}°C`
            );
        } else {
            console.log(`  ${hour.toString().padStart(6)} ${temp.toFixed(2).padStart(12)}°C ${"(warming up)".padStart(13)}`);
        }
    }
    console.log();
}

// ── Example 3: Outlier Handling in Online Mode ────────────────────────────────
function example_3_outlier_handling() {
    console.log("Example 3: Outlier Handling in Online Mode");

    const data = [
        [1, 2.0], [2, 4.1], [3, 5.9],
        [4, 25.0], // Outlier!
        [5, 10.1], [6, 12.0], [7, 14.1],
        [8, 50.0], // Outlier!
        [9, 18.0], [10, 20.1],
    ];

    for (const method of ["bisquare", "talwar"]) {
        const model = new fastloess.OnlineLoess(
            { fraction: 0.5, iterations: 5, robustness_method: method, return_residuals: true },
            { window_capacity: 6 }
        );
        const smoothed = [];
        for (const [x, y] of data) {
            const res = model.add_point(x, y);
            if (res !== null) smoothed.push(res.smoothed.toFixed(1));
        }
        console.log(`  ${method}: [${smoothed.join(', ')}]`);
    }
    console.log();
}

// ── Example 4: Window Size Comparison ────────────────────────────────────────
function example_4_window_size_comparison() {
    console.log("Example 4: Window Size Comparison");

    const data = Array.from({ length: 20 }, (_, i) => {
        const x = i + 1;
        return [x, 2 * x + Math.sin(x * 0.5) * 3];
    });

    for (const windowSize of [5, 10, 15]) {
        const model = new fastloess.OnlineLoess(
            { fraction: 0.5, iterations: 2 },
            { window_capacity: windowSize }
        );
        const smoothed = [];
        for (const [x, y] of data) {
            const res = model.add_point(x, y);
            if (res !== null) smoothed.push(res.smoothed);
        }
        const last5 = smoothed.slice(-5).map(v => v.toFixed(2));
        console.log(`  window_capacity=${windowSize}: last 5 = [${last5.join(', ')}]`);
    }
    console.log();
}

// ── Example 5: Memory-Bounded Processing ──────────────────────────────────────
function example_5_memory_bounded_processing() {
    console.log("Example 5: Memory-Bounded Processing (Embedded Systems)");

    const total = 1000;
    const model = new fastloess.OnlineLoess(
        { fraction: 0.3, iterations: 1 },
        { window_capacity: 20 }
    );

    let count = 0;
    let lastSmoothed = 0;
    for (let i = 0; i < total; i++) {
        const x = i;
        const y = 2 * x + Math.sin(x * 0.1) * 5 + ((i % 7) - 3) * 0.5;
        const res = model.add_point(x, y);
        if (res !== null) {
            count++;
            lastSmoothed = res.smoothed;
            if (count % 200 === 0) {
                console.log(`  Processed: ${count.toString().padStart(4)} pts | smoothed=${lastSmoothed.toFixed(2)}`);
            }
        }
    }
    console.log(`  Total processed: ${count}, final smoothed: ${lastSmoothed.toFixed(2)}`);
    console.log(`  Memory: constant (window=20)`);
    console.log();
}

// ── Example 6: Sliding Window Behavior ───────────────────────────────────────
function example_6_sliding_window_behavior() {
    console.log("Example 6: Sliding Window Behavior");

    const data = [[1, 2], [2, 4], [3, 6], [4, 8], [5, 10], [6, 12], [7, 14], [8, 16]];
    const model = new fastloess.OnlineLoess(
        { fraction: 0.6, iterations: 0, return_residuals: true },
        { window_capacity: 4 }
    );

    console.log(`  ${"Pt".padStart(4)} ${"X".padStart(6)} ${"Y".padStart(8)} ${"Smoothed".padStart(10)} ${"Status".padStart(22)}`);
    data.forEach(([x, y], i) => {
        const res = model.add_point(x, y);
        if (res !== null) {
            console.log(`  ${(i + 1).toString().padStart(4)} ${x.toFixed(0).padStart(6)} ${y.toFixed(0).padStart(8)} ${res.smoothed.toFixed(2).padStart(10)} ${"Window full (sliding)".padStart(22)}`);
        } else {
            console.log(`  ${(i + 1).toString().padStart(4)} ${x.toFixed(0).padStart(6)} ${y.toFixed(0).padStart(8)} ${"-".padStart(10)} ${`Filling (${i + 1}/4)`.padStart(22)}`);
        }
    });
    console.log("  Output starts after window fills (4 pts), then slides.");
    console.log();
}

// ── Example 7: Benchmark (Sequential Online) ──────────────────────────────────
function example_7_benchmark() {
    console.log("Example 7: Benchmark (Sequential Online)");

    const n = 1000;
    const model = new fastloess.OnlineLoess(
        { fraction: 0.5, iterations: 3 },
        { window_capacity: 10 }
    );

    const t0 = process.hrtime.bigint();
    let count = 0;
    for (let i = 0; i < n; i++) {
        const x = i;
        const y = Math.sin(x * 0.1) + Math.cos(x * 0.01);
        const res = model.add_point(x, y);
        if (res !== null) count++;
    }
    const ms = Number(process.hrtime.bigint() - t0) / 1e6;

    console.log(`  ${count} pts processed in ${ms.toFixed(2)}ms`);
    console.log(`  window_capacity=10`);
    console.log();
}

// ── Example 8: Update Modes (Full vs Incremental) and min_points ───────────────
function example_8_update_modes() {
    console.log("Example 8: Update Modes (Full vs Incremental) and min_points");

    const data = Array.from({ length: 30 }, (_, i) => [i, 2 * i + 1]);

    for (const mode of ["full", "incremental"]) {
        const model = new fastloess.OnlineLoess(
            { fraction: 0.5, iterations: 2 },
            { window_capacity: 15, min_points: 5, update_mode: mode }
        );
        let emitted = 0;
        for (const [x, y] of data) {
            const res = model.add_point(x, y);
            if (res !== null) emitted++;
        }
        console.log(`  ${mode}: ${emitted} points emitted (out of ${data.length})`);
    }

    // Show iterations_used from the returned OnlineOutput
    const model = new fastloess.OnlineLoess(
        { fraction: 0.5, iterations: 2, return_residuals: true, return_robustness_weights: true },
        { window_capacity: 10, min_points: 3 }
    );
    let lastSmoothed = null;
    let lastIterations = null;
    for (const [x, y] of data) {
        const res = model.add_point(x, y);
        if (res !== null) { lastSmoothed = res.smoothed; lastIterations = res.iterations_used; }
    }
    if (lastSmoothed !== null) {
        console.log(`  last smoothed: ${lastSmoothed.toFixed(3)}`);
        if (lastIterations !== null) console.log(`  iterations_used: ${lastIterations}`);
    }
    console.log();
}

// ── Example 9: Advanced Online Options ────────────────────────────────────────
function example_9_advanced_online_options() {
    console.log("Example 9: Advanced Online Options");

    const data = Array.from({ length: 30 }, (_, i) => [i, 2 * i + 1]);

    const model = new fastloess.OnlineLoess(
        {
            fraction: 0.5,
            iterations: 2,
            degree: "quadratic",
            scaling_method: "mar",
            boundary_policy: "reflect",
            zero_weight_fallback: "return_original",
            distance_metric: "chebyshev",
            auto_converge: 1e-3,
            return_residuals: true,
            return_robustness_weights: true,
        },
        { window_capacity: 15, min_points: 5 }
    );

    let emitted = 0;
    let lastSmoothed = null;
    for (const [x, y] of data) {
        const res = model.add_point(x, y);
        if (res !== null) { emitted++; lastSmoothed = res.smoothed; }
    }

    console.log(`  emitted: ${emitted}`);
    if (lastSmoothed !== null) {
        console.log(`  last smoothed: ${lastSmoothed.toFixed(3)}`);
    }
    console.log();
}

// ── Main ──────────────────────────────────────────────────────────────────────
function main() {
    console.log("=".repeat(60));
    console.log("fastloess Online Smoothing - Comprehensive Examples");
    console.log("=".repeat(60));
    console.log();

    example_1_basic_streaming();
    example_2_sensor_data_simulation();
    example_3_outlier_handling();
    example_4_window_size_comparison();
    example_5_memory_bounded_processing();
    example_6_sliding_window_behavior();
    example_7_benchmark();
    example_8_update_modes();
    example_9_advanced_online_options();

    console.log("=== Online Smoothing Examples Complete ===");
}

main();

Download online_smoothing.js


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 { Loess } = require('fastloess');

// 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 model = new Loess({ fraction: 0.3 });
const result = model.fit(x, y);
console.log('Smoothed values:', result.y);

// With options
const resultWithOptions = new Loess({
    fraction: 0.3,
    iterations: 3,
    confidence_intervals: 0.95,
    return_diagnostics: true
}).fit(x, y);

console.log('R²:', resultWithOptions.diagnostics.r_squared);

TypeScript Support

The package includes TypeScript definitions:

import { Loess, LoessResult } from 'fastloess';

const options = {
    fraction: 0.3,
    iterations: 3,
    confidence_intervals: 0.95
};

const result: LoessResult = new Loess(options).fit(x, y);