如何确定在R中的LOESS回归中使用什么跨度?


26

我正在R中运行LOESS回归模型,我想比较具有不同样本量的12个不​​同模型的输出。如果可以帮助回答问题,我可以更详细地描述实际模型。

以下是样本数量:

Fastballs vs RHH 2008-09: 2002
Fastballs vs LHH 2008-09: 2209
Fastballs vs RHH 2010: 527 
Fastballs vs LHH 2010: 449

Changeups vs RHH 2008-09: 365
Changeups vs LHH 2008-09: 824
Changeups vs RHH 2010: 201
Changeups vs LHH 2010: 330

Curveballs vs RHH 2008-09: 488
Curveballs vs LHH 2008-09: 483
Curveballs vs RHH 2010: 213
Curveballs vs LHH 2010: 162

LOESS回归模型是一种表面拟合,其中每个棒球投球的X位置和Y位置用于预测挥杆,挥杆的概率。但是,我想在所有这12个模型之间进行比较,但是由于样本范围如此之大,因此设置相同的跨度(即跨度= 0.5)将产生不同的结果。

我的基本问题是如何确定模型的跨度?较高的跨度可以使拟合更加平滑,而较低的跨度可以捕获更多趋势,但是如果数据太少则会引入统计噪声。对于较小的样本量,使用较高的跨度;对于较大的样本量,使用较低的跨度。

我该怎么办?在R中为LOESS回归模型设置跨度时,有什么好的经验法则?提前致谢!


注意,跨度量度对于不同数量的观测值意味着不同的窗口大小。
塔尔·加利利

2
我经常看到黄土被视为黑盒子。不幸的是,这不是事实。除了查看散点图和叠加的黄土曲线并检查它是否能很好地描述数据中的模式外,别无其他方法。迭代和残差检查是黄土拟合的关键
suncoolsu 2011年

Answers:


14

如果目的是找到具有最低RMSEP的拟合,通常会使用交叉验证,例如k倍。将您的数据分为k组,然后依次离开各组,使用k -1组数据和选定的平滑参数值拟合黄土模型,并使用该模型预测剩下的组。存储遗漏组的预测值,然后重复进行,直到k组中的每一个都被遗漏一次。使用一组预测值,计算RMSEP。然后,对要调整的平滑参数的每个值重复整个过程。选择在CV下给出最低RMSEP的平滑参数。

如您所见,这在计算上相当繁重。如果没有可以用于LOESS的真正CV的通用交叉验证(GCV)替代方法,我会感到惊讶-Hastie等人(第6.2节)指出这很容易做到,并且在他们的一项练习中涵盖了。

我建议您阅读Hastie等人的第5章中的6.1.1、6.1.2和6.2节,以及有关平滑样条曲线正则化的部分(因为内容也适用于此)。(2009)统计学习的要素:数据挖掘,推理和预测。第二版。施普林格。可以免费下载PDF。


8

我建议检查广义的加性模型(GAM,请参阅R中的mgcv软件包)。我只是在自己了解它们,但是它们似乎会自动找出数据证明多少“摇摆不定”是合理的。我还看到您正在处理二项式数据(罢工与非罢工),因此请务必分析原始数据(即,不要按比例汇总,使用原始的逐节距数据)并使用family = '二项式'(假设您将使用R)。如果您了解有关哪些单独的投手和击球手为数据做出了贡献,则可以通过执行广义加性混合模型(GAMM,请参见R中的gamm4包)并将投手和击球手指定为随机效果来增加功率(再次,设置family ='binomial')。最后,您可能希望允许X和Y的平滑之间进行交互,但是我自己从未尝试过这样做,所以我不知道该怎么做。没有X * Y交互作用的gamm4模型如下所示:

fit = gamm4(
    formula = strike ~ s(X) + s(Y) + pitch_type*batter_handedness + (1|pitcher) + (1|batter)
    , data = my_data
    , family = 'binomial'
)
summary(fit$gam)

想到这一点,您可能希望让平滑度在音高类型和击球员手感的每个级别内变化。这使问题更加棘手,因为我还没有找到如何让平滑度因多个变量而变化,从而随后产生有意义的分析测试(请参阅我对R-SIG-Mixed-Models列表的查询)。您可以尝试:

my_data$dummy = factor(paste(my_data$pitch_type,my_data$batter_handedness))
fit = gamm4(
    formula = strike ~ s(X,by=dummy) + s(Y,by=dummy) + pitch_type*batter_handedness + (1|pitcher) + (1|batter)
    , data = my_data
    , family = 'binomial'
)
summary(fit$gam)

但这不会对平滑度进行有意义的测试。在尝试自己解决此问题时,我使用了自举重采样,在每次迭代中,我都获得了完整数据空间的模型预测,然后为该空间中的每个点计算自举95%CI,并计算我想计算的任何影响。


似乎ggplot默认使用GAM的geom_smooth函数处理N> 1000个数据点。
例如

6

对于黄土回归,我作为非统计师的理解是,您可以根据视觉解释来选择跨度(具有众多跨度值的图可以选择看起来最小的平滑度值合适的跨度),也可以使用交叉验证(CV)或广义交叉验证(GCV)。下面是我根据竹泽的出色著作《非参数回归简介》(来自p219)编写的用于黄土回归的GCV的代码。

locv1 <- function(x1, y1, nd, span, ntrial)
{
locvgcv <- function(sp, x1, y1)
{
    nd <- length(x1)

    assign("data1", data.frame(xx1 = x1, yy1 = y1))
    fit.lo <- loess(yy1 ~ xx1, data = data1, span = sp, family = "gaussian", degree = 2, surface = "direct")
    res <- residuals(fit.lo)

    dhat2 <- function(x1, sp)
    {
        nd2 <- length(x1)
        diag1 <- diag(nd2)
        dhat <- rep(0, length = nd2)

        for(jj in 1:nd2){
            y2 <- diag1[, jj]
            assign("data1", data.frame(xx1 = x1, yy1 = y2))
            fit.lo <- loess(yy1 ~ xx1, data = data1, span = sp, family = "gaussian", degree = 2, surface = "direct")
            ey <- fitted.values(fit.lo)
            dhat[jj] <- ey[jj]
            }
            return(dhat)
        }

        dhat <- dhat2(x1, sp)
        trhat <- sum(dhat)
        sse <- sum(res^2)

        cv <- sum((res/(1 - dhat))^2)/nd
        gcv <- sse/(nd * (1 - (trhat/nd))^2)

        return(gcv)
    }

    gcv <- lapply(as.list(span1), locvgcv, x1 = x1, y1 = y1)
    #cvgcv <- unlist(cvgcv)
    #cv <- cvgcv[attr(cvgcv, "names") == "cv"]
    #gcv <- cvgcv[attr(cvgcv, "names") == "gcv"]

    return(gcv)
}

根据我的数据,我执行了以下操作:

nd <- length(Edge2$Distance)
xx <- Edge2$Distance
yy <- lcap

ntrial <- 50
span1 <- seq(from = 0.5, by = 0.01, length = ntrial)

output.lo <- locv1(xx, yy, nd, span1, ntrial)
#cv <- output.lo
gcv <- output.lo

plot(span1, gcv, type = "n", xlab = "span", ylab = "GCV")
points(span1, gcv, pch = 3)
lines(span1, gcv, lwd = 2)
gpcvmin <- seq(along = gcv)[gcv == min(gcv)]
spangcv <- span1[pgcvmin]
gcvmin <- cv[pgcvmin]
points(spangcv, gcvmin, cex = 1, pch = 15)

抱歉,代码相当草率,这是我第一次使用R,但是它应该让您了解如何进行GSV进行黄土回归,以找到比简单的目测更客观的最佳跨度。在上面的图中,您对使函数最小化的跨度感兴趣(在绘制的“曲线”上最低)。


3

如果切换到通用的加性模型,则可以使用mgcv软件包中的gam()函数,作者向我们保证

因此,k的确切选择通常不是关键性的:应该选择足够大的值,以确保您有足够的自由度合理地很好地表示基础“真相”,但又要足够小以保持合理的计算效率。显然,“大”和“小”取决于要解决的特定问题。

k这是平滑器的自由度参数,类似于黄土的平滑度参数)


谢谢Mike :)我从以前的回答中已经看到您在GAM方面很强。我肯定会在将来查看它:)
Tal Galili

2

您可以从头开始使用包装中的loess()函数编写自己的交叉验证循环stats

  1. 设置玩具数据框。

    set.seed(4)
    x <- rnorm(n = 500)
    y <- (x)^3 + (x - 3)^2 + (x - 8) - 1 + rnorm(n = 500, sd = 0.5)
    plot(x, y)
    df <- data.frame(x, y)
    
  2. 设置有用的变量来处理交叉验证循环。

    span.seq <- seq(from = 0.15, to = 0.95, by = 0.05) #explores range of spans
    k <- 10 #number of folds
    set.seed(1) # replicate results
    folds <- sample(x = 1:k, size = length(x), replace = TRUE)
    cv.error.mtrx <- matrix(rep(x = NA, times = k * length(span.seq)), 
                            nrow = length(span.seq), ncol = k)
    
  3. 运行一个嵌套for循环,遍历中的每个跨度span.seq和中的每个折叠folds

    for(i in 1:length(span.seq)) {
      for(j in 1:k) {
        loess.fit <- loess(formula = y ~ x, data = df[folds != j, ], span = span.seq[i])
        preds <- predict(object = loess.fit, newdata = df[folds == j, ])
        cv.error.mtrx[i, j] <- mean((df$y[folds == j] - preds)^2, na.rm = TRUE)
        # some predictions result in `NA` because of the `x` ranges in each fold
     }
    }
    
  4. 从10折中的每一个计算平均交叉验证均方误差:

    CV(10)=110i=110MSEi
    cv.errors <- rowMeans(cv.error.mtrx)
  5. 查找哪个跨度导致最低的。MSE

    best.span.i <- which.min(cv.errors)
    best.span.i
    span.seq[best.span.i]
    
  6. 绘制结果。

    plot(x = span.seq, y = cv.errors, type = "l", main = "CV Plot")
    points(x = span.seq, y = cv.errors, 
           pch = 20, cex = 0.75, col = "blue")
    points(x = span.seq[best.span.i], y = cv.errors[best.span.i], 
           pch = 20, cex = 1, col = "red")
    
    best.loess.fit <- loess(formula = y ~ x, data = df, 
                            span = span.seq[best.span.i])
    
    x.seq <- seq(from = min(x), to = max(x), length = 100)
    
    plot(x = df$x, y = df$y, main = "Best Span Plot")
    lines(x = x.seq, y = predict(object = best.loess.fit, 
                                 newdata = data.frame(x = x.seq)), 
          col = "red", lwd = 2)
    

欢迎使用该网站@hynso。这是一个不错的答案(+1),同时感谢您使用该网站提供的格式设置选项。请注意,自发布此Q以来的7年中,我们不应该成为R专用站点,并且我们对R的特定问题的容忍度有所降低。总之,它可能会更好,如果你可以扩大此瓦特/伪代码为未来观众谁不读R.
恢复莫妮卡-呱

太棒了,谢谢@gung的提示。我将添加伪代码。
hynso


0

所述fANCOVA包提供了一种自动的方式使用GCV或AIC来计算理想的跨距:

FTSE.lo3 <- loess.as(Index, FTSE_close, degree = 1, criterion = c("aicc", "gcv")[2], user.span = NULL, plot = F)
FTSE.lo.predict3 <- predict(FTSE.lo3, data.frame(Index=Index))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.