本文展示了怎样使用LOO-CV与GCV(舍一交叉验证与广义交叉验证)交叉验证方法去寻找LOESS(局部加权多项式回归)中的最优span并展示出最优span下真实曲线与回归曲线的差异。

LOO-CV and GCV

LOO-CV prediction error can be computed based on the original model (the one that uses all the n samples):

\[\frac{1}{n}\sum_{i=1}^n (y_i-\hat{y_i}^{[-i]})^2=\frac{1}{n}\sum_{i=1}^n(\frac{y_i-\hat{y_i}}{1-S_{ii}})^2\] While \(S_{ii}\) is the (i, i)-th entry of the smoothing matrix S

GCV prediction error is very similar to LOO-CV

\[\frac{1}{n}\sum_{i=1}^n(\frac{y_i-\hat{y_i}}{1-trace(S)})^2\]

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.4     v dplyr   1.0.7
## v tidyr   1.1.3     v stringr 1.4.0
## v readr   2.0.1     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
lo.lev <- function(x1, sp){
  # x1: n-by-1 feature vector
  # sp: a numerical value for "span"
  
  n = length(x1);
  lev = rep(0, n)
  
  
  for(i in 1:n){
       y = rep(0, n); y[i]=1;
       si = loess(y ~ x1, sp=sp)$fitted
       si=si[i]
       lev[i]= si;
       }
  
  return(lev)
}

onestep_CV <- function(x1, y1, sp){
  
  CVmode=loess(y1 ~ x1, sp=sp)
  lolev=lo.lev(x1,sp)
  m=mean(lolev)
  cv=mean(((y1-CVmode$fitted)/(1-lolev))^2)
  gcv=mean(((y1-CVmode$fitted)/(1-m))^2)
  
  return(list(cv = cv, gcv = gcv))
}

myCV <- function(x1, y1, span){
  # x1: feature vector of length n
  # y1: response vector of length n
  # span: a sequence of values for "span"
  
  m = length(span)
  cv = rep(0, m)
  gcv = rep(0, m)
  
  for(i in 1:m){
    tmp = onestep_CV(x1, y1, span[i])
    cv[i] = tmp$cv
    gcv[i] = tmp$gcv
  }
  return(list(cv = cv, gcv = gcv))
}

The three functions above are the key function to obtain the estimation error for different spans range from 0.2 to 0.9.

lo.lev function is to calculate the (i, i)-th entry of the smoothing matrix of LOESS, onestep_CV is to find the estimation error given by LOO-CV and GCV with a fixed span. The main function myCV is to give a loop to find the error under different spans.

And then generating data

mydata=read_csv("https://raw.githubusercontent.com/Zhenz2020/Zhenz2020.github.io/main/Data_LOOCV.csv")
## Rows: 30 Columns: 2
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## dbl (2): x, y
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
span_value=seq(from=0.2,by=0.05,length=15)
cv.out = myCV(mydata$x, mydata$y, span_value)
myout = data.frame(CV = cv.out$cv, 
                   GCV = cv.out$gcv, 
                   span = span_value)
myout
##           CV      GCV span
## 1  12.415911 2.110162 0.20
## 2   2.241473 1.489206 0.25
## 3   1.502980 1.190110 0.30
## 4   1.259175 1.174423 0.35
## 5   1.190380 1.102540 0.40
## 6   1.156812 1.062503 0.45
## 7   1.124306 1.040422 0.50
## 8   1.179664 1.118841 0.55
## 9   1.179464 1.119269 0.60
## 10  1.250914 1.180585 0.65
## 11  1.553562 1.519091 0.70
## 12  1.636175 1.627429 0.75
## 13  1.764534 1.744549 0.80
## 14  1.976094 1.925696 0.85
## 15  2.035108 1.979820 0.90
myout$span[myout$GCV == min(myout$GCV)]
## [1] 0.5
myout$span[myout$CV == min(myout$CV)]
## [1] 0.5

As we can see above, the best spans for LOO-CV and GCV are both 0.5

And then, we can plot the the data and the estimation curve by span=0.5

spangcv.min = 0.5
plot(mydata$x, mydata$y, xlab="", ylab="", col="gray");
fx = 1:50/50;
fy = sin(12*(fx+0.2))/(fx+0.2)
lines(fx, fy, col=8, lwd=2);
f = loess(y ~ x, mydata, span = spangcv.min)
lines(fx, predict(f, data.frame(x = fx), surface = "direct"), 
      lty=2, lwd=2, col="blue")

In the figure above gray circle is the data, true curve is the gray one and the LOOES curve is the blue one.