0
$\begingroup$

I'm new to GAMs and am trying to figure out how to parameterize and interpret models where the response variable is percent of salmon migrating through a given route and I think there might be autocorrelation. Apologies in advance for the long post but you can skip to my question at the end if it's helpful.

Example data is here

The data contains percent (my response variable), Julian date (JulianDate), Year, and stock (GroupName). There is also some biological reason to believe that at least some stocks differ in their percents depending upon which "cycle" the year of migration is in (CycleLine, e.g. percents may be lower for some stocks for Cycle3 years).

BACKGROUND MODEL PARAMETERIZATION

I think the percent should be explained by JulianDate, GroupName, and maybe CycleLine. I started with:

gam1 <- bam(Percent ~ GroupName +
          s(JulianDate) + s(JulianDate, by = GroupName),
        data = data,
        method = "fREML", 
        family = betar(), 
        discrete = T, 
        select = T, 
# select = T will place extra penalties to help with variable selection
        nthreads = 2)

based off of this question where @GavinSimpson suggested estimating a global or average smooth effect of x on y (the s(JulianDate) term) plus a smooth difference term (the second s(JulianDate, by = GroupName) term).

summary(gam1) Produces:

Family: Beta regression(3.059) 
Link function: logit 

Formula:
Percent ~ GroupName + s(JulianDate) + s(JulianDate, by = GroupName)

Parametric coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)     -0.16096    0.04339  -3.709 0.000210 ***
GroupNameStock2  0.18920    0.04975   3.803 0.000145 ***
GroupNameStock4 -0.51227    0.05981  -8.565  < 2e-16 ***
GroupNameStock3  0.49874    0.05098   9.783  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Approximate significance of smooth terms:
                                     edf Ref.df      F  p-value    
s(JulianDate)                 6.8299873      9 59.983  < 2e-16 ***
s(JulianDate):GroupNameStock1 0.0001348      9  0.000 0.394002    
s(JulianDate):GroupNameStock2 0.2757218      9  0.036 0.278520    
s(JulianDate):GroupNameStock4 5.7796322      9  2.487 0.000215 ***
s(JulianDate):GroupNameStock3 3.5601339      9  5.724  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

 R-sq.(adj) =  0.397   Deviance explained = 44.1%
 fREML = 2947.5  Scale est. = 1         n = 4385

gratia::draw(gam1) produces:

draw(gam1)

But the R-sq is only 0.397 and when I check forecast::auto.arima(residuals(gam1)) I get

Series: residuals(gam1) 
ARIMA(2,1,2) 

Coefficients:
         ar1      ar2     ma1     ma2
       1.4314  -0.5199  -1.482  0.4889
 s.e.  0.0723   0.0618   0.076  0.0754

sigma^2 = 0.1825:  log likelihood = -2490.74
AIC=4991.48   AICc=4991.5   BIC=5023.41

As I mentioned above, I have reason to believe that CycleLine could improve the fit and maybe deal with the autocorrelation:

gam2 <- bam(Percent ~ GroupName + CycleLine + 
          s(JulianDate) + 
          s(JulianDate, by = GroupName) +
          s(JulianDate, by = CycleLine), 
        data = data, 
        method = "fREML",
        family = betar(), 
        discrete = T, 
        select = T, 
        nthreads = 2)

The summary(gam2)$r.sq is still only 0.426 though and forecast::auto.arima(residuals(gam2)) produces:

Series: residuals(gam2) 
ARIMA(2,1,2) 

Coefficients:
         ar1      ar2      ma1     ma2
      1.3935  -0.4912  -1.4397  0.4464
s.e.  0.0709   0.0605   0.0744  0.0739

sigma^2 = 0.1908:  log likelihood = -2587.87
AIC=5185.73   AICc=5185.75   BIC=5217.66

Even when I scrap CycleLine and just look at the interaction between JulianDate and Year the model actually has a slightly higher r-squared of 0.523 but still has un-accounted for autocorrelation:

gam3 <- bam(Percent ~ GroupName +
          te(JulianDate, Year, by = GroupName),
        data = data,
        method = "fREML",
        family = betar(), 
        discrete = T, 
        select = T, 
        nthreads = 2)

auto.arima(residuals(gam3)) produces:

Series: residuals(gam3) 
ARIMA(2,0,5) with zero mean 

Overall, all the auto.arima() checks and acf() checks show there is unaccounted for autocorrelation:

    par(mfrow = c(1, 4), cex = 1.1)
    acf(residuals(gam1))
    acf(residuals(gam2))
    acf(residuals(gam3))

acf plots showing autocorrelation in GAMs

AUTOCORRELATION QUESTION

Given that background, I'm struggling with how to correctly account for the autocorrelation. It seems like adding explanatory variables doesn't capture it so I was advised here to look into setting the rho and AR.start variables or increasing my k value but I'm not sure that I'm parameterizing those correctly. I'm not sure what to set my k value to and when I try to add rho and AR.start I get strange results. For example, using a variation of gam2:

# First mark the start of each time series as TRUE, 
# and all other data points as FALSE
data %<>%
  arrange(GroupName, Year, CycleLine, JulianDate) %>% 
  as.data.frame()

simdat <- itsadug::start_event(data, column = "JulianDate", 
                               event = c("GroupName", "CycleLine"), 
                               label.event = "Event")

r1 <- itsadug::start_value_rho(gam2, plot = TRUE) # value = 0.879586

gam4 <- bam(Percent ~ GroupName + CycleLine + 
          s(JulianDate) + 
          s(JulianDate, by = GroupName) +
          s(JulianDate, by = CycleLine), 
        data = simdat, 
        rho = r1, 
        AR.start = simdat$start.event,
    method = "fREML", 
    family = betar(), 
    discrete = T, 
    select = T, 
    nthreads = 2)
```
but I think something must be wrong since the `summary(gam4)$r.sq` is -0.099 and when I look at the uncorrected vs. 'corrected' residuals I'm still seeing autocorrelation: 
```r
  # Uncorrected versus corrected residuals:
par(mfrow = c(1, 2), cex = 1.1)
itsadug::acf_resid(gam2)
itsadug::acf_resid(gam4)

comparison of acf between gam without rho/AR.start and gam that has those parameters specified

How can I better account for the autocorrelation? My understanding that rho is just for an AR1 but the results of auto.arima suggest that the process is more complicated? And the r1 value doesn't match the output of forecast::auto.arima(residuals(gam2))$coef (see values above).

So, to summarize, any feedback would be greatly appreciated on how I can better account for autocorrelation. Even when I have JulianDate and CycleLine/Year I see autocorrelation in the residuals AND when I set rho and AR.start I still see autocorrelation. Am I miss-specifying rho and AR.start or do I need a more complex way of accounting for the autocorrelation (and what might that be)?

$\endgroup$
2
  • 1
    $\begingroup$ You should stratify the autocorrelation checks by group. $\endgroup$
    – Roland
    Commented Aug 1 at 12:17
  • $\begingroup$ Thanks @Roland, when I look at it by group I still see autocorrelation and it seems to differ by group (see edit to my post above). Still not sure where to go from there though. $\endgroup$ Commented Aug 8 at 15:17

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.