Finding your way around geosmooth

geosmooth estimates a smooth response, or a probability mass, from data given as a matrix of coordinates or as a weighted graph. This guide is organized by task. The table below names the function to start with for each task, the next section walks through a first fit, and the catalog that follows lists every exported function with a link to its help page.

Many of the exported functions build operators or generate synthetic data. You do not need any of them to fit your first model.

Choose a starting point

Your task Start with What to know before choosing
Regress a response on coordinates fit.lps() Local polynomial smoothing; selects neighborhood size, degree, and kernel by cross-validation.
Average predictions from overlapping local models fit.malps() Model-averaged local polynomial smoothing; supports coordinate and graph-geodesic neighborhoods.
Couple local polynomial predictions across overlaps fit.ps.lps() Prediction-synchronized local polynomial smoothing; experimental R reference implementation.
Fit a locally adaptive trend with a sparse difference penalty fit.lpl.tf() Local polynomial lifting trend filtering; requires optional package genlasso.
Fit a one-dimensional continuous response fit.gaussian.trend.filtering() Order-2 Gaussian trend filtering with squared-error loss, explicit penalties and convergence diagnostics.
Fit a one-dimensional binary or binomial response fit.logistic.trend.filtering() Order-2 logistic trend filtering with a native C++ solver, explicit penalties, optional covariates/offsets, and convergence diagnostics.
Fit adaptive curves along fixed or learned projections fit.projection.trend.filtering() Gaussian or binomial additive projection models; mean-loss penalties and separate joint-fit and search diagnostics.
Add synchronization to a lifting trend filter fit.slpl.tf() Synchronized local polynomial lifting trend filtering; adds a second, quadratic penalty.
Smooth a response already attached to graph vertices fit.metric.graph.lowpass() or fit.graph.trend.filtering() Choose spectral low-pass smoothing or a graph-difference penalty. Edge-weight meanings differ; see below.
Penalize local second derivatives on a point cloud fit.ssrhe.hessian.regression() Hessian-energy regression, following semi-supervised regression using Hessian energy (SSRHE); use lambda.selection = "cv" or "gcv" to select penalties.
Use an absolute-value Hessian penalty fit.ssrhe.hessian.l1.regression() Different regularization from quadratic Hessian energy; has its own solver and tuning controls.
Interpolate graph values while fixing a region boundary perform.harmonic.smoothing() Iterative harmonic relaxation; harmonic.smoother() also records extrema and topology diagnostics.
Estimate probability mass over a fixed support fit.density() Takes nonnegative counts or masses; returns mass over the rows of X.
Estimate where one subject visits that support fit.subject.od() Takes visit indices, including repeats; handles occupation-density fitting and optional visit-level validation.
Build reproducible synthetic examples synthetic.spec() then materialize.synthetic() Combine geometry, sampling, truth, and response noise.

The rows of this table are different estimators, listed in no particular order of predictive performance. Two experimental methods, parallel-transport trend filtering and transported graph Hessians, are described in their own section.

A first regression fit

fit.lps() takes a matrix X with one observation per row and a response y with one value per row. By default it works in the coordinates you supply. It can also fit each local polynomial in a local chart, a coordinate system computed near the point of interest, usually by principal component analysis (PCA). For a first fit, keep the supplied coordinates and the default numerical settings.

library(geosmooth)
set.seed(1)
X <- matrix(seq(0, 1, length.out = 60), ncol = 1)
truth <- sin(2 * pi * X[, 1])
y <- truth + rnorm(nrow(X), sd = 0.08)
fit <- fit.lps(
  X, y, foldid = rep(1:5, length.out = nrow(X)),
  support.grid = c(8L, 12L, 16L), degree.grid = 0:1,
  kernel.grid = "gaussian"
)
fit$selected
#>   support.size degree   kernel bandwidth.multiplier candidate.id chart.dim
#> 4            8      1 gaussian                    1            4         1
#>   cv.rmse.observed
#> 4       0.07990112
Noisy observations along a sine curve, with a fitted solid curve close to the dashed synthetic truth.

The points are noisy observations. The solid blue line is the selected fit; the dashed orange line is known only because these data are synthetic.

Here CV selected 8-point neighborhoods, degree 1, and the Gaussian kernel, with a tuning RMSE of 0.0799 response units. A larger neighborhood averages over more observations, and the degree determines the shape that is fitted within a neighborhood. Because this RMSE was used to choose the settings, it is not an independent estimate of prediction error.

head(fitted(fit))
#> [1] -0.01193899  0.09633293  0.20478722  0.31316459  0.41899355  0.52015223
new.X <- matrix(seq(0.1, 0.9, length.out = 5), ncol = 1)
predict(fit, newdata = new.X)
#> [1]  0.593224184  0.978163565  0.003195166 -0.910822694 -0.559835093

Some terms used throughout the package: cross-validation (CV) chooses tuning parameters by predicting responses that were held out of the fit. A support is the neighborhood used for one local fit, and a grid is a list of candidate values. The support size and the polynomial degree together determine whether a local fit has enough observations. For grouped observations, use lps.grouped.foldid() to keep each group in one fold. Use lps.nested.cv() when you need an outer assessment of the whole tuning procedure.

Prediction and reusable fits

Fitted object How to get predictions Scope
Local polynomial smoother predict(fit) or predict(fit, newdata = Xnew) Existing evaluation points or new coordinate rows.
Model-averaged local polynomial smoother predict(fit) or predict(fit, newdata = Xnew) New coordinates are supported for coordinate-distance fits; graph-geodesic fits currently reject new targets.
Either lifting trend filter predict(fit) Training points only; newdata must be NULL.
Prediction-synchronized, chart-kernel, local-likelihood, graph low-pass, graph trend-filtering, Hessian, or parallel-transport fit fitted(fit) No registered predict() method for these classes in this version. Chart-kernel and local-likelihood fits can receive X.eval when fitting.
Density or occupation-density result fit$rho Probability mass on the evaluation support.
Tracked harmonic result (harmonic.smoother()) fitted(fit) Values on the original graph vertices.
Basic harmonic result (perform.harmonic.smoothing()) fit$harmonic_predictions An unclassed list; retains the basic function’s original contract.

summary(fit) shows the selected settings and the diagnostics stored in the fit, and says so when no convergence information is available. fitted(fit) returns the fitted values as a vector or a matrix, as stored by the fit. residuals(fit) returns the response minus the fitted values, with NA where the response is missing. Residuals exist only at the training points, so a fit that was evaluated at other coordinates has none. They are on the scale of the response, also for binomial fits; they are not Pearson or deviance residuals. Probability mass is a different quantity and is read from fit$rho. See help("model-values", package = "geosmooth") and help("model-summary", package = "geosmooth") for details.

Three further terms: a refit fits a new response while reusing the neighborhoods or the operator of an existing fit. It does not repeat the model selection of the original fit; the help page of each refit method says whether the penalties stay fixed or can be selected again. An operator is the matrix of differences or derivatives that a method penalizes; it is not a fitted response. A spectral basis is a stored set of graph eigenvectors, which allows many responses to be smoothed without recomputing them.

Refit and extract smoother matrices

refit(fit, y = new.y, ...) works for the six kinds of model in the table below. R chooses the method from the class of the fitted object. The arguments named in the last column belong to that method only.

Fitted model Accepted response What is reused and what can be changed
MALPS Vector; omitted or NULL reuses the stored response Supports and averaging weights stay fixed. Optional weights affect local coefficient estimation; robust residual weights are recomputed.
Lifting trend filter Vector required Reuses the residual operator and stored penalty, or accepts a fixed lambda.
Synchronized lifting trend filter Vector required Reuses both operators and stored penalties, or accepts fixed lambda1 and lambda2.
Metric graph low-pass Vector or matrix required Reuses the spectral basis and smoothing parameter; per.column.gcv = TRUE selects a parameter for each response column.
Quadratic Hessian-energy regression Vector or matrix; omitted or NULL reuses the stored response Reuses the operator and fixed penalties; accepts lambda1, lambda2, weights, and ridge. CV/GCV fits dispatch here and retain their selected penalties by default.
Hessian L1 regression One response; omitted or NULL reuses the stored response Reuses the derivative operator; accepts lambda.grid, fixed or CV lambda.selection, and solver controls.

A refit of a metric graph low-pass fit or of a quadratic Hessian fit returns a summary that cannot itself be refitted, so for further responses keep calling refit() on the original fit. The other four kinds return fitted objects that can be refitted again.

smoother.matrix(fit, ...) returns the matrix that maps responses to predictions, for LPS and MALPS fits. For LPS, the fit must have a single candidate configuration; the matrix maps the training responses to the predictions at the evaluation points, and check.tol sets the tolerance of an internal check. For MALPS, the matrix maps training responses to predictions at the training points, given the stored supports and weights; max.n limits the size of the matrix. For a robust MALPS fit, allow.robust = TRUE returns the matrix for the final robust weights held fixed. It does not reflect how those weights depend on the response.

malps.fit <- fit.malps(
  X, y, degree = 1L, support.type = "knn", support.size = 12L
)
new.y <- cos(2 * pi * X[, 1])
malps.refit <- refit(malps.fit, y = new.y)
S <- smoother.matrix(malps.fit)
max(abs(as.vector(S %*% new.y) - predict(malps.refit)))
#> [1] 4.440892e-16

If you are updating code written for version 0.1.0: the six refit.*() functions and the two *.smoother.matrix() functions are no longer exported. Call refit() and smoother.matrix() instead, and in refit calls replace fitted.model = with object = and y.new = with y =. The other arguments and the classes of the results are unchanged. help("refit", package = "geosmooth") and help("smoother.matrix", package = "geosmooth") link to the arguments of each method, and the old help names still work.

Nearest-neighbor probabilities for a binary outcome

fit.neighbor.probability() estimates the probability of a binary outcome at a point from the outcomes of its nearest neighbors. It offers three estimators, selected by method. "knn" is the ordinary proportion among the k nearest people. "case_raw" and "case_corrected" instead extend the neighborhood until it contains k cases; the second corrects the proportion for this stopping rule. So k counts people for "knn" and cases for the other two. Give k directly, or give fold labels in foldid to select k by the Brier score.

The function is designed for data in which a participant can have several records:

Three settings control how neighbors are found. With Euclidean coordinates, search = "auto" uses an exact tree search (ANN with eps = 0), and search = "brute" uses an exhaustive search that serves as a reference. search = "ann" with, for example, eps = 0.1 allows approximate neighbors. The tolerance eps bounds the error in distance, not the error in the estimated probability, and an approximate search can miss participants or tied records, so the diagnostics for ties are complete only with an exact search. Chebyshev, intrinsic simplex, and precomputed distances always use the exhaustive search; asking for ANN or a positive eps with them is an error.

For compositional data normalized so that the largest coordinate is one, metric = "linf.simplex" with normalization = "max" measures distance along the surface of the simplex, using the solver in dgraphs. Paths may cross intermediate faces. Distances are computed for one query point at a time, so neither a matrix of all pairwise distances nor a graph of the samples is needed. With normalization = "none", the rows must already be max-normalized to within sqrt(.Machine$double.eps), and normalization = "sum" is not allowed for this metric. Note that max normalization combined with metric = "euclidean" measures straight-line distance instead, which can give different neighbors. The search structures are reused within one prediction call or cross-validation split and rebuilt on the next call, so a fitted object can be saved and reloaded.

fit <- fit.neighbor.probability(X, y, method = "case_corrected", k = 3,
                                id = record_id, group = participant_id,
                                normalization = "max", metric = "linf.simplex")
predict(fit, newdata = query_X, query.group = query_participant_id,
        type = "diagnostics")

When the neighborhood stops at a number of cases, it depends on the outcomes, so the estimator is not a linear smoother and smoother.matrix() is not available for it. To assess a model whose k was tuned, use a separate set of outer folds, grouped by participant.

Function catalog

The tables in this section list every function exported by geosmooth, each with a one-line description. To open the help page of a function in R, use, for example, help("fit.lps", package = "geosmooth"). The last two tables list related functions that belong to the dgraphs package. They are not exported by geosmooth, and their help pages are in dgraphs.

Shared model operations

Function Purpose
refit() Refit a supported model with a new response using stored geometry and method-specific tuning controls.
smoother.matrix() Extract the conditional linear map for a supported LPS or MALPS fit.

Local regression and prediction synchronization

Function Purpose
fit.neighbor.probability() Fit C++ ordinary kNN, raw nearest-case or corrected nearest-case binary estimators, with deterministic ties and grouped tuning.
fit.lps() Fit and tune local polynomial regression, including supported Gaussian and binary response modes.
fit.malps() Fit and optionally tune a model-averaged collection of local polynomial models.
fit.ps.lps() Fit experimental local polynomial models whose predictions are synchronized on overlaps.
fit.chart.kernel() Fit a chart-kernel field, with optional reference-measure quadrature weights.
fit.local.likelihood() Fit local density or Bernoulli likelihood models; returns a fitted field.
unfold.simplex.chart() Certify and unfold a local one/two-face simplex neighborhood before constructing charts.
fit.masked.graph.field() Fit a graph field from partially observed responses.
support.masked.graph.field() Identify graph components supported by observed responses.
cv.masked.graph.field() Assess masked fields using participant-grouped nested cross-validation.
compare.masked.graph.fields() Compare candidate graph fields with independent workers.
select.masked.graph.field() Assemble nested graph-selection predictions from inner-fold choices.

Model assessment and uncertainty

Function Purpose
assess.logistic.trend.filtering() Test constant binary probability by retuned outcome permutations and measure nested-CV improvement over a training-only constant. Summarize absolute probability deviation and total variation; optionally test them using the same fits, with Holm adjustment. Requires independent unit-weight binary observations without covariates or offsets.
assess.gaussian.trend.filtering() Assess continuous outcomes with retuned squared-error permutation tests, optional absolute-deviation/total-variation tests and nested-CV MSE/MAE improvement. Requires independent unit-weight observations without covariates or offsets, exchangeable under a common-distribution null.
lps.grouped.foldid() Assign whole groups of observations to validation folds.
lps.nested.cv() Assess a tuned local polynomial smoother using outer and inner validation folds.
lps.backend.diagnostics() Inspect the computational backend, chart dimensions, and selected controls.
lps.pointwise.band() Calculate pointwise uncertainty for a supported fixed-configuration Gaussian fit.
malps.gcv() Calculate generalized cross-validation and optional leave-one-out diagnostics for a stored model-averaged fit.
bootstrap.malps() Measure refit variability by bootstrap case weights while holding the selected geometry fixed.

Generalized cross-validation (GCV) estimates prediction error from the effective degrees of freedom of a smoother, without fitting a separate model for each left-out observation. It is a different criterion from cross-validation with folds, and the two need not select the same settings.

The uncertainty functions for LPS and for MALPS rest on different assumptions. lps.pointwise.band() requires a Gaussian fit made with the R backend and the orthogonal polynomial basis, with a single candidate configuration and a fixed chart dimension, and evaluated at the training points. malps.gcv() and bootstrap.malps() treat the selected supports and weights as fixed and do not repeat the model selection. Neither gives simultaneous coverage, and neither accounts for the uncertainty of choosing the settings from the data.

Lifting trend filters

Function Purpose
lpl.tf.operator() Build a local polynomial residual operator.
fit.lpl.tf() Fit a trend filter with an absolute-value penalty on those residuals.
slpl.tf.operator() Build the residual operator together with an overlap synchronization operator.
fit.slpl.tf() Fit the residual penalty plus quadratic synchronization.

Both fitting functions accept an operator that was built beforehand, or build one themselves. For a single fit, call the fitting function directly. Build the operator yourself when you want to fit several responses, examine the neighborhoods, or avoid recomputing an expensive operator. Both fitting functions require the optional package genlasso, and both predict only at the training points.

Graph spectral smoothing and graph trend filtering

Function Purpose
fit.metric.graph.lowpass() Select a graph-spectral smoothing parameter by GCV and fit a response.
metric.graph.lowpass.operator() Construct a graph Laplacian from edge lengths or explicit conductances.
metric.graph.lowpass.basis() Construct and retain a partial or complete graph spectrum for repeated smoothing.
apply.metric.graph.lowpass.path() Apply a supplied basis over a parameter grid; retain the candidate fits.
metric.graph.heat.eta.grid() Choose heat-time candidates using spectral information.
metric.graph.heat.extend.lower() Propose a smaller heat time for an external search, subject to resolution safeguards.
graph.trend.filtering.operator() Construct graph differences or experimental path divided differences.
fit.graph.trend.filtering() Fit an absolute-value graph-difference penalty; requires genlasso.

geosmooth does not construct graphs or compute shortest paths; the dgraphs package does. A graph is passed as an adjacency list, in which element i holds the indices (starting from 1) of the neighbors of vertex i, and a weight list of the same shape that holds a value for each edge.

The meaning of the edge values depends on the method. A length is a distance between two vertices. A conductance is the strength of their connection, and larger values mean a stronger connection. Metric low-pass smoothing expects lengths by default and converts them to conductances; pass weight.type = "conductance" if your values are already conductances. Graph trend filtering with the default recursive operator treats the values as conductances, whereas its path divided-difference operator treats them as lengths.

The heat time eta controls the amount of smoothing in graph low-pass filtering. A basis that contains only part of the spectrum may be unable to represent the filter for small positive heat times. If you build or extend a grid of heat times yourself, consult the spectral diagnostics returned with the basis.

With a complete spectrum, heat smoothing also accepts missing responses, as long as every connected component of the graph has at least one observed response. The observed-data heat guide describes the criterion that is minimized in this case and the optional numerical cross-checks requested with numerical.policy = "verified.fallback". Under that policy, a heat time is not selected automatically when the GCV scores cannot distinguish the candidates, either because the scores are constant by construction or because their differences are smaller than the numerical accuracy. The resolution bands reported there describe numerical accuracy; they are not confidence intervals.

From a constructed graph to a fitted response

This example constructs a graph with dgraphs and smooths a response on it. The 20 locations lie on a line, and the radius used to construct the graph joins only adjacent locations, so the graph is a chain. Vertex i, row i of the coordinates, and entry i of the response must refer to the same observation. The smoother cannot detect a response that is in the wrong order, so keep your identifiers in a table and match the response to the vertices by identifier before fitting.

# Each row, vertex, and response refers to the same location.
locations <- matrix(seq(0, 1, length.out = 20), ncol = 1,
                    dimnames = list(sprintf("site-%02d", 1:20), "position"))
response <- sin(2 * pi * locations[, 1])
graph <- dgraphs::create.rknn.graph(locations, radius = .06,
                                    graph.detail = "minimal")
# dgraphs' new dgraph representation has public accessors. The second branch
# supports the pinned transitional dependency used by this development release.
if (inherits(graph, "dgraph")) {
  adjacency <- dgraphs::graph.adjacency(graph)
  edge.lengths <- dgraphs::graph.lengths(graph)
} else {
  adjacency <- graph$adj_list
  edge.lengths <- graph$weight_list
}
graph.fit <- fit.metric.graph.lowpass(
  adjacency, edge.lengths, response,
  n.eigenpairs = nrow(locations), eigen.solver = "dense",
  conductance.rule = "inverse.length.power", conductance.alpha = 1,
  conductance.epsilon = 1e-8, eta.grid = c(.001, .01, .1, 1)
)
head(data.frame(site = rownames(locations), response,
                fitted = fitted(graph.fit)))
#>            site  response      fitted
#> site-01 site-01 0.0000000 0.006105107
#> site-02 site-02 0.3246995 0.324089090
#> site-03 site-03 0.6142127 0.612949746
#> site-04 site-04 0.8371665 0.835444574
#> site-05 site-05 0.9694003 0.967406378
#> site-06 site-06 0.9965845 0.994534692
graph.fit$gcv[c("eta.optimal", "gcv.optimal", "effective.df")]
#> $eta.optimal
#> [1] 0.001
#> 
#> $gcv.optimal
#> [1] 0.004647749
#> 
#> $effective.df
#> [1] 19.2978
# Reuse the fitted object's spectral basis, with the selected smoothing fixed.
second.fit <- refit(graph.fit, y = cos(2 * pi * locations[, 1]))
head(fitted(second.fit))
#> [1]  0.99896194  0.94388145  0.78751744  0.54582318  0.24498057 -0.08240949

With conductance.alpha = 1, an edge of length 2 receives the conductance 1 / (2 + 1e-8), about 0.5, so longer edges connect their endpoints less strongly. The conversion from lengths to conductances affects the result of the smoothing. The example computes all eigenvectors of the graph Laplacian, so nothing is lost by truncating the spectrum. For a large graph you would keep only some of them; help("metric.graph.lowpass.basis") describes the diagnostics to consult in that case. The second response is smoothed with the same eigenvectors and the heat time already selected. The GCV score that is printed was used for tuning and is not an estimate of held-out prediction error.

Where the values are used What they mean Consequence
Coordinate neighborhoods Distances in the supplied coordinate units Rescaling coordinates changes supports.
Graph-geodesic neighborhoods Sums of edge lengths along paths Unreachable vertices have infinite distance; support rules must handle connectivity.
Metric low-pass, weight.type = "length" (default) Positive lengths transformed to conductances Reciprocal edges and matching lengths are required; zero lengths are rejected.
Metric low-pass, weight.type = "conductance" Supplied nonnegative connection strengths Reciprocal weights must agree; zero-weight edges are removed and positive weights are used unchanged.
Recursive graph differences Conductance-like weights Greater weight penalizes differences more strongly.
Path divided differences Edge lengths The operator uses metric spacing, not conductance.

For example, metric.graph.lowpass.operator(list(2L, integer()), list(1, numeric())) is rejected, because the edge from vertex 1 to vertex 2 is not also listed from vertex 2 to vertex 1. If your input has directed edges or edges of length zero, decide what they should mean before you construct an undirected graph. A conductance of zero means that there is no connection. A length of zero is not allowed, because it cannot be converted to a conductance.

One-dimensional Gaussian and logistic trend filtering

Function Purpose
fit.logistic.trend.filtering() Fit a univariate piecewise-quadratic logit curve using the native C++ backend.
fit.gaussian.trend.filtering() Fit a univariate piecewise-quadratic mean curve with squared-error loss using the same order-2 geometry and native C++ solvers.
fit.projection.trend.filtering() Fit centered order-2 curves on fixed directions or learn directions by projection pursuit; supports Gaussian and binomial losses.
cv.projection.trend.filtering() Select component counts and mean-loss penalties using supplied, optionally grouped folds; repeat all direction learning within each training split.

For a continuous outcome, fit the model over a list of penalties that you supply, and then assess both the association with the predictor and the held-out prediction:

x.continuous <- seq(0, 1, length.out = 40)
y.continuous <- sin(2*pi*x.continuous) + 0.2*cos(seq_along(x.continuous))
gaussian.fit <- fit.gaussian.trend.filtering(
  x.continuous, y.continuous, lambda = c(Inf, 0.01), backend = "banded")
# Nine permutations keep this API example short; use more for inference.
gaussian.assessment <- assess.gaussian.trend.filtering(
  gaussian.fit, permutations = 9, inner.folds = 3, outer.folds = 3, seed = 42,
  additional.tests = c("absolute.deviation", "total.variation"))
gaussian.assessment$tests
#>                 test  statistic   status requested completed failed exceedances
#> 1      squared.error 19.3392875 complete         9         9      0           0
#> 2 absolute.deviation  0.6093144 complete         9         9      0           0
#> 3    total.variation  3.6858303 complete         9         9      0           0
#>   comparison.tolerance resolution p.value p.value.lower p.value.upper
#> 1         1.933929e-09        0.1     0.1           0.1           0.1
#> 2         1.000000e-10        0.1     0.1           0.1           0.1
#> 3         3.685830e-10        0.1     0.1           0.1           0.1
#>   p.value.holm
#> 1          0.3
#> 2          0.3
#> 3          0.3
gaussian.assessment$prediction$scores
#>             model        mse       mae
#> 1   Constant mean 0.55796292 0.6414749
#> 2 Trend filtering 0.03138962 0.1546267

The test statistic is the reduction in the residual sum of squares relative to the sample mean. The tuning is repeated for every permutation. The test assumes that the outcomes are exchangeable under the null hypothesis; equal means with variances that depend on the predictor are not enough. The nested cross-validation is a separate assessment. It compares the smoother with the mean of the outer training data, using MSE and MAE on the same held-out rows. The curve summaries are in the units of the response. Note that lambda = Inf gives a quadratic curve, whereas the baseline of the assessment is a constant. The example shows how the functions are used; with nine permutations it says nothing about the calibration or the power of the test.

The logistic trend-filtering vignette walks through synthetic probabilities, trial weights and held-out Pima predictions.

fit.logistic.trend.filtering() accepts binary observations, or proportions together with the numbers of trials as weights. You must supply the penalties in lambda. They refer to the predictor after it has been rescaled to the range of the training data, not to its original units. Observations with exactly equal predictor values share one value of the curve but keep their own covariates and offsets. To select a penalty by cross-validation, supply foldid; give repeated observations of one subject the same fold label.

Always inspect fit$diagnostics. A fit that reached the iteration limit, detected separation, or failed numerically is not a successful fit. predict() applies the transformations learned from the training data and holds the curve constant outside the training range. No ridge penalty is added unless you request one.

Two solvers are available. backend = "dense" is the reference solver for small problems. The banded solver does not form dense matrices over all knots, yet its stopping checks still examine every knot coefficient. backend = "auto" chooses the banded solver when there are more than 256 distinct predictor values or when the dense solver would need too much memory. Both solvers check an estimate of their memory use before allocating.

Cross-validation for Gaussian, logistic, local polynomial lifting, and graph trend filtering uses cv.penalty.scale = "training_mass" by default. A fold is fitted to less data than the full fit, so its penalty is multiplied by the ratio of the training weight to the full weight. This keeps the penalty in the same proportion to the mean loss. Set cv.penalty.scale = "none" to use the same numerical penalty in every fold, as earlier versions did. The one-dimensional fits scale explicit ridge penalties in the same way and record the settings used in each fold. The assessment functions apply the same rule to their outer and inner training sets. Fits saved by earlier versions keep the earlier convention, and fits with a fixed penalty are not affected.

Hessian-energy regression

Function Purpose
ssrhe.hessian.operator() Estimate local derivative operators and Hessian-energy matrices from point-cloud neighborhoods.
fit.ssrhe.hessian.regression() Fit quadratic Hessian-energy regression using fixed penalties, label CV, or GCV; optionally select supports.
fit.ssrhe.hessian.l1.regression() Fit an absolute-value derivative penalty with fixed or CV-selected regularization.
ssrhe.support.grid() Build candidate adaptive-radius support profiles for tuning.

The Hessian of a function is its matrix of second derivatives. These methods estimate it locally from the points near each observation and penalize its size. A quadratic penalty reduces curvature everywhere by a moderate amount, whereas an absolute-value penalty can set some second derivatives exactly to zero. The two penalties therefore give different estimates.

Cross-validation works when some points have no observed response (semi-supervised regression). GCV requires a single response vector with no missing values and strictly positive observation weights. The trace needed for GCV can be computed exactly or estimated with random probes; the exact computation costs more, and the estimate depends on a random seed. For the absolute-value penalty, solver = "admm" uses the built-in alternating direction method of multipliers, and the genlasso solver requires that optional package.

To select the penalties of the quadratic method, call the same fitting function with lambda.selection = "cv" or "gcv", and put the settings of the selection in cv.control or gcv.control. A single penalty value and a grid of candidates cannot be given together.

hessian.fit <- fit.ssrhe.hessian.regression(
  X, y, k = 12L, tangent.dim = 1L,
  lambda.selection = "cv", lambda1.grid = c(0.01, 0.1),
  cv.control = list(foldid = rep(1:5, length.out = nrow(X)))
)
hessian.fit$lambda
#> $lambda1
#> [1] 0.01
#> 
#> $lambda2
#> [1] 0
#> 
#> $ridge
#> [1] 0

The separate .cv() and .gcv() fitting functions of version 0.1.0 have been removed. Their old help names now open the help page of fit.ssrhe.hessian.regression(), which explains how to update a call. Calls with fixed lambda1 and optional lambda2 work as before.

Harmonic interpolation

Function Purpose
perform.harmonic.smoothing() Relax interior values while holding the region boundary fixed; return convergence diagnostics.
harmonic.smoother() Run relaxation while recording extrema, prediction history, and topology stability.
get.region.boundary() Find region vertices with a neighbor outside the region.

Harmonic smoothing replaces the values inside a region of the graph by values that are, at each vertex, the weighted average of the neighbors’ values, while the values on the boundary of the region stay fixed. A boundary vertex is a vertex of the region that has a neighbor outside it. A vertex of degree one is therefore not a boundary vertex when the region is the whole graph.

The two functions behave differently when the region has no boundary, so they are not interchangeable. perform.harmonic.smoothing() returns its input unchanged with status = "no_boundary", whereas harmonic.smoother() still runs the iteration. Both report numerical convergence only when the change between iterations and the harmonic residual are both small.

harmonic.smoother() returns a harmonic_smoother object with print(), summary(), and plot() methods. It also reports whether the local extrema of the values stopped changing: stability_detected says whether a stable stretch of iterations was found, and stable_iteration identifies it, or is NA if none was found. This is a separate diagnostic from convergence. For example, on a path whose values alternate, recording every second iteration can look stable while the values are still oscillating. Check converged and max_residual, and do not take stable extrema as a sign that the amount of smoothing is right.

Probability mass and subject occupation

Function Purpose
fit.density() Fit empirical or graph-random-walk mass estimates from nonnegative weights on a support.
normalize.density() Convert a numeric field or supported fitted object to probability mass, with normalization diagnostics.
fit.subject.od() Convert repeated visit indices into counts and fit a subject-occupation distribution.
support <- matrix(seq(0, 1, length.out = 6), ncol = 1)
occupation <- fit.subject.od(
  support, subject.index = c(1L, 3L, 3L, 6L), method = "empirical"
)
occupation$rho
#> [1] 0.25 0.00 0.50 0.00 0.00 0.25
sum(occupation$rho)
#> [1] 1

In this example the third support point is visited twice and the first and sixth once each. rho is a vector of probability masses, one for each support point, that sums to one. It is not a density per unit volume. normalize.density() converts a fitted field to such a vector; it does not fit anything. If the field has negative values, they are set to zero before normalizing, and the result reports how much was changed.

fit.subject.od() offers more methods than fit.density(), because it can also fit one of the regression smoothers and normalize the result. Two kinds of cross-validation occur here and should not be confused. Visit-level cross-validation holds out visits and takes its folds from visit.foldid. The cross-validation of a regression smoother holds out rows of the support and takes its folds from foldid, or from the argument of that name in the chosen method.

Experimental transport methods

Function Purpose
pttf.geometry() Build local tangent frames and their transport across a graph.
pttf.operator() Construct parallel-transport trend-filtering differences from that geometry.
pttf.operator.filter.rows() Select operator rows while maintaining provenance and related operator data.
fit.pttf.trend.filtering() Fit a parallel-transport trend filter with an absolute-value or quadratic penalty.
transported.graph.hessian.operator() Build a separate experimental graph Hessian diagnostic with several direction-matching rules.

Derivatives estimated at neighboring points are expressed in different local coordinate frames. Parallel transport aligns the frames so that the derivatives can be compared. The functions in this table are experimental, and they return detailed diagnostics about the frames, the operator, and the solver. transported.graph.hessian.operator() serves a different purpose from pttf.operator(): it builds a diagnostic operator and does not lead to a fitted response.

Synthetic datasets: specifications, responses, and reproducibility

Function Purpose
synthetic.spec() Combine geometry, sampling, truth, and response descriptors without drawing data.
materialize.synthetic() Draw a dataset from a specification with an explicit seed and random-number policy.
materialize.synthetic.instance() Reproduce a registered frozen instance and optionally validate its checksum.
synthetic.registry.ids() List maintained recipe identifiers.
synthetic.registry.spec() Look up a recipe specification; some recipe families allow parameter overrides.
synthetic.registry.seed() Reproduce legacy SSRHE recipe seeds; this is not a seed generator for every registry family.
validate.synthetic.dataset() Check the structure and consistency of a materialized dataset.
compare.synthetic.dataset() Compare two datasets with explicit numerical tolerances.
synthetic.dataset.checksum() Compute a content checksum for reproducibility checks.
synthetic.sampling.stratified() Describe allocation between the supported synthetic strata.
synthetic.truth.named() Select a named truth specification with optional parameters.
synthetic.truth.polynomial() Specify polynomial truth in the chosen evaluation coordinates.
synthetic.truth.gaussian.mixture() Specify Gaussian-mixture truth using centers, scales, and weights.
synthetic.truth.occupation.mixture() Specify occupation-mixture truth with covariance and probability controls.
synthetic.truth.logit() Specify a logit-based truth with prevalence and clipping controls.
synthetic.response.gaussian() Specify Gaussian response noise with a supplied standard deviation.
synthetic.response.heteroskedastic.gaussian() Specify Gaussian noise whose scale depends on truth.
synthetic.response.clustered.gaussian() Specify within-cluster correlated Gaussian noise.
synthetic.response.laplace.outlier() Specify Laplace noise and an outlier component.
synthetic.response.bernoulli() Specify binary response draws and optional positive-count requirements.

The functions whose names begin with synthetic. only describe a data set; no random numbers are drawn until you call materialize.synthetic(). The result is a synthetic_dataset object with print(), plot(), and as.data.frame() methods. vignette("synthetic-datasets", package = "geosmooth") gives complete examples. For new work, use the random-number policy "named.stream.v1". The other seed and algorithm settings exist so that data sets generated by earlier versions can be reproduced exactly; leave them at their defaults unless that is your aim.

Geometry constructors and helpers supplied by dgraphs

The following functions were exported by geosmooth 0.1.0 and are now provided by dgraphs. In existing code, replace geosmooth:: with dgraphs::. Their help pages are in dgraphs; see, for example, help("synthetic.circle", package = "dgraphs").

Function Purpose
dgraphs::synthetic.circle() Describe a circle or circular arc and its embedding.
dgraphs::synthetic.helix() Describe a helical curve.
dgraphs::synthetic.trefoil() Describe a trefoil curve.
dgraphs::synthetic.torus.patch() Describe a patch of a torus.
dgraphs::synthetic.sphere.cap() Describe a spherical cap.
dgraphs::synthetic.quadform() Describe a flat or quadratic geometry with intrinsic and ambient dimensions.
dgraphs::synthetic.simplex() Describe a simplex with a given number of parts.
dgraphs::synthetic.stratified() Combine strata and optional junctions.
dgraphs::synthetic.stratum.point() Describe a point stratum.
dgraphs::synthetic.stratum.segment() Describe a line-segment stratum.
dgraphs::synthetic.stratum.rectangle() Describe a rectangular stratum.
dgraphs::synthetic.point.line.junction() Describe a point-and-line junction geometry.
dgraphs::embed.synthetic.geometry() Map latent coordinates to the ambient space.
dgraphs::edge.lengths.synthetic.geometry() Evaluate geometry-aware lengths between latent endpoints.
dgraphs::quadform.gradient() Evaluate the gradient of a quadratic geometry.
dgraphs::quadform.metric() Evaluate its induced metric.

Sampling constructors supplied by dgraphs

Function Purpose
dgraphs::synthetic.sampling.uniform.interval() Describe uniform draws on an interval.
dgraphs::synthetic.sampling.uniform.box() Describe uniform draws in a box.
dgraphs::synthetic.sampling.uniform.rectangle() Describe uniform draws in a rectangle with its specified draw algorithm.
dgraphs::synthetic.sampling.uniform.disk() Describe uniform draws in a disk.
dgraphs::synthetic.sampling.grid.interval() Describe an interval grid with an endpoint convention.
dgraphs::synthetic.sampling.gapped.uniform() Describe sampling across separated intervals.
dgraphs::synthetic.sampling.truncated.normal() Describe bounded Gaussian draws.
dgraphs::synthetic.sampling.clustered() Describe clustered observations around random centers.
dgraphs::synthetic.sampling.dirichlet.zeros() Describe Dirichlet sampling with structural zeros.

Two sampling descriptions of similar regions do not necessarily draw the same random numbers, because each description also fixes the order of the draws and the algorithm.

Finding detailed help

The function links in this guide open the online reference. The same help pages are installed with the package and need no internet connection: use help("fit.lps", package = "geosmooth"), with the name of the function you want. help("geosmooth-package", package = "geosmooth") lists the main functions by task, and browseVignettes("geosmooth") lists the installed guides.

The synthetic datasets guide develops reproducible geometry, sampling, truth, and response specifications in more detail.

help(package = "geosmooth")
help("geosmooth-package", package = "geosmooth")
help("fit.lps", package = "geosmooth")
help("predict.lps", package = "geosmooth")
vignette("function-guide", package = "geosmooth")
vignette("synthetic-datasets", package = "geosmooth")

The help pages give the exact arguments, defaults, return values, and examples. Use this guide to find the right function, and read its help page before changing its more specialized settings.

Tuning and numerical settings

The help page of each main fitting function groups its arguments into those needed for a first fit and those that control model selection, the neighborhoods, the numerical solver, and the diagnostics that are returned. Start by tuning over a small grid of the statistical settings. Tightening numerical tolerances is not a substitute for that.

Method Arguments for folds and selection What is selected, and the main cost
LPS / MALPS foldid, cv.folds, support/degree/kernel grids Held-out response selection; candidate combinations multiply fitting work.
Quadratic Hessian lambda.selection, cv.control, gcv.control Label CV or GCV; exact trace calculation is more expensive than sampled traces.
Hessian L1 fold.id, nfolds, lambda.grid, admm.* CV excludes unconverged solves; each candidate and fold needs a solve.
Graph low-pass eta.grid, n.eigenpairs GCV over heat times; a full spectral basis uses quadratic storage.

The argument names differ among the methods because the selection criteria differ; giving them the same names would not make the criteria equivalent.

smoother.matrix() returns a dense matrix with one row per evaluation point and one column per training point. Stored candidate fits and solver histories also take memory, so request them only when you intend to examine them.

The catalog above covers every function exported by geosmooth 0.2.0. The geometry and sampling functions belong to dgraphs. Methods for generics such as print(), predict(), and summary() are not listed individually; the package index, help(package = "geosmooth"), lists them all.

For intrinsic simplex charts, weighted PCA/PLS and their limits, see the local simplex charts guide.

For worked synthetic comparisons, held-out prediction figures and reproducible PCA/PLS examples, see PCA and PLS charts.