Scanpy: use of layers

Current behavior

When working with AnnData.layers, applying a preprocessing transformation and storing the result in a new layer currently requires copying the source layer first and then modifying that copy in place.

For example, creating a normalized-counts layer requires:

adata.layers["normalized_counts"] = (
    adata.layers["counts"].copy()
)
sc.pp.normalize_total(
    adata,
    layer="normalized_counts",
)

This works, but IMHO is excessively verbose and thus confusing.

Basically, the layer argument simultaneously identifies the input matrix and the matrix that will be modified. As a result, creating a new derived layer requires two separate operations:

  1. Copy the source layer manually.
  2. Run the transformation on the copied layer.

The current inplace=False behavior does not fully solve this ergonomically because it returns a dictionary rather than the transformed matrix itself:

result = sc.pp.normalize_total(
    pca_adata,
    layer="counts",
    inplace=False,
)

pca_adata.layers["normalized_counts"] = result["X"]

I think this is still more cumbersome than necessary, and the "X" key is confusing when the input came from a named layer rather than from adata.X.

Suggested behavior

I think a more natural API would allow the transformed matrix to be returned directly:

# Preserve raw counts and create a normalized-expression layer
pca_adata.layers["normalized_counts"] = sc.pp.normalize_total(
    pca_adata,
    layer="counts",
)

or have an explicit output-layer argument:

sc.pp.normalize_total(
    pca_adata,
    layer="counts",
    output_layer="normalized_counts",
)

This would make the data flow explicit:

counts layer → normalization → normalized_counts layer

What do you think? Am I overlooking an existing way to work exclusively with named layers, without relying on .X or making the code unnecessarily verbose?

or have an explicit output-layer argument:

If I am not mistaken, this will be scanpy 2.0 behavior, which means it will be coming in the next minor release as a preview. @flying-sheep Can you confirm this? I think the plan is to do this with accessors

Nice! Is there a epic or meta issue I can subscribe to in order to follow the development of this new feature? I searched on the GitHub repo but couldn’t find any

That would be this one! `anndata.acc` based API · Issue #4007 · scverse/scanpy · GitHub

1 Like