--- title: "Quick-start for 'loclm'" author: "Jonathan Rougier" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Quick-start for 'loclm'} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 5, fig.asp = 0.65, fig.align = "center" ) ``` `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 ```{r setup} library(loclm) show(packageVersion("loclm")) show(date()) ``` Here I'm just setting up a nicer plotting frame. ```{r setup1} 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. ```{r banana function} #### 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`. ```{r make_dataset} ## 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. ```{r fit_default} ## 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. ```{r LOO0} ## 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. ```{r LOO1} ## 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)) spanhat <- span_vals[which.min(rmse)] show(c(span = spanhat)) ``` Try with the optimal span. ```{r LOO2} 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. ```{r 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)) 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. ```{R wrap-up} ## reset the session par(oldpar) ```