Hi everyone,
I’m working with the Ovary dataset from the nlme package and fitting models using brms as follows:
brm_mlm <- brm(follicles ~ sin_time + cos_time+(sin_time||Mare),
data=ovary_df,cores=4)
brm_ar <- brm(follicles ~ sin_time + cos_time+(sin_time||Mare)+ar(gr=Mare,cov=TRUE),
data=ovary_df,cores=4)
These are inspired by the models below from Mixed-Effects Models in S and S-PLUS (Pinheiro & Bates, 2000):
fm1Ovar.lme <- lme( follicles ~ sin(2*pi*Time) + cos(2*pi*Time),
data = Ovary, random = pdDiag(~sin(2*pi*Time)) )
fm2Ovar.lme <- update( fm1Ovar.lme, correlation = corAR1() )
fm5Ovar.lme <- update(fm1Ovar.lme, corr = corARMA(p = 1, q = 1))
In the book, they examine the autocorrelation function (ACF) of the standardized residuals from the first model, observe significant lag-1 autocorrelation, and proceed to fit an AR(1) model (and later an ARMA(1,1)) to address it.
They then plot the ACF of normalized residuals (standardized residuals pre-multiplied by the inverse square root of the estimated residual covariance matrix):
plot( ACF(fm5Ovar.lme, maxLag = 10, resType = "n"),
+ alpha = 0.01 )
to obtain normalized residuals behaving like white noise.
I’m trying to replicate this with my brm_ar model. Here is its summary output:
brm_ar
Family: gaussian
Links: mu = identity; sigma = identity
Formula: follicles ~ sin_time + cos_time + (sin_time || Mare) + ar(gr = Mare, cov = TRUE)
Data: ovary_df (Number of observations: 308)
Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
total post-warmup draws = 4000
Correlation Structures:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
ar[1] 0.58 0.07 0.46 0.72 1.00 1353 1868
Multilevel Hyperparameters:
~Mare (Number of levels: 11)
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sd(Intercept) 3.14 0.93 1.75 5.24 1.00 994 1976
sd(sin_time) 2.63 1.05 0.67 5.00 1.00 1010 1230
Regression Coefficients:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
Intercept 12.25 1.04 10.13 14.28 1.00 1024 1522
sin_time -0.60 0.55 -1.64 0.49 1.00 2905 2934
cos_time -1.23 0.58 -2.37 -0.10 1.00 2351 2581
Further Distributional Parameters:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sigma 2.92 0.13 2.69 3.18 1.00 2293 2336
Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
For the moment, I have tried to manually construct the covariance matrix using the following functions:
make_ar_matrix <- function(n,rho,sigma_ar_2){
M <- matrix(0,n,n)
for (i in 1:n){
for(j in 1:n){
M[i,j] = sigma_ar_2*rho^(abs(i-j))
}
}
return(M)
}
inverse_sqrt_matrix <- function(A) {
eig <- eigen(A)
V <- eig$vectors
D <- eig$values
# Check positive definiteness
if (any(D <= 0)) stop("Matrix is not positive definite")
D_inv_sqrt <- diag(1 / sqrt(D))
A_inv_sqrt <- V %*% D_inv_sqrt %*% t(V)
return(A_inv_sqrt)
}
normalize_resid_ar <- function(df,rho_ar,sigma_ar_2){
count_mare_obs <- df %>% count(Mare) %>% pull(n)
ar_mat_mare <- lapply(count_mare_obs , function(x) make_ar_matrix(x,rho_ar,sigma_ar_2))
ar_inv_sqrt <- lapply(ar_mat_mare, inverse_sqrt_matrix)
group_split_df <- df %>% group_split(Mare)
normalized_res <- lapply(1:length(count_mare_obs ), function(i){
ar_inv_sqrt[[i]]%*%group_split_df[[i]]$resid
})
df <- df %>% mutate(resid_nrm=do.call(rbind,normalized_res))
}
Assuming the residuals follow an AR(1) structure, we expect:
\text{Cov}(\epsilon_{ij},\epsilon{ik}) = \sigma^2\rho^{|j-k|}
Where \rho is ar[1]
and \sigma^2 is the residual variance (sigma^2 = 2.92^2
from the summary above).
My question:
Is there a built-in or recommended way to extract:
- The residual covariance matrix directly from a
brms
model withar(...)
terms, or - The normalized residuals (i.e., transformed standardized residuals that account for the autocorrelation structure)?
Or must I always manually compute them for both AR(1) and ARMA(1,1) structures as shown above?
Thanks in advance!