Quick-start for ‘loclm’

Jonathan Rougier

loclm() implements local linear regression, using high-level functions. It can handle both numerical and non-numerical variables. Non-numerical variables are converted to factors.

Top stuff

library(loclm)
show(packageVersion("loclm"))
#> [1] '1.0.0'
show(date())
#> [1] "Thu Jul 30 07:00:27 2026"

Here I’m just setting up a nicer plotting frame.

oldpar <- par(mgp = c(2.5, 0.7, 0), mar = c(4, 4, 2, 0.5), family = "Hershey",
  cex.main = 0.8, cex.axis = 0.8, cex.lab = 0.8, las = 1)
op <- par(no.readonly = TRUE)

Create a dataset

Let’s use the multivariate Banana function variant B from

S. Kok and C. Sandrock, 2009, Locating and Characterizing the Stationary Points of the Extended Rosenbrock Function, Evolutionary Computing, 17(3), pages 437-453.

As the paper explains, variant B is pathological, especially in higher dimensions.

#### d-dimensional Banana function

## (d >= 2 is implied by x)

banana <- function(x) {
  stopifnot(is.vector(x, mode = "numeric"))
  d <- length(x)
  if (d == 1) {
    x <- c(x, 1)
    d <- 2L
  }
  robj <- 0
  for (j in seq_len(d - 1)) {
    robj <- robj + 100 * (x[j]^2 - x[j+1])^2 + (x[j] - 1)^2
  }
  robj
}

Here is a dataset of 101 runs from a 5D banana function, plus a little noise. Feel free to play with d and sigma.

## make a dataset

set.seed(1234) # for reproducibility, feel free to change

d <- 3
sigma <- 1
X <- matrix(runif(d * 101), ncol = d)
y <- apply(X, 1, banana) + rnorm(nrow(X), sd = sigma)

## here's where we'll predict

newX <- matrix(runif(d * 23), ncol = d)
newy <- apply(newX, 1, banana) # leave off the noise

All numeric inputs

First, fit the model with the default span.

## default span

fit <- loclm(X, y)
pp <- predict(fit, newX)
cex <- 0.6
col <- c("darkblue", "forestgreen")
par(op)
matplot(newX[, 2], cbind(newy, pp), pch = c(1, 2), cex = cex,
  col = col,
  main = "Default span")

Not great.

LOO cross-validation to set the span

Now use Leave-One-Out (LOO) cross-validation to set the span. We need a general-purpose LOO function.

## LOO cross-validation

LOO <- function(X, y, reg, ...) {
  n <- length(y)
  yhat <- vapply(seq.int(n), \(i) {
    fit <- reg(X[-i, , drop=FALSE], y[-i], ...)
    predict(fit, X[i, , drop=FALSE])
  }, FUN.VALUE = 0)
  list(yhat = yhat, res = y - yhat)
}

Now call this with loclm as the regression method, and span as the parameter.

## try different spans

span_vals <- c(seq(0.1, 0.2, 0.01), 0.75)
rmse <- vapply(span_vals, \(span) {
    loo <- LOO(X, y, reg = loclm, span = span)
    sqrt(mean(loo$res^2))
  }, FUN.VALUE = 0)
show(cbind(span = span_vals, rmse = rmse))
#>       span      rmse
#>  [1,] 0.10  8.302191
#>  [2,] 0.11  8.136132
#>  [3,] 0.12  8.008744
#>  [4,] 0.13  7.910766
#>  [5,] 0.14  7.944608
#>  [6,] 0.15  7.262508
#>  [7,] 0.16  7.284905
#>  [8,] 0.17  7.398039
#>  [9,] 0.18  7.391820
#> [10,] 0.19  7.454959
#> [11,] 0.20  7.617551
#> [12,] 0.75 14.358125
spanhat <- span_vals[which.min(rmse)]
show(c(span = spanhat))
#> span 
#> 0.15

Try with the optimal span.

fit <- loclm(X, y, span = spanhat)
pp <- predict(fit, newX)
par(op)
matplot(newX[, 2], cbind(newy, pp), pch = c(1, 2), cex = cex,
  col = col,
  main = sprintf("span = %s (optimal)", format(spanhat)))

That’s a big improvement.

Factor inputs

Let’s make the first input into a factor.

## first input is a factor

breaks <- seq(0, 1, 0.2)
X <- data.frame(X)
X[[1]] <- factor(LETTERS[cut(X[[1]], breaks)])
show(head(X, 10))
#>    X1         X2        X3
#> 1   A 0.56507611 0.3174938
#> 2   D 0.28025778 0.7678555
#> 3   D 0.20419632 0.5263085
#> 4   D 0.13373890 0.7323019
#> 5   E 0.32568192 0.3076657
#> 6   D 0.15506197 0.4041733
#> 7   A 0.12996214 0.2044024
#> 8   B 0.43553106 0.9856331
#> 9   D 0.03864265 0.5663108
#> 10  C 0.71330156 0.2803751

newX <- data.frame(newX)
newX[[1]] <- factor(LETTERS[cut(newX[[1]], breaks)])

## OK, here is the fit

fit <- loclm(X, y, span = spanhat)
pp <- predict(fit, newX)
par(op)
matplot(newX[, 2], cbind(newy, pp), pch = c(1, 2), cex = cex,
  col = col,
  main = sprintf("First input is a factor"))

Not a disaster.

## reset the session

par(oldpar)