Alternative Software

Overview

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.

Comparing fitted values

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.

Why defaults differ

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.

What this package adds

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.