Skip to content

fix: match Seurat key sanitization when detecting assay-specific reductions - #3

Merged
zqfang merged 1 commit into
mainfrom
fix/reduction-key-sanitization
Jul 23, 2026
Merged

fix: match Seurat key sanitization when detecting assay-specific reductions#3
zqfang merged 1 commit into
mainfrom
fix/reduction-key-sanitization

Conversation

@zqfang

@zqfang zqfang commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Problem

Reductions whose names contain non-alphanumeric characters were silently dropped from .obsm on export.

Seurat strips non-alphanumerics when deriving a key from a reduction name (umap_harmony → key umapharmony_), but the modality-specific check in WriteH5ADHelper only stripped dots — gsub('\\.', '', red_name). The sanitized name therefore still contained the underscore that the key no longer had, so the reduction failed every branch of the test, hit the next, and was skipped without any warning.

Embeddings that carry feature.loadings (e.g. pca) survived via the earlier loadings-based check. UMAP-style embeddings have no loadings, so they had no other way to be recognised.

Reproduced on a real single-assay object with WriteH5AD(..., assay = 'RNA'):

exported: X_harmony, X_humap, X_humap2, X_humap3, X_pca, X_umap
dropped:  umap_harmony, pca_harmony, pca_uncorrected, mrVI_umap

The dropped set is exactly the reductions whose names contain a character Seurat removes from the key.

Changes

All in R/WriteH5MU.R:

  • Strip all non-alphanumerics ([^[:alnum:]]) instead of only dots, so the comparison matches Seurat's own key derivation. Applied in both WriteH5ADHelper and the parallel block in WriteH5MU so the two paths stay in agreement.
  • Add fixed = TRUE to the grepl calls — reduction names were being interpreted as regex patterns, which is wrong for arbitrary user-chosen names.
  • Replace the silent next with a warning() naming the reduction, its key, and its assay.used, so a future mismatch is visible rather than producing a quietly incomplete file.

Behavior change to review

In WriteH5MU, these four reductions were previously misclassified as multimodal and written to the global /obsm. They are now correctly recognised as assay-specific and land in /mod/<assay>/obsm.

This is the intended placement, but it is a breaking change for any downstream code reading these embeddings from the top-level obsm of an .h5mu.

Test plan

  • devtools::test() — 57 pass, 0 fail, 0 warn
  • Re-exported a 58122 × 38322 single-assay Seurat object with WriteH5AD(assay = 'RNA'); all 10 reductions now present in .obsm (was 6)
  • Read back with anndata: X_umap_harmony is (58122, 2) float64, all finite, values match the R-side cell.embeddings exactly; cell order matches colnames(srt)
  • WriteH5MU round-trip via mudata.read_h5mu — all 10 under mod['RNA'].obsm, global obsm holds only the RNA mod map
  • Confirm no downstream consumers depend on the old global-obsm placement

…ctions

Reductions whose names contain non-alphanumeric characters were silently
dropped from .obsm on export. Seurat strips those characters when deriving
a key from a reduction name (umap_harmony -> umapharmony_), but the
modality-specific check only stripped dots, so the name never matched its
own key and the reduction failed every branch of the test.

On a single-assay object this dropped umap_harmony, pca_harmony,
pca_uncorrected and mrVI_umap while keeping umap, pca and harmony.
Embeddings without feature loadings had no other way to be recognised.

- strip all non-alphanumerics instead of only dots, in both
  WriteH5ADHelper and the parallel block in WriteH5MU
- add fixed = TRUE: reduction names were being treated as regex patterns
- warn instead of silently skipping, so a future mismatch is visible

Note for WriteH5MU: these reductions were previously misclassified as
multimodal and written to the global /obsm. They now correctly land in
/mod/<assay>/obsm.
Copilot AI review requested due to automatic review settings July 23, 2026 05:19
@zqfang
zqfang merged commit 71c8a0f into main Jul 23, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes export-time detection of assay-specific reductions by matching Seurat’s key sanitization rules, preventing reductions with non-alphanumeric characters in their names from being silently omitted or misrouted during WriteH5AD/WriteH5MU export.

Changes:

  • Sanitize reduction names by stripping all non-alphanumeric characters (instead of only .) when comparing reduction names to Seurat-derived keys.
  • Use grepl(..., fixed = TRUE) to avoid interpreting arbitrary reduction names as regex patterns.
  • Add a warning when a reduction is not recognized as assay-specific in WriteH5ADHelper, instead of silently skipping it.
Comments suppressed due to low confidence (1)

R/WriteH5MU.R:422

  • Same as above: if gsub('[^[:alnum:]]', '', red_name) yields an empty string, grepl('', red@key, fixed=TRUE) will always match and can misclassify the reduction as modality-specific. Add an nzchar() guard (or equivalent).
        if (grepl(tolower(assay_emb), tolower(red_name), fixed = TRUE) ||
            grepl(tolower(assay_emb), tolower(red@key), fixed = TRUE) ||
            grepl(tolower(red_name), tolower(red@key), fixed = TRUE)  ||
            grepl(tolower(gsub('[^[:alnum:]]', '', red_name)), tolower(red@key), fixed = TRUE)
            ) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread R/WriteH5MU.R
Comment on lines +164 to 168
if (grepl(tolower(emb_assay), tolower(red_name), fixed = TRUE) ||
grepl(tolower(emb_assay), tolower(red@key), fixed = TRUE) ||
grepl(tolower(red_name), tolower(red@key), fixed = TRUE) ||
grepl(tolower(gsub('[^[:alnum:]]', '', red_name)), tolower(red@key), fixed = TRUE)
) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants