Robustness

How Robustness Works

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

  1. Fit initial LOESS
  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 Methods
Robustness Iterations
Robustness Iterations

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.

library(rfastloess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)

model <- Loess(iterations = 3, robustness_method = "bisquare")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.4897657

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.

model <- Loess(iterations = 3, robustness_method = "huber")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.4999039

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.

model <- Loess(iterations = 3, robustness_method = "talwar")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.4730281

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:

library(rfastloess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)
y[c(20, 50, 80)] <- y[c(20, 50, 80)] + 5  # inject outliers

model <- Loess(iterations = 5, outputs = "weights")
result <- fit(model, x, y)

shown <- 0
for (i in seq_along(result$robustness_weights)) {
    if (result$robustness_weights[i] < 0.5 && shown < 5) {
        cat(sprintf("Potential outlier at index %d: weight = %.3f\n",
                    i, result$robustness_weights[i]))
        shown <- shown + 1
    }
}
#> Potential outlier at index 2: weight = 0.245
#> Potential outlier at index 9: weight = 0.363
#> Potential outlier at index 12: weight = 0.052
#> Potential outlier at index 18: weight = 0.383
#> Potential outlier at index 20: weight = 0.000

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
Scaling Methods Comparison
Scaling Methods Comparison
library(rfastloess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)

model <- Loess(iterations = 3, scaling_method = "mad")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.4897657

Auto-Convergence

Stop iterations early when weights stabilize:

Performance: Auto-convergence can significantly reduce computation when weights stabilize before reaching max iterations.

library(rfastloess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)

model <- Loess(iterations = 10, auto_converge = 1e-6)
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.4777674
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] rfastloess_3.0.0 rmarkdown_2.32  
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39    R6_2.6.1         fastmap_1.2.0    xfun_0.61       
#>  [5] maketools_1.3.2  cachem_1.1.0     knitr_1.52       htmltools_0.5.9 
#>  [9] buildtools_1.0.0 lifecycle_1.0.5  cli_3.6.6        sass_0.4.10     
#> [13] jquerylib_0.1.4  compiler_4.6.1   sys_3.4.3        tools_4.6.1     
#> [17] evaluate_1.0.5   bslib_0.12.0     yaml_2.3.12      otel_0.2.0      
#> [21] jsonlite_2.0.0   rlang_1.3.0