scverse / scverse/scanpy

Add support for backed mode in calculate_qc_metrics

Open
#3,464 2 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.