Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/4440.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix {func}`~scanpy.pp.calculate_qc_metrics` for sparse matrices of small integer dtypes, which gave negative `pct_counts_in_top_N_genes` (e.g. `int16`) or raised (e.g. `uint16`) {smaller}`P Paladugu`
4 changes: 2 additions & 2 deletions src/scanpy/preprocessing/_qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ def top_segment_proportions_sparse_csr(data, indptr, ns):
ns = ns.astype(np.int64)
ns = np.sort(ns)
maxidx = ns[-1]
sums = np.zeros((indptr.size - 1), dtype=data.dtype)
sums = np.zeros((indptr.size - 1), dtype=np.float64)
values = np.zeros((indptr.size - 1, len(ns)), dtype=np.float64)
# Just to keep it simple, as a dense matrix
partitioned = np.zeros((indptr.size - 1, maxidx), dtype=data.dtype)
Expand All @@ -447,7 +447,7 @@ def top_segment_proportions_sparse_csr(data, indptr, ns):
partitioned[i, :] = -(np.partition(-data[start:end], maxidx))[:maxidx]
partitioned[i, :] = np.partition(partitioned[i, :], maxidx - ns)
partitioned = partitioned[:, ::-1][:, : ns[-1]]
acc = np.zeros((indptr.size - 1), dtype=data.dtype)
acc = np.zeros((indptr.size - 1), dtype=np.float64)
prev = 0
# can’t use enumerate due to https://github.com/numba/numba/issues/2625
for j in range(ns.size):
Expand Down
19 changes: 19 additions & 0 deletions tests/test_qc_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ def test_top_proportions(request: pytest.FixtureRequest, array_type):
assert (segfull == propfull).all()


@pytest.mark.parametrize(
"dtype",
[np.int8, np.int16, np.uint8, np.uint16, np.uint32],
ids=lambda d: d.__name__,
)
def test_top_segments_sparse_small_int(dtype):
"""Row sums must not overflow or fail for small integer dtypes."""
dense = (
np.random
.default_rng(0)
.integers(1, min(np.iinfo(dtype).max, 1000), (3, 60))
.astype(dtype)
)
ns = np.array([5, 10])
expected = top_segment_proportions(dense, ns)
result = top_segment_proportions(sparse.csr_matrix(dense), ns) # noqa: TID251
np.testing.assert_allclose(result, expected)


# While many of these are trivial,
# they’re also just making sure the metrics are there
def test_qc_metrics(adata_prepared: AnnData):
Expand Down
Loading