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
Robustness Iterations
Robustness Methods
Bisquare (Default)
Smooth downweighting. Points transition gradually from full weight to zero.
Use when: General purpose, balanced approach.
library(rfastlowess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)
model <- Lowess(iterations = 3, robustness_method = "bisquare")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.5023423Comparison
| 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(rfastlowess)
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 <- Lowess(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 12: weight = 0.460
#> Potential outlier at index 20: weight = 0.000
#> Potential outlier at index 24: weight = 0.465
#> Potential outlier at index 25: weight = 0.218
#> Potential outlier at index 50: weight = 0.000Scale 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
library(rfastlowess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)
model <- Lowess(iterations = 3, scaling_method = "mad")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.5023423Auto-Convergence
Stop iterations early when weights stabilize:
Performance: Auto-convergence can significantly reduce computation when weights stabilize before reaching max iterations.
library(rfastlowess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 100)
y <- sin(x) + rnorm(100, sd = 0.3)
model <- Lowess(iterations = 10, auto_converge = 1e-6)
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.5015125
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 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.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
#> [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
#> [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] rfastlowess_5.0.0
#>
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.39 desc_1.4.3 R6_2.6.1 fastmap_1.2.0
#> [5] xfun_0.61 cachem_1.1.0 knitr_1.52 htmltools_0.5.9
#> [9] rmarkdown_2.32 lifecycle_1.0.5 cli_3.6.6 sass_0.4.10
#> [13] pkgdown_2.2.1 textshaping_1.0.5 jquerylib_0.1.4 systemfonts_1.3.2
#> [17] compiler_4.6.1 tools_4.6.1 ragg_1.5.2 bslib_0.12.0
#> [21] evaluate_1.0.5 yaml_2.3.12 otel_0.2.0 jsonlite_2.0.0
#> [25] rlang_1.3.0 fs_2.1.0 htmlwidgets_1.6.4