Define latent factors for capturing unmeasured structure shared between numerator and denominator. Each observation carries factor scores, and each process loads on them with its own loading, so the factors model the residual covariance of an observation's two linear predictors.
Usage
latent_factor(
n_factors = 1L,
prior = NULL,
shared = TRUE,
constraint = c("sum_to_zero", "first_zero")
)
# S3 method for class 'ratiod_latent'
print(x, ...)
# S3 method for class 'ratiod_fit'
latent_factors(fit, summary = TRUE, probs = c(0.025, 0.5, 0.975), ...)Arguments
- n_factors
Integer; number of latent factors. Default is 1. With
shared = TRUEat most 2 factors are identified, withshared = FALSEat most 1 (see Details).- prior
Prior for the diagonal loadings. Default is a PC prior with P(loading > 1) = 0.01, which shrinks toward simpler models.
Logical; if TRUE (default), the factors load on both the numerator and the denominator linear predictor. If FALSE, they load on the numerator only.
- constraint
Identifiability constraint for the factor scores:
"sum_to_zero"(default): each factor's scores sum to zero across observations"first_zero": the first observation's scores are fixed at zero
- x
A ratiod_latent object
- ...
Ignored
- fit
A
ratiod_fitsampled withlatent = latent_factor(...).- summary
Logical;
TRUE(default) returns one row per observation and factor with the posterior mean, sd andprobsquantiles of its score,FALSEthe draws matrix with columnsfactor[observation,factor].- probs
Quantiles for the summary.
Value
A ratiod_latent object for use in tratio().
Details
Why Use Latent Factors?
When modeling ratios, the numerator and denominator often share unmeasured drivers. For example, in relative abundance data, both the focal species count and total count might be affected by:
Observer skill (unmeasured)
Local microhabitat conditions (unmeasured)
Weather on sampling day (unmeasured)
Latent factors capture this shared structure without requiring the drivers to be measured.
Mathematical Model
For observation i with K latent factors:
$$\eta^{num}_i = X^{num}_i \beta^{num} + \sum_{k=1}^{K} \lambda_{1k} f_{ik} + \ldots$$ $$\eta^{denom}_i = X^{denom}_i \beta^{denom} + \sum_{k=1}^{K} \lambda_{2k} f_{ik} + \ldots$$
where:
\(f_{ik} \sim N(0, 1)\) are factor scores, with \(\sum_i f_{ik} = 0\) for each k
\(\Lambda = (\lambda_{pk})\) is the loading matrix, lower triangular with a positive diagonal
The factors add the covariance \(\Lambda \Lambda^\top\) between the two linear predictors of an observation. A 2 x 2 covariance has three free entries: one factor (two loadings) fills it up to rank one and two factors (three loadings) fill it completely, so a third factor would not be identified. Fixing \(\Lambda\) lower triangular with a positive diagonal makes it the Cholesky factor of that covariance, which is unique; without it the loadings and scores rotate freely.
The ratio's linear predictor gains \(\sum_k (\lambda_{1k} - \lambda_{2k}) f_{ik}\): a factor that loads equally on both processes cancels in the ratio, one that loads unequally moves it.
Priors
Each diagonal loading carries prior (a PC prior by default). Each
off-diagonal loading carries a normal prior centred at zero, with the
standard deviation that gives \(|\lambda|\) the same 99% quantile as the
diagonal prior.
Choosing n_factors
n_factors = 1: one unmeasured driver acting on both processes, with process-specific strengthn_factors = 2: an unrestricted residual covariance between the two processes
Relationship to Random Effects
Latent factors differ from random effects in several ways:
Random effects are grouped (e.g., site-level), factors are observation-level
Random effects require grouping structure, factors don't
Factors capture residual correlation not explained by observed predictors
You can use both together: random effects for known grouping, factors for residual unmeasured structure.
See also
tratio() for model fitting, prior_pc() for prior specification
Examples
# Basic latent factor (single factor on both processes)
latent_factor()
#> Latent factor specification
#> ===========================
#>
#> Number of factors: 1
#> Shared: Yes (loads on both num and denom)
#> Constraint: sum_to_zero
#>
#> Diagonal loading prior:
#> PC prior: P(x > 1.00) = 0.010
#> => Exponential(4.605)
# Two latent factors: an unrestricted residual covariance
latent_factor(n_factors = 2)
#> Latent factor specification
#> ===========================
#>
#> Number of factors: 2
#> Shared: Yes (loads on both num and denom)
#> Constraint: sum_to_zero
#>
#> Diagonal loading prior:
#> PC prior: P(x > 1.00) = 0.010
#> => Exponential(4.605)
# Custom prior (more regularization)
latent_factor(n_factors = 1, prior = prior_pc(U = 0.5, alpha = 0.01))
#> Latent factor specification
#> ===========================
#>
#> Number of factors: 1
#> Shared: Yes (loads on both num and denom)
#> Constraint: sum_to_zero
#>
#> Diagonal loading prior:
#> PC prior: P(x > 0.50) = 0.010
#> => Exponential(9.210)
# Numerator-only factor (not shared)
latent_factor(n_factors = 1, shared = FALSE)
#> Latent factor specification
#> ===========================
#>
#> Number of factors: 1
#> Shared: No (numerator only)
#> Constraint: sum_to_zero
#>
#> Diagonal loading prior:
#> PC prior: P(x > 1.00) = 0.010
#> => Exponential(4.605)
# \donttest{
# Use in model fitting
set.seed(1)
n <- 60
df <- data.frame(
species_count = rnbinom(n, size = 5, mu = 15),
total_count = rnbinom(n, size = 8, mu = 100),
habitat = factor(rep(c("forest", "grassland"), each = n / 2)),
site = factor(rep(1:10, each = n / 10))
)
fit <- tratio(
species_count | total_count ~ habitat + (1 | site),
data = df,
family = ratiod_negbin_negbin(),
latent = latent_factor(n_factors = 1),
control = list(iter = 400, chains = 1, seed = 1)
)
#> Warning: 1 divergent transition(s) after warmup; raise `adapt_delta` or reparameterise.
head(latent_factors(fit))
#> observation factor mean sd q2.5 q50 q97.5
#> 1 1 1 0.05108493 0.8919842 -1.429591 0.0412012 1.660451
#> 2 2 1 -0.04685641 1.0814458 -1.901767 -0.0997448 1.665242
#> 3 3 1 -0.12870936 1.0316818 -2.003343 -0.1618067 1.754759
#> 4 4 1 0.16531480 1.3374516 -2.746690 0.3052586 2.452132
#> 5 5 1 -0.25082946 1.0148477 -2.186926 -0.2652328 1.275443
#> 6 6 1 -0.41132380 1.1269832 -2.550916 -0.3559215 1.469450
# }