Group factor analysis (GFA)#
Group factor analysis is a Bayesian dimensionality reduction method for multi-group data. It automatically determines:
How many latent dimensions describe the data
Which groups each latent dimension involves
Model specification#
GFA uses a static state model with Bayesian observation parameters.
State model. Latent variables are independent across samples:
Observation model. Each group \(m\) has its own loading matrix and noise:
where \(\boldsymbol{\epsilon}^{(m)} \sim \mathcal{N}(\mathbf{0}, \text{diag}(\boldsymbol{\phi}^{(m)})^{-1})\). The diagonal noise covariance (anisotropic noise) encourages latent variables to explain shared variance among observed dimensions.
Automatic relevance determination#
GFA uses ARD priors to automatically select the number of latent dimensions. Each loading column has a precision parameter \(\alpha_j^{(m)}\) with a Gamma prior. During inference:
If latent \(j\) is needed for group \(m\): \(\alpha_j^{(m)}\) stays small, allowing non-zero loadings
If latent \(j\) is not needed: \(\alpha_j^{(m)}\) grows large, shrinking the loading column toward zero
This mechanism prunes unnecessary dimensions automatically. Initialize with
more latent dimensions than expected (x_dim_init), and GFA will prune to
the effective dimensionality.
Inference#
GFA uses mean-field variational inference, maximizing the evidence lower bound (ELBO). Convergence is monitored via the ELBO, which is guaranteed to be non-decreasing.
from latents.gfa import GFAModel, GFAFitConfig
from latents.callbacks import ProgressCallback
model = GFAModel(config=GFAFitConfig(x_dim_init=10))
model.fit(Y, callbacks=[ProgressCallback()])
# Check convergence
model.tracker.plot_lb()
print(f"Effective dimensionality: {model.latents_posterior.x_dim}")
See Fitting GFA to simulated data for a complete example.
Interpreting results#
Variance explained#
The fraction of shared variance explained by latent \(j\) in group \(m\) is:
A latent is considered significant in a group if it explains at least some threshold of shared variance (e.g., \(\nu_j^{(m)} \geq 0.02\)), though this threshold depends on your use case.
# Compute dimensionalities and variance explained
num_dim, sig_dims, var_exp, dim_types = model.obs_posterior.compute_dimensionalities(
cutoff_shared_var=0.02, # significance threshold
)
# Visualize
from latents.plotting import plot_dimensionalities, plot_var_exp
plot_dimensionalities(num_dim, dim_types, group_names=["A", "B", "C"])
plot_var_exp(var_exp, dim_types, group_names=["A", "B", "C"])
Leave-group-out prediction#
Leave-group-out prediction measures how well GFA captures cross-group interactions. For each group, predict its activity using only the remaining groups. The leave-group-out \(R^2\) quantifies prediction accuracy:
\(R^2_{\text{lgo}} = 1\): perfect prediction
\(R^2_{\text{lgo}} < 0\): worse than predicting the mean
from latents.gfa.analysis import predictive_performance
# Evaluate on held-out test data (Y_test is an ObsStatic instance)
R2, MSE = predictive_performance(Y_test, model.obs_posterior)
print(f"Leave-group-out R²: {R2:.3f}")
References#
Latents implements GFA with anisotropic observation noise:
Gokcen, E., Jasper, A. I., Xu, A., Kohn, A., Machens, C. K. & Yu, B. M. Uncovering motifs of concurrent signaling across multiple neuronal populations. Advances in Neural Information Processing Systems 36, 34711-34722 (2023). Paper link
The original GFA formulation with isotropic noise:
Klami, A., Virtanen, S., Leppäaho, E. & Kaski, S. Group factor analysis. IEEE Transactions on Neural Networks and Learning Systems 26, 2136-2147 (2015). DOI: 10.1109/tnnls.2014.2376974