Skip to content

Robustness

Outlier handling through iterative reweighting.

Standard LOWESS can be biased by outliers. Robustness iterations downweight points with large residuals:

  1. Fit initial LOWESS
  2. Compute residuals
  3. Assign robustness weights (large residuals → low weight)
  4. Refit using combined distance × robustness weights
  5. Repeat steps 2–4

Robustness Methods

Robustness Iterations


Smooth downweighting. Points transition gradually from full weight to zero.

$$w(u) = \begin{cases} (1 - u^2)^2 & |u| < 1 \ 0 & |u| \geq 1 \end{cases}$$

Use when: General purpose, balanced approach.

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 3, robustness_method: "bisquare" });
const result = model.fit(x, y);
console.log("y[0]:", result.y[0].toFixed(4));
y[0]: 0.1662

Linear penalty beyond threshold. Less aggressive than Bisquare.

$$w(u) = \begin{cases} 1 & |u| \leq k \ k/|u| & |u| > k \end{cases}$$

Use when: Moderate outliers, want to retain some influence.

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 3, robustness_method: "huber" });
const result = model.fit(x, y);
console.log("y[0]:", result.y[0].toFixed(4));
y[0]: 0.1635

Hard threshold. Points are either fully weighted or completely excluded.

$$w(u) = \begin{cases} 1 & |u| \leq k \ 0 & |u| > k \end{cases}$$

Use when: Extreme outliers, want binary exclusion.

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 3, robustness_method: "talwar" });
const result = model.fit(x, y);
console.log("y[0]:", result.y[0].toFixed(4));
y[0]: 0.1410

Method Transition Aggressiveness Use Case
Bisquare Smooth Moderate General purpose
Huber Gradual Mild Preserve influence
Talwar Hard Strong Extreme contamination

Use robustness weights to identify potential outliers:

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 5, return_robustness_weights: true });
const result = model.fit(x, y);
result.robustness_weights.forEach((w, i) => {
if (w < 0.5) {
console.log(`Potential outlier at index ${i}: weight = ${w.toFixed(3)}`);
}
});
Potential outlier at index 26: weight = 0.484
Potential outlier at index 31: weight = 0.470
Potential outlier at index 70: weight = 0.447
Potential outlier at index 75: weight = 0.483

Residuals are scaled before computing robustness weights. Two methods:

Method Formula Robustness
MAD median(|r − median(r)|) Very robust (default)
MAR median(|r|) Robust, uncentered
Mean mean(|r|) Less robust, fastest

Scaling Methods Comparison

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 3, scaling_method: "mad" });
const result = model.fit(x, y);
console.log("y[0]:", result.y[0].toFixed(4));
y[0]: 0.1662

Stop iterations early when weights stabilize:

const { Lowess } = require('fastlowess-wasm');
const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);
const model = new Lowess({ iterations: 10, auto_converge: 1e-6 });
const result = model.fit(x, y);
console.log("Iterations used:", result.iterations_used);
Iterations used: 7