What a fit writes into your AnnData

fit mutates adata in place and returns the model. Everything below is written by fit() unless noted.

obsm

Key

Shape

Meaning

X_bae

(n_cells, latent_dim)

The latent space. Exactly X @ varm["BAE_encoder_weights"].

X_bae_scaled

(n_cells, latent_dim)

Transfer models only. Per-dimension standardized latent. see Transferring a model to a new dataset.

X_bae_splitsoftmax

(n_cells, 2 * latent_dim)

Only when you call transform_splitsoftmax().

transform also writes X_bae (and X_bae_scaled for transfers), so calling it on a query dataset annotates that dataset in place.

varm

Key

Shape

Written by

BAE_encoder_weights

(n_genes, latent_dim)

fit, apply_encoder

BAE_iteration_frequency

(n_genes, latent_dim)

stability_selection()

BAE_selection_frequency

(n_genes, latent_dim)

the standalone structboost.stability_selection()

bae_program_weights

(n_genes, 2 * latent_dim)

transform_splitsoftmax

Note the encoder matrix is stored transposed relative to the internal (latent_dim, n_genes) layout, so it lines up with adata.var.

uns["bae"]

Always present after a fit:

latent_dim, is_fitted, training_history, latent_init, disentanglement, boosting_precompute_covcache, variance_explained.

boosting_precompute_covcache records the resolved covariance-cache strategy, not the setting: the default is "auto", so without this a run does not say which strategy it actually used.

Present when the corresponding feature was used:

Key

Written when

layer

the fit read a layer rather than adata.X

disentanglement_lambda

disentanglement="correlation"

training_report

diagnostics=True

mandatory_genes

mandatory_genes was passed

batch_key, batch_columns

batch_key was passed

batch_weights, nuisance_ridge

the mode included "encoder"

latent_obs_r2_per_dim, reconstruction_loss_by_obs

batch_key was passed

stability_selection

a stability run was performed

encoder_source

apply_encoder installed an aggregated encoder

Note

layer is absent, not None, when the fit read adata.X. AnnData’s uns cannot hold None, and no key is the honest encoding of “the default”.

The two metrics people confuse

Both appear only for conditioned fits, and they answer different questions.

latent_obs_r2_per_dim

An integration metric. The fraction of each latent dimension’s variance explained by the conditioned obs columns. Values near zero are the goal. A single high entry is a residual covariate axis worth inspecting.

reconstruction_loss_by_obs

A fairness metric. Reconstruction MSE per group. It says whether the model fits all groups comparably, not whether it integrated them.

A group can reconstruct poorly in a perfectly integrated model, and a model can reconstruct every group equally well while still routing batch through the latent. Check the one that matches your question.

Per-group losses are skipped, with a warning, for any column with more than 50 levels. Beyond that the breakdown is per-cell noise rather than a summary.

Gene panel identity

The encoder maps gene columns by position. Applying a model to a matrix with the same width but a different gene order produces plausible, wrong numbers.

transform and reconstruct compare adata.var_names against the names recorded at fit time and emit a UserWarning naming the first differing positions. It is a warning, not an error, and the call proceeds. To move a model onto a genuinely different panel, use from_reference(), which aligns by gene name.

Round-tripping to disk

Everything above survives adata.write_h5ad(...). To persist the model, meaning the decoder, the config and the covariate encodings, see Saving and loading a model. the AnnData keys alone are not enough to reproduce reconstruct.