Provides geometric methods for nonparametric regression and
density estimation on data represented as coordinate matrices or weighted
graphs.
Methods include local polynomial smoothing, model-averaged local polynomial
smoothing, local polynomial lifting trend filtering, synchronized local
polynomial lifting trend filtering, order-2 Gaussian and logistic trend filtering,
graph low-pass filtering, and
Hessian-energy regression. Methodological references include Gajer and
Ravel (2025) "Adaptive Geometric Regression for High-Dimensional Structured
Data"
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.

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.
vignette("function-guide", package = "geosmooth").help("geosmooth-package", package = "geosmooth") is a shorter overview
with links to the main functions.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.
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.
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.
Local polynomial smoothing (LPS), fit.lps(). Fits a weighted
polynomial to the observations near each point at which a prediction is
wanted. This is the standard local-regression method and a good first
choice.
Model-averaged local polynomial smoothing (MALPS), fit.malps(). Fits
a local polynomial around each of a set of anchor points and predicts at a
point by averaging the local fits that cover it.
Local polynomial lifting trend filtering (LPL-TF), fit.lpl.tf() and
lpl.tf.operator(). Penalizes the absolute values of the residuals that
remain when each fitted value is predicted from its neighbors by a local
polynomial. As in trend filtering, this L1 penalty lets the fit adapt to
the local smoothness of the response.
Synchronized LPL-TF (S-LPL-TF, also called SLPLiFT), fit.slpl.tf()
and slpl.tf.operator(). Adds to LPL-TF a quadratic penalty that makes the
local predictions agree where their neighborhoods overlap.
Graph low-pass filtering, fit.metric.graph.lowpass(). Smooths a
response defined on the vertices of an undirected graph whose edges carry
positive lengths or nonnegative conductances. The
graph workflow
explains how responses are matched to vertices, how lengths are converted
to conductances, and how to reuse the computed eigenvectors for further
responses. The
observed-data heat guide
covers missing responses and the numerical checks that are available.
One-dimensional Gaussian trend filtering,
fit.gaussian.trend.filtering(). Fits a piecewise-quadratic curve (trend
filtering of order 2) to a continuous outcome. Its companion,
assess.gaussian.trend.filtering(), tests by permutation whether the
outcome varies with the predictor, repeating the tuning for every
permutation, and compares held-out prediction error with that of a constant
mean. The permutation test assumes that the outcomes are exchangeable under
the null hypothesis; a constant mean with unequal variances is not enough.
One-dimensional logistic trend filtering,
fit.logistic.trend.filtering(). Fits the probability of a binary outcome
as a function of a continuous predictor.
assess.logistic.trend.filtering()
tests by permutation whether the probability is constant and measures the
gain in held-out prediction. The
logistic trend-filtering guide
works through simulated data and the Pima diabetes data.
Hessian-energy regression, fit.ssrhe.hessian.regression() and
fit.ssrhe.hessian.l1.regression(). Penalizes estimated second
derivatives of the fit on a point cloud, following the semi-supervised
regression with Hessian energy (SSRHE) of Kim, Steinke, and Hein (2009).
The first function uses a quadratic penalty and the second an L1 penalty.
Neighborhoods can have a fixed number of points, be supplied by you, or
have radii that adapt to a graph.
The examples below continue from the Quick Start and use its X, y, and
foldid.
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.fit <- fit.malps(
X = X,
y = y,
degree = 1L,
support.type = "knn",
support.size = 12L,
kernel = "tricube",
support.selection = "fixed",
coordinate.method = "coordinates"
)
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"
)
}
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.
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.
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.
To run the main tests and a quick package check from a source checkout:
make test
make check-fast