Skip to content

Fix Ingest truncating to settings.N_PCS even when pp.neighbors used more PCs - #4401

Open
SID-6921 wants to merge 1 commit into
scverse:mainfrom
SID-6921:fix-ingest-pca-truncation
Open

SID-6921 wants to merge 1 commit into
scverse:mainfrom
SID-6921:fix-ingest-pca-truncation

Conversation

@SID-6921

@SID-6921 SID-6921 commented Oct 7, 2026

Copy link
Copy Markdown

Ingest falls back to a hardcoded settings.N_PCS (50) whenever the reference adata's neighbors params recorded neither use_rep nor n_pcs — but that's exactly the state pp.neighbors leaves when called with its own defaults, and its default (n_pcs=None) uses the full stored PCA representation unsliced (_get_pca_or_small_x), not 50 components. Whenever more than 50 PCs were computed (e.g. sc.pp.pca(adata, n_comps=100) then sc.pp.neighbors(adata)), Ingest silently builds its KNN index and transforms new data in a different, truncated representation than the one that actually produced the reference neighbor graph — no error, just a quietly wrong embedding/label mapping for sc.tl.ingest.

Found via sibling comparison between Ingest._init_neighbors's three branches and _get_pca_or_small_x (the function that actually computes pp.neighbors' representation) — the first two branches already match the stored params correctly; only the fallback diverged.

Repro
import anndata as ad, numpy as np, scanpy as sc
X = np.random.default_rng(0).normal(size=(300, 200)).astype("float32")
adata_ref = ad.AnnData(X)
sc.pp.pca(adata_ref, n_comps=100)
sc.pp.neighbors(adata_ref)  # default n_pcs=None -> uses all 100 PCs
ing = sc.tl.Ingest(adata_ref)
ing._init_neighbors(adata_ref, None)
print(ing._n_pcs)  # 50 before this fix, should be 100
AI assistance
  • Tool: Claude Code
  • Role: found the inconsistency by comparing Ingest's three _init_neighbors branches against _get_pca_or_small_x's actual default behavior, then wrote the fix and regression test
  • Verified locally against a synthetic reproduction (the repo's pbmc68k_reduced test fixture hits an unrelated zarr-version incompatibility in this environment, so the new test couldn't be run through the full suite here — logic was verified directly against the same sc.pp.pca/sc.pp.neighbors/sc.tl.Ingest calls the test uses)
Test results

Added test_representation_more_than_n_pcs_default to tests/test_ingest.py, covering n_comps = settings.N_PCS + 20. Manually verified the underlying fix across n_comps of 30, 50, and 100 against a synthetic AnnData (below, below+at, and above the settings.N_PCS threshold) — all three now correctly match the PCs pp.neighbors actually used, whereas 100 previously and incorrectly truncated to 50.

When pp.neighbors runs with its own n_pcs=None default, it uses every
stored PCA component unsliced (_get_pca_or_small_x). Ingest's
fallback branch for "no use_rep/n_pcs recorded" instead hardcoded
settings.N_PCS (50), silently using a different, truncated
representation than the one that actually produced the reference
neighbor graph whenever more than 50 PCs were computed (e.g.
sc.pp.pca(adata, n_comps=100) followed by sc.pp.neighbors(adata)).

Fixes it by using the full stored X_pca, matching pp.neighbors'
actual default behavior instead of assuming it always caps at
settings.N_PCS.
Copilot AI balanced review requested due to automatic review settings October 7, 2026 02:54

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.78%. Comparing base (7ad567d) to head (52bf151).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4401   +/-   ##
=======================================
  Coverage   83.78%   83.78%           
=======================================
  Files         134      134           
  Lines       12691    12691           
=======================================
  Hits        10633    10633           
  Misses       2058     2058           
Flag Coverage Δ
hatch-test.low-vers 80.49% <100.00%> (ø)
hatch-test.pre 83.58% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/scanpy/tools/_ingest.py 85.47% <100.00%> (ø)

This branch has not been deployed

No deployments
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