rfastloess and base R’s stats::loess() both
fit locally weighted polynomials. Their defaults and result objects
differ, so a useful numerical comparison needs to match the fit settings
first. This page shows how to do that and where the two APIs offer
different capabilities.
For a non-robust, one-dimensional local linear fit, use the same span
and polynomial degree, disable boundary padding, and request the direct
surface. stats::loess() uses the Gaussian family by
default; iterations = 0L disables robust reweighting in
Loess().
library(rfastloess)
set.seed(42)
x <- seq(0, 2 * pi, length.out = 60)
y <- sin(x) + rnorm(length(x), sd = 0.2)
reference <- stats::loess(
y ~ x, span = 0.5, degree = 1, family = "gaussian",
control = stats::loess.control(surface = "direct")
)
result <- fit(
Loess(
fraction = 0.5, iterations = 0L, surface_mode = "direct",
boundary_policy = "noboundary", parallel = FALSE
),
x, y
)
max_difference <- max(abs(result$y - reference$fitted))
cat("Within 1e-10:", max_difference < 1e-10, "\n")
#> Within 1e-10: TRUE
stopifnot(max_difference < 1e-10)For robust fitting, R uses family = "symmetric". Match
its iteration count and uncentered median absolute residual
("mar") scale. The zero-weight fallback is set explicitly
for degenerate neighborhoods; this example does not exercise that
fallback:
reference_robust <- stats::loess(
y ~ x, span = 0.5, degree = 1, family = "symmetric",
control = stats::loess.control(surface = "direct", iterations = 3)
)
result_robust <- fit(
Loess(
fraction = 0.5, iterations = 3L, surface_mode = "direct",
zero_weight_fallback = "return_original",
boundary_policy = "noboundary",
scaling_method = "mar", parallel = FALSE
),
x, y
)
robust_difference <- max(abs(result_robust$y - reference_robust$fitted))
cat("Within 1e-10:", robust_difference < 1e-10, "\n")
#> Within 1e-10: TRUE
stopifnot(robust_difference < 1e-10)The interpolated surface needs one more setting. Both implementations
expand the interpolation bounding box slightly beyond the data, so the
outermost vertices sit outside the observed range.
stats::loess() fits those vertices at the requested degree,
while Loess() reduces them to a linear fit by default. Set
boundary_degree_fallback = FALSE to match R:
reference_interpolated <- stats::loess(
y ~ x, span = 0.5, degree = 2, family = "gaussian",
control = stats::loess.control(surface = "interpolate")
)
result_interpolated <- fit(
Loess(
fraction = 0.5, iterations = 0L, degree = "quadratic",
surface_mode = "interpolate", boundary_policy = "noboundary",
boundary_degree_fallback = FALSE, parallel = FALSE
),
x, y
)
interpolated_difference <- max(
abs(result_interpolated$y - reference_interpolated$fitted)
)
cat("Within 1e-10:", interpolated_difference < 1e-10, "\n")
#> Within 1e-10: TRUE
stopifnot(interpolated_difference < 1e-10)These checks demonstrate agreement for the stated data and settings,
not a universal tolerance guarantee across all inputs.
stats::loess()$fitted and fit(Loess(), x, y)$y
both follow the input observation order by default;
outputs = "sorted" is available when sorted LOESS output is
preferred.
| Setting | stats::loess() |
Loess() |
|---|---|---|
| Local neighborhood | span = 0.75 |
fraction = 0.67 |
| Polynomial | degree = 2 |
degree = "linear" |
| Robust fitting | family = "gaussian" (off) |
iterations = 3L |
| Boundary handling | No added padding | boundary_policy = "extend" |
| Residual scale for robust fits | Uncentered absolute residual ("mar") |
scaling_method = "mad" |
| Zero-weight fallback | No configurable option | "use_local_mean" |
| Degree at interpolation vertices outside the data range | Requested degree | boundary_degree_fallback = TRUE (linear above degree
1) |
surface_mode = "direct" and R’s
surface = "direct" remove interpolation from the
comparison. R’s default is interpolation, as is the LOESS binding’s.
Both expand the interpolation bounding box by 0.5% beyond the data
range, so the outermost vertices lie outside the data.
stats::loess() fits them at the requested degree;
Loess() reduces them to a linear fit to avoid unstable
extrapolation. Because this package defaults to
degree = "linear", that reduction changes nothing by
default. It applies only when a higher degree is requested: with
degree = "quadratic" on the interpolated surface, fitted
values near the data boundary differ from stats::loess()
unless boundary_degree_fallback = FALSE is set, as shown
above.
The alternative settings above are for reference matching; the
package retains its own defaults for ordinary use. See
vignette("boundary") and vignette("scaling")
for their effects.
Compared with stats::loess(), rfastloess
provides:
| Feature | rfastloess |
stats::loess() |
|---|---|---|
| Polynomial degree | 0-4 | 0-2 |
| Kernel functions | 7 options | Tricube only |
| Robustness weighting | 3 methods | Bisquare only |
| Scale estimation | MAD, MAR, mean | MAR for robust fits |
| Distance metrics | 6 options | Euclidean, optionally normalized |
| Boundary padding | 4 policies | None |
| Zero-weight fallback | 3 options | Not configurable |
| Automatic convergence | Yes | No |
| Built-in confidence / prediction interval bounds | Yes | SE available; bounds require calculation |
Cross-validation for fraction |
K-fold, LOOCV | No automatic span selection |
| Streaming / online modes | Yes | No |
| Parallel execution | Yes | No built-in parallel fitting |
Both support multivariate predictors, per-observation weights,
prediction, and prediction standard errors. See
vignette("concepts") for an overview of the additional
options, or the Benchmarks article for
performance comparisons.