Robustness
Outlier handling through iterative reweighting.
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
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.
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 3 , robustness_method = "bisquare" ) $ fit ( x , y )
result = fl . smooth ( x , y , iterations = 3 , robustness_method = "bisquare" )
let model = Lowess :: new ()
. iterations ( 3 )
. robustness_method ( Bisquare )
. adapter ( Batch )
. build () ? ;
result = smooth ( x , y , iterations = 3 , robustness_method = "bisquare" )
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "bisquare" });
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "bisquare" });
auto result = fastlowess :: smooth ( x , y , {
. iterations = 3 ,
. robustness_method = "bisquare"
});
Huber
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.
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 3 , robustness_method = "huber" ) $ fit ( x , y )
result = fl . smooth ( x , y , iterations = 3 , robustness_method = "huber" )
let model = Lowess :: new ()
. iterations ( 3 )
. robustness_method ( Huber )
. adapter ( Batch )
. build () ? ;
result = smooth ( x , y , iterations = 3 , robustness_method = "huber" )
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "huber" });
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "huber" });
auto result = fastlowess :: smooth ( x , y , {
. iterations = 3 ,
. robustness_method = "huber"
});
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.
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 3 , robustness_method = "talwar" ) $ fit ( x , y )
result = fl . smooth ( x , y , iterations = 3 , robustness_method = "talwar" )
let model = Lowess :: new ()
. iterations ( 3 )
. robustness_method ( Talwar )
. adapter ( Batch )
. build () ? ;
result = smooth ( x , y , iterations = 3 , robustness_method = "talwar" )
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "talwar" });
const result = smooth ( x , y , { iterations : 3 , robustnessMethod : "talwar" });
auto result = fastlowess :: smooth ( x , y , {
. iterations = 3 ,
. robustness_method = "talwar"
});
Comparison
Method
Transition
Aggressiveness
Use Case
Bisquare
Smooth
Moderate
General purpose
Huber
Gradual
Mild
Preserve influence
Talwar
Hard
Strong
Extreme contamination
Detecting Outliers
Use robustness weights to identify potential outliers:
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 5 , return_robustness_weights = TRUE ) $ fit ( x , y )
weights <- result $ robustness_weights
outliers <- which ( weights < 0.5 )
cat ( "Potential outliers at indices:" , outliers , "\n" )
result = fl . smooth ( x , y , iterations = 5 , return_robustness_weights = True )
for i , w in enumerate ( result [ "robustness_weights" ]):
if w < 0.5 :
print ( f "Potential outlier at index { i } : weight = { w : .3f } " )
let model = Lowess :: new ()
. iterations ( 5 )
. return_robustness_weights ()
. adapter ( Batch )
. build () ? ;
let result = model . fit ( & x , & y ) ? ;
if let Some ( weights ) = & result . robustness_weights {
for ( i , & w ) in weights . iter (). enumerate () {
if w < 0.5 {
println! ( "Potential outlier at index {}: weight = {:.3}" , i , w );
}
}
}
result = smooth ( x , y , iterations = 5 , return_robustness_weights = true )
for ( i , w ) in enumerate ( result . robustness_weights )
if w < 0.5
println ( "Potential outlier at index $i : weight = $w " )
end
end
const result = smooth ( x , y , { iterations : 5 , returnRobustnessWeights : true });
result . robustnessWeights . forEach (( w , i ) => {
if ( w < 0.5 ) {
console . log ( `Potential outlier at index ${ i } : weight = ${ w . toFixed ( 3 ) } ` );
}
});
const result = smooth ( x , y , { iterations : 5 , returnRobustnessWeights : true });
result . robustnessWeights . forEach (( w , i ) => {
if ( w < 0.5 ) {
console . log ( `Potential outlier at index ${ i } : weight = ${ w . toFixed ( 3 ) } ` );
}
});
auto result = fastlowess :: smooth ( x , y , {
. iterations = 5 ,
. return_robustness_weights = true
});
auto weights = result . robustnessWeights ();
for ( size_t i = 0 ; i < weights . size (); ++ i ) {
if ( weights [ i ] < 0.5 ) {
std :: cout << "Potential outlier at " << i << std :: endl ;
}
}
Scale Estimation
Residuals are scaled before computing robustness weights. Two methods:
Method
Description
Robustness
MAD
Median Absolute Deviation
Very robust
MAR
Mean Absolute Residual
Less robust, faster
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 3 , scaling_method = "mad" ) $ fit ( x , y )
result = fl . smooth ( x , y , iterations = 3 , scaling_method = "mad" )
let model = Lowess :: new ()
. iterations ( 3 )
. scaling_method ( MAD ) // Default
. adapter ( Batch )
. build () ? ;
result = smooth ( x , y , iterations = 3 , scaling_method = "mad" )
const result = smooth ( x , y , { iterations : 3 , scalingMethod : "mad" });
const result = smooth ( x , y , { iterations : 3 , scalingMethod : "mad" });
auto result = fastlowess :: smooth ( x , y , {
. iterations = 3 ,
. scaling_method = "mad"
});
Auto-Convergence
Stop iterations early when weights stabilize:
Performance
Auto-convergence can significantly reduce computation when weights stabilize before reaching max iterations.
R Python Rust Julia Node.js WebAssembly C++
result <- Lowess ( iterations = 10 , auto_converge = 1e-6 ) $ fit ( x , y )
result = fl . smooth ( x , y , iterations = 10 , auto_converge = 1e-6 )
let model = Lowess :: new ()
. iterations ( 10 ) // Maximum iterations
. auto_converge ( 1e-6 ) // Stop when change < 1e-6
. adapter ( Batch )
. build () ? ;
result = smooth ( x , y , iterations = 10 , auto_converge = 1e-6 )
const result = smooth ( x , y , { iterations : 10 , autoConverge : 1e-6 });
const result = smooth ( x , y , { iterations : 10 , autoConverge : 1e-6 });
auto result = fastlowess :: smooth ( x , y , {
. iterations = 10 ,
. auto_converge = 1e-6
});