Add support for backed mode in calculate_qc_metrics
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.6k
- Forks
- 779
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 27
Description
What kind of feature would you like to request?
Additional function parameters / changed functionality / changed defaults?
Please describe your wishes
Currently, running calculate_qc_metrics on anndata objects loaded with the backed mode fails, as the .X object is of CSRDataset: backend hdf5 type, which the function does not handle. The qc function expects the whole X layer to be loaded in memory, which is not necessary for the operations it does (getting sum and non-zero count of rows/columns).
I have made a (perhaps not so great) workaround, where the function reads through the matrix in "chunked" mode, and then concatenates the results to produce final qc metrics for the whole dataset. The chunksize used was arbitrarily chosen.
Perhaps a person more acquainted with the codebase of scanpy can produce a better version of this, but given the simpicity of computations involved in the function, I think supporting backed mode should be available.
def calculate_qc_metrics(
adata: AnnData,
*,
expr_type: str = "counts",
var_type: str = "genes",
qc_vars: Collection[str] | str = (),
percent_top: Collection[int] | None = (50, 100, 200, 500),
layer: str | None = None,
use_raw: bool = False,
inplace: bool = False,
log1p: bool = True,
parallel: bool | None = None,
) -> tuple[pd.DataFrame, pd.DataFrame] | None:
if parallel is not None:
warn(
"Argument `parallel` is deprecated, and currently has no effect.",
FutureWarning,
)
# Pass X so I only have to do it once
X = _choose_mtx_rep(adata, use_raw=use_raw, layer=layer)
if isspmatrix_coo(X):
X = csr_matrix(X) # COO not subscriptable
if issparse(X):
X.eliminate_zeros()
# Convert qc_vars to list if str
if isinstance(qc_vars, str):
qc_vars = [qc_vars]
chunk_size = 1000000
obs_metrics = []
for i in range(0, min(chunk_size, X.shape[0]), chunk_size):
res = describe_obs(
adata[i : min(i + chunk_size, X.shape[0])],
expr_type=expr_type,
var_type=var_type,
qc_vars=qc_vars,
percent_top=percent_top,
inplace=False,
X=X[i : min(i + chunk_size, X.shape[0])],
log1p=log1p,
)
if inplace:
adata.obs.loc[res.index, res.columns] = res
else:
obs_metrics.append(res)
var_metrics = []
for i in range(0, min(chunk_size, X.shape[1]), chunk_size):
res = describe_var(
adata[:, i : min(i + chunk_size, X.shape[1])],
expr_type=expr_type,
var_type=var_type,
inplace=False,
X=X[:, i : min(i + chunk_size, X.shape[1])],
log1p=log1p,
)
if inplace:
adata.var.loc[res.index, res.columns] = res
else:
var_metrics.append(res)
if not inplace:
return pd.concat(obs_metrics, copy=False), pd.concat(var_metrics, copy=False)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at calculate_qc_metrics and inspect how _choose_mtx_rep, CSRDataset, and the describe_obs/describe_var calls handle matrix slicing. Compare the proposed chunked approach with the current in-memory path, then verify that backed AnnData objects produce complete observation and variable metrics without requiring the whole X matrix in memory.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100