3

The code for the Holt-Winters function in R contains the following if clause:

if (!is.null(gamma) && is.logical(gamma) && !gamma)

Obviously the first means "if NOT gamma is Null". I am a bit confused by the meaning of the third - it looks like this reads, "if NOT gamma", but there is nothing following like an equals sign or an is.null, etc.

Forgive me if this is a somewhat basic question, but I am very new to R.

Full code:

{
x <- as.ts(x)
seasonal <- match.arg(seasonal)
f <- frequency(x)
if (!is.null(alpha) && (alpha == 0)) 
    stop("cannot fit models without level ('alpha' must not be 0 or FALSE)")
if (!all(is.null(c(alpha, beta, gamma))) && any(c(alpha, 
    beta, gamma) < 0 || c(alpha, beta, gamma) > 1)) 
    stop("'alpha', 'beta' and 'gamma' must be within the unit interval")
if ((is.null(gamma) || gamma > 0)) {
    if (seasonal == "multiplicative" && any(x == 0)) 
        stop("data must be non-zero for multiplicative Holt-Winters")
    if (start.periods < 2) 
        stop("need at least 2 periods to compute seasonal start values")
}
if (!is.null(gamma) && is.logical(gamma) && !gamma) {
    expsmooth <- !is.null(beta) && is.logical(beta) && !beta
    if (is.null(l.start)) 
        l.start <- if (expsmooth) 
            x[1L]
        else x[2L]
    if (is.null(b.start)) 
        if (is.null(beta) || !is.logical(beta) || beta) 
            b.start <- x[2L] - x[1L]
    start.time <- 3 - expsmooth
    s.start <- 0
}
else {
    start.time <- f + 1
    wind <- start.periods * f
    st <- decompose(ts(x[1L:wind], start = start(x), frequency = f), 
        seasonal)
    if (is.null(l.start) || is.null(b.start)) {
        dat <- na.omit(st$trend)
        cf <- coef(.lm.fit(x = cbind(1, seq_along(dat)), 
            y = dat))
        if (is.null(l.start)) 
            l.start <- cf[1L]
        if (is.null(b.start)) 
            b.start <- cf[2L]
    }
    if (is.null(s.start)) 
        s.start <- st$figure
}
lenx <- as.integer(length(x))
if (is.na(lenx)) 
    stop("invalid length(x)")
len <- lenx - start.time + 1
hw <- function(alpha, beta, gamma) .C(C_HoltWinters, as.double(x), 
    lenx, as.double(max(min(alpha, 1), 0)), as.double(max(min(beta, 
        1), 0)), as.double(max(min(gamma, 1), 0)), as.integer(start.time), 
    as.integer(!+(seasonal == "multiplicative")), as.integer(f), 
    as.integer(!is.logical(beta) || beta), as.integer(!is.logical(gamma) || 
        gamma), a = as.double(l.start), b = as.double(b.start), 
    s = as.double(s.start), SSE = as.double(0), level = double(len + 
        1L), trend = double(len + 1L), seasonal = double(len + 
        f))
if (is.null(gamma)) {
    if (is.null(alpha)) {
        if (is.null(beta)) {
            error <- function(p) hw(p[1L], p[2L], p[3L])$SSE
            sol <- optim(optim.start, error, method = "L-BFGS-B", 
              lower = c(0, 0, 0), upper = c(1, 1, 1), control = optim.control)
            if (sol$convergence || any(sol$par < 0 | sol$par > 
              1)) {
              if (sol$convergence > 50) {
                warning(gettextf("optimization difficulties: %s", 
                  sol$message), domain = NA)
              }
              else stop("optimization failure")
            }
            alpha <- sol$par[1L]
            beta <- sol$par[2L]
            gamma <- sol$par[3L]
        }
        else {
            error <- function(p) hw(p[1L], beta, p[2L])$SSE
            sol <- optim(c(optim.start["alpha"], optim.start["gamma"]), 
              error, method = "L-BFGS-B", lower = c(0, 0), 
              upper = c(1, 1), control = optim.control)
            if (sol$convergence || any(sol$par < 0 | sol$par > 
              1)) {
              if (sol$convergence > 50) {
                warning(gettextf("optimization difficulties: %s", 
                  sol$message), domain = NA)
              }
              else stop("optimization failure")
            }
            alpha <- sol$par[1L]
            gamma <- sol$par[2L]
        }
    }
    else {
        if (is.null(beta)) {
            error <- function(p) hw(alpha, p[1L], p[2L])$SSE
            sol <- optim(c(optim.start["beta"], optim.start["gamma"]), 
              error, method = "L-BFGS-B", lower = c(0, 0), 
              upper = c(1, 1), control = optim.control)
            if (sol$convergence || any(sol$par < 0 | sol$par > 
              1)) {
              if (sol$convergence > 50) {
                warning(gettextf("optimization difficulties: %s", 
                  sol$message), domain = NA)
              }
              else stop("optimization failure")
            }
            beta <- sol$par[1L]
            gamma <- sol$par[2L]
        }
        else {
            error <- function(p) hw(alpha, beta, p)$SSE
            gamma <- optimize(error, lower = 0, upper = 1)$minimum
        }
    }
}
else {
    if (is.null(alpha)) {
        if (is.null(beta)) {
            error <- function(p) hw(p[1L], p[2L], gamma)$SSE
            sol <- optim(c(optim.start["alpha"], optim.start["beta"]), 
              error, method = "L-BFGS-B", lower = c(0, 0), 
              upper = c(1, 1), control = optim.control)
            if (sol$convergence || any(sol$par < 0 | sol$par > 
              1)) {
              if (sol$convergence > 50) {
                warning(gettextf("optimization difficulties: %s", 
                  sol$message), domain = NA)
              }
              else stop("optimization failure")
            }
            alpha <- sol$par[1L]
            beta <- sol$par[2L]
        }
        else {
            error <- function(p) hw(p, beta, gamma)$SSE
            alpha <- optimize(error, lower = 0, upper = 1)$minimum
        }
    }
    else {
        if (is.null(beta)) {
            error <- function(p) hw(alpha, p, gamma)$SSE
            beta <- optimize(error, lower = 0, upper = 1)$minimum
        }
    }
}
final.fit <- hw(alpha, beta, gamma)
fitted <- ts(cbind(xhat = final.fit$level[-len - 1], level = final.fit$level[-len - 
    1], trend = if (!is.logical(beta) || beta) 
    final.fit$trend[-len - 1], season = if (!is.logical(gamma) || 
    gamma) 
    final.fit$seasonal[1L:len]), start = start(lag(x, k = 1 - 
    start.time)), frequency = frequency(x))
if (!is.logical(beta) || beta) 
    fitted[, 1] <- fitted[, 1] + fitted[, "trend"]
if (!is.logical(gamma) || gamma) 
    fitted[, 1] <- if (seasonal == "multiplicative") 
        fitted[, 1] * fitted[, "season"]
    else fitted[, 1] + fitted[, "season"]
structure(list(fitted = fitted, x = x, alpha = alpha, beta = beta, 
    gamma = gamma, coefficients = c(a = final.fit$level[len + 
        1], b = if (!is.logical(beta) || beta) final.fit$trend[len + 
        1], s = if (!is.logical(gamma) || gamma) final.fit$seasonal[len + 
        1L:f]), seasonal = seasonal, SSE = final.fit$SSE, 
    call = match.call()), class = "HoltWinters")

}

Statsanalyst
  • 331
  • 2
  • 3
  • 16
  • What about second one? What do you think `is.logical` means? – pogibas Nov 09 '17 at 15:19
  • @PoGibas I would assume it means, "if gamma is of object type 'logical'." – Statsanalyst Nov 09 '17 at 15:20
  • https://stat.ethz.ch/R-manual/R-devel/library/base/html/logical.html – Statsanalyst Nov 09 '17 at 15:21
  • 2
    yes! And logical is `TRUE` or `FALSE`, therefore we know that `gamma` is `TRUE` or `FALSE`. What do you think `!TRUE` or `!FALSE` means? – pogibas Nov 09 '17 at 15:23
  • @Gregor a plausible answer, although gamma isn't a Boolean value in the Holt-Winters model - it's a number between 0 and 1. – Statsanalyst Nov 09 '17 at 15:27
  • Many functions allow you to pass in parameters of different types. You can pass an number OR a logical value as `gamma=`. This code specifically checks if you passed a logical value rather than a numeric value and only runs if FALSE. from the `?HoltWinters` help page: "gamma - parameter used for the seasonal component. If set to FALSE, an non-seasonal model is fitted." – MrFlick Nov 09 '17 at 15:29
  • @Gregor: yes. I adjusted my answer when I realized this. – Ben Bolker Nov 09 '17 at 15:40
  • @Gregor +1 - seems like the answer to me. – Statsanalyst Nov 09 '17 at 15:45

1 Answers1

3

edit: I was confused about the context.

! is the logical-NOT operator in R.

As pointed out in the comments, R often allows users to pass arguments of different types. In this case ?HoltWinters says

gamma: gamma parameter used for the seasonal component. If set to ‘FALSE’, an non-seasonal model is fitted.

So gamma can be either a numeric value or a logical (FALSE) value.

Since this !gamma follows is.logical(gamma) && ..., it will only be evaluated if gamma is a logical (TRUE/FALSE) value. In this case, !gamma is equivalent to gamma==FALSE, but most programmers would shorten this to !gamma (so that FALSE becomes TRUE and TRUE becomes FALSE).

We wouldn't want to test gamma=FALSE without the is.logical() test first, because someone might have specified gamma=0, in which case R would evaluate 0==FALSE, which according to its coercion rules is TRUE.

This test could also have been written if (identical(gamma,FALSE)) - which would correctly evaluate both NULL and 0 as different from FALSE.


In contrast, if gamma were to be numeric, !gamma would be shorthand for gamma != 0.

According to R's rules for coercion from floating-point to logical, 0 gets converted to FALSE and any non-zero, non-NA value gets converted to TRUE (see this question for more detail). Thus !gamma is equivalent to gamma!=0. Some old-school programmers use this for brevity; I don't think the brevity-clarity tradeoff is worth it, but that's just my opinion.

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453