Robustness
Outlier handling through iterative reweighting.
How Robustness Works
Section titled “How Robustness Works”Standard LOWESS can be biased by outliers. Robustness iterations downweight points with large residuals:
- Fit initial LOWESS
- Compute residuals
- Assign robustness weights (large residuals → low weight)
- Refit using combined distance × robustness weights
- Repeat steps 2–4
Robustness Methods
Section titled “Robustness Methods”Bisquare (Default)
Section titled “Bisquare (Default)”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.1662Linear 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.1635Talwar
Section titled “Talwar”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.1410Comparison
Section titled “Comparison”| Method | Transition | Aggressiveness | Use Case |
|---|---|---|---|
| Bisquare | Smooth | Moderate | General purpose |
| Huber | Gradual | Mild | Preserve influence |
| Talwar | Hard | Strong | Extreme contamination |
Detecting Outliers
Section titled “Detecting Outliers”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.484Potential outlier at index 31: weight = 0.470Potential outlier at index 70: weight = 0.447Potential outlier at index 75: weight = 0.483Scale Estimation
Section titled “Scale Estimation”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 |
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.1662Auto-Convergence
Section titled “Auto-Convergence”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