geosmooth

geosmooth is an R package for nonparametric regression and density estimation when the data have geometric structure. The data can be given as a matrix of coordinates, one row per observation, or as a weighted graph whose vertices are the observations. The package estimates a smooth response, or a probability mass, over those points. Its methods include local polynomial smoothing, model-averaged local polynomial smoothing, two forms of local polynomial lifting trend filtering, one-dimensional Gaussian and logistic trend filtering, graph low-pass filtering, and Hessian-energy regression.

Noisy observations along a sine curve; the solid blue fitted response follows the dashed orange synthetic truth.

In the figure, the points are noisy observations, the solid line is a local polynomial fit whose settings were selected by cross-validation, and the dashed line is the true curve, which is known here because the data are simulated. The first-fit guide gives the code, the selected settings, and an explanation of the tuning score.

Where to start

Installation

Install the released version from CRAN:

install.packages("geosmooth")
help("geosmooth-package", package = "geosmooth")

To install the development version from GitHub, you need the tools for compiling R packages from source:

install.packages("pak")
pak::pkg_install("pgajer/geosmooth")
vignette("function-guide", package = "geosmooth")

Version 0.2.0 requires R >= 4.1.0 and dgraphs >= 0.4.0. The optional GRIP and edge-KK embeddings also require grip >= 0.3.0.

Quick Start

library(geosmooth)

set.seed(1)
x <- seq(0, 1, length.out = 60)
X <- cbind(x = x)
y <- sin(2 * pi * x) + rnorm(length(x), sd = 0.08)
foldid <- rep(1:5, length.out = length(y))

lps.fit <- fit.lps(
    X = X,
    y = y,
    foldid = foldid,
    support.grid = c(8L, 12L, 16L),
    degree.grid = 0:1,
    kernel.grid = "gaussian"
)

head(predict(lps.fit))

fit.lps() fits a local polynomial smoother (LPS). It selects the neighborhood size, the degree of the local polynomial, and the kernel by cross-validation.

Choosing a method

The methods differ in the kind of data they take and in how they penalize roughness. The main ones are listed here; the function guide covers the rest.

Basic examples

The examples below continue from the Quick Start and use its X, y, and foldid.

LPS

lps.fit <- fit.lps(
    X = X,
    y = y,
    foldid = foldid,
    support.grid = c(8L, 12L),
    degree.grid = 0:1,
    kernel.grid = "gaussian"
)

lps.pred <- predict(lps.fit, X)

MALPS

malps.fit <- fit.malps(
    X = X,
    y = y,
    degree = 1L,
    support.type = "knn",
    support.size = 12L,
    kernel = "tricube",
    support.selection = "fixed",
    coordinate.method = "coordinates"
)

LPL-TF and S-LPL-TF

The operators can be built once and reused for several responses.

lpl.op <- lpl.tf.operator(
    X = X,
    degree = 1L,
    support.type = "knn",
    support.size = 12L,
    kernel = "gaussian",
    coordinate.method = "coordinates"
)

slpl.op <- slpl.tf.operator(
    X = X,
    degree = 1L,
    support.type = "knn",
    support.size = 12L,
    kernel = "gaussian",
    coordinate.method = "coordinates"
)

Fitting these two models requires the optional package genlasso.

if (requireNamespace("genlasso", quietly = TRUE)) {
    lpl.fit <- fit.lpl.tf(
        y = y,
        operator = lpl.op,
        lambda = 0.1,
        lambda.selection = "fixed"
    )

    slpl.fit <- fit.slpl.tf(
        y = y,
        operator = slpl.op,
        lambda1 = 0.1,
        lambda2 = 0.01,
        lambda.selection = "fixed"
    )
}

Hessian-energy regression

grid <- expand.grid(x = seq(0, 1, length.out = 5),
                    y = seq(0, 1, length.out = 5))
X2 <- as.matrix(grid)
y2 <- sin(2 * pi * X2[, 1]) + 0.25 * X2[, 2]

ssrhe.fit <- fit.ssrhe.hessian.regression(
    X = X2,
    y = y2,
    k = 12L,
    tangent.dim = 2L,
    lambda1 = 0.05,
    return.local.diagnostics = FALSE
)

# The same function can select the penalties by cross-validation
# (or by "gcv" when every response is observed).
ssrhe.cv <- fit.ssrhe.hessian.regression(
    X = X2,
    y = y2,
    k = 12L,
    tangent.dim = 2L,
    lambda.selection = "cv",
    lambda1.grid = c(0.01, 0.05, 0.2),
    cv.control = list(cv.folds = 5L)
)

The code of these examples is also available in inst/examples/geosmooth_quickstart.R.

Graphs and the dgraphs package

geosmooth does not construct graphs. Methods that work directly on coordinates need no graph: LPS, MALPS, and both lifting trend filters with coordinate neighborhoods, and Hessian-energy regression with neighborhoods of fixed size or neighborhoods that you supply. Methods that use distances along a graph take a graph object from the dgraphs package, which also computes the shortest paths. These are MALPS and the lifting trend filters with graph-geodesic neighborhoods, and Hessian-energy regression with adaptive-radius neighborhoods.

Compiled code

The computationally demanding parts of the package are written in C++: LPS cross-validation and prediction, the construction of local PCA coordinate charts, graph low-pass filtering, and the Hessian-energy operators.

Development

To run the main tests and a quick package check from a source checkout:

make test
make check-fast