Skip to contents

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 = TRUE at most 2 factors are identified, with shared = FALSE at 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.

shared

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_fit sampled with latent = latent_factor(...).

summary

Logical; TRUE (default) returns one row per observation and factor with the posterior mean, sd and probs quantiles of its score, FALSE the draws matrix with columns factor[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 strength

  • n_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
# }