Skip to contents
library(GLMMcosinor)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(ggplot2)

GLMMcosinor allows specification of mixed models accounting for fixed and/or random effects. Mixed model specification follows the lme4 format. See their vignette, Fitting Linear Mixed-Effects Models Using lme4, for details about how to specify mixed models.

Data with subject-level differences

To illustrate an example of using a model with random effects on the cosinor components, we will first simulate some data with id-level differences in amplitude and acrophase.

f_sample_id <- function(id_num,
                        n = 30,
                        mesor,
                        amp,
                        acro,
                        family = "gaussian",
                        sd = 0.2,
                        period,
                        n_components,
                        beta.group = TRUE) {
  data <- simulate_cosinor(
    n = n,
    mesor = mesor,
    amp = amp,
    acro = acro,
    family = family,
    sd = sd,
    period = period,
    n_components = n_components
  )
  data$subject <- id_num
  data
}

dat_mixed <- do.call(
  "rbind",
  lapply(1:30, function(x) {
    f_sample_id(
      id_num = x,
      mesor = rnorm(1, mean = 0, sd = 1),
      amp = rnorm(1, mean = 3, sd = 0.5),
      acro = rnorm(1, mean = 1.5, sd = 0.2),
      period = 24,
      n_components = 1
    )
  })
) |>
  mutate(subject = as.factor(subject))

A quick graph shows how there are individual differences in terms of MESOR, amplitude and phase.

dat_mixed |>
  ggplot(aes(times, Y, col = subject)) +
  geom_point() +
  geom_line() +
  theme_bw()

A single component model with random effects

For the model, we should include a random effect for the MESOR, amplitude and acrophase as these are clustered within individuals.

In the model formula, we can use the special amp_acro[n] which represents the nth cosinor component. In this case, we only have one component so we use amp_acro1. Following the lme4-style mixed model formula, we add our random effect for this component and the intercept term (MESOR) clustered within subjects by using (1 + amp_acro1 | subject). The code below fits this model

mixed_mod <- cglmm(
  Y ~ amp_acro(times,
    n_components = 1,
    period = 24
  ) + (1 + amp_acro1 | subject),
  data = dat_mixed
)

This works by replacing the amp_acro1 with the relevant cosinor components when the data is rearranged and the formula created. The formula created can be accessed using .$formula, and shows the amp_acro1 is replaced by the main_rrr1 and main_sss1 (the cosine and sine components of time that also appear in the fixed effects).

mixed_mod$formula
#> Y ~ main_rrr1 + main_sss1 + (1 + main_rrr1 + main_sss1 | subject)
#> <environment: 0x559311317d10>

The mixed model can also be plotted using autoplot, but some of the plotting features that are available for fixed-effects models may not be available for mixed-effect models.

autoplot(mixed_mod, superimpose.data = TRUE)

The summary of the model shows that the input means for MESOR, amplitude and acrophase are similar to what we specified in the simulation (0, 3, and 1.5, respectively).

summary(mixed_mod)
#> 
#>  Conditional Model 
#> Raw model coefficients:
#>                estimate standard.error    lower.CI upper.CI    p.value    
#> (Intercept) -0.03231528     0.18976318 -0.40424427  0.33961 0.86477998    
#> main_rrr1    0.27614529     0.07553744  0.12809463  0.42420 0.00025644 ***
#> main_sss1    2.91288462     0.07833708  2.75934677  3.06642 < 2.22e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Transformed coefficients:
#>                estimate standard.error    lower.CI upper.CI p.value    
#> (Intercept) -0.03231528     0.18976318 -0.40424427  0.33961 0.86478    
#> amp1         2.92594481     0.07824558  2.77258630  3.07930 < 2e-16 ***
#> acr1         1.47627749     0.02580137  1.42570774  1.52685 < 2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

We can see that the predicted values from the model closely resemble the patterns we see in the input data.

data.frame(pred = predict(mixed_mod)) |>
  cbind(dat_mixed) |>
  ggplot() +
  geom_point(aes(x = times, y = Y, col = subject)) +
  geom_line(aes(x = times, y = pred, col = subject))

This looks like a good model fit for these data. We can highlight the importance of using a mixed model in this situation rather than a fixed effects only model by creating that (bad) model and comparing the two by using the Akaike information criterion using AIC().

fixed_effects_mod <- cglmm(
  Y ~ amp_acro(times, n_components = 1, period = 24),
  data = dat_mixed
)

AIC(fixed_effects_mod$fit)
#> [1] 2765.136
AIC(mixed_mod$fit)
#> [1] 82.13652

Aside from not being able to be useful to see the differences between subjects from the model, we end up with much worse model fit and likely biased and/or imprecise estimates of our fixed effects that we are interested in!