When iterations > 0, LOESS computes robustness
weights by comparing each residual to the current residual scale
estimate \(\hat{\sigma}\). The
scaling_method parameter controls how \(\hat{\sigma}\) is estimated.
\[w_i = B\!\left(\frac{|r_i|}{6 \cdot \hat{\sigma}}\right)\]
| Method | Formula | Robustness | Speed |
|---|---|---|---|
"mad" |
Median absolute deviation from median | Very robust | Moderate |
"mar" |
Median of |residuals| | Robust | Fast |
"mean" |
Mean of |residuals| | Less robust | Fastest |
\[\hat{\sigma} = \text{median}(|r_i - \text{median}(r_i)|)\]
First centers residuals at their median, then takes the median of the absolute deviations. Double use of the median makes it highly resistant to extreme outliers.
Use when: Data may contain outliers (default for most applications).
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\[\hat{\sigma} = \text{median}(|r_i|)\]
Uses the uncentered median — unlike MAD it does not subtract the residual median first. Still robust (median-based), faster (one partial sort instead of two).
Use when: Speed matters and data have minimal systematic bias in residuals.
model <- Loess(iterations = 3, scaling_method = "mar")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.5096452\[\hat{\sigma} = \frac{1}{n}\sum_i |r_i|\]
Arithmetic mean of absolute residuals. Non-robust: a single extreme outlier inflates \(\hat{\sigma}\), causing the bisquare weight function to under-downweight other outliers.
Use when: Clean data with no outliers; maximum computation speed required.
model <- Loess(iterations = 3, scaling_method = "mean")
result <- fit(model, x, y)
cat("Smoothed y[0]:", result$y[1], "\n")
#> Smoothed y[0]: 0.512746| Situation | Recommended Method |
|---|---|
| General purpose, possible outliers | "mad" (default) |
| Speed matters; residuals have minimal systematic bias | "mar" |
| Clean data, no outliers | "mean" |
See vignette("robustness", package = "rfastloess") for a
broader discussion of outlier handling.
library(rfastloess)
set.seed(42)
x <- 1:100
y <- sin(x / 10) + rnorm(100, sd = 0.3)
y[c(20, 50, 80)] <- y[c(20, 50, 80)] + 5 # outliers
methods <- c("mad", "mar", "mean")
colors <- c("blue", "red", "green")
plot(x, y, pch = 16, col = "gray",
main = "Scaling Method Comparison (with outliers)")
for (i in seq_along(methods)) {
model <- Loess(iterations = 3, scaling_method = methods[i])
result <- fit(model, x, y)
lines(result$x, result$y, col = colors[i], lwd = 2)
}
legend("topright", methods, col = colors, lwd = 2)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