Repository navigation
feat: switch to use and out - #4359
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4359 +/- ##
=========================================
+ Coverage 0 85.05% +85.05%
=========================================
Files 0 138 +138
Lines 0 12263 +12263
=========================================
+ Hits 0 10430 +10430
- Misses 0 1833 +1833
Flags with carried forward coverage won't be shown. Click here to find out more.
|
If we want to discourage use of
|
|
Yeah, I think |
|
Or sentinels, e.g. |
|
Yeah, an enum would of course be clearest, but I think the bool is intuitive enough, don’t you think? I currently left the default to be
|
I like that, this would establish some conventions |
|
Yeah! I’ll meet with Ilan today about this, and we’ll probably hash something out. I intentionally left it out of this PR so far since the design is still changing, so I don’t want to muddle the discussion with bikeshedding about which conventional names to settle on. |
|
Plan:
|
|
OK, multi-out is blocked on not having Storing accessors for e.g. |
ilan-gold
left a comment
There was a problem hiding this comment.
Got through _simple.py - my main two things are use_rep's deprecation (could punt on this) and whether or not the plan is to store accessors in uns right now. The current state of things has that, it seems, but I thought that that was not a goal.
|
@selmanozleyen any insights from your return value efforts in squidpy? |
|
I will share share some things that are no go to me, some edge cases and in the end my opinion on this. No go'sI am really against this kind of behaviour:
For example what would this mean then? ( also I don't think this is an edge case, it might happen to someone) sc.pp.neighbors(ad) # obsp connectivities/distances, uns["neighbors"]
sc.pp.neighbors(ad, n_neighbors=5, out="obsp.distances")Before I share my opinions here are some edge cases we might need to think about: other edge cases1- same ref's to different keys. Do we allow? sc.pp.neighbors(ad, out={"connectivities": A.obsp["x"], "distances": A.obsp["x"]}) 2- a stupid case sc.pp.pca(ad); sc.pp.neighbors(ad, out="obsp.pca")
# primary outputs not overwritten but the uns are
# this particular example is unlikely but it implies a rule we'd need to follow
# whenever there is sc.pp.neighbors(ad, out="obsp.x") we'd need to ensure there is no function x since
# it would write to uns["x"] as sc.pp.neighbours would do. I.e., by default there is an implicit write here that might cause complicationsmy opinionI would treat But out should be more clear and explicit imo. If one key is given other's shouldn't be written explicitly with it. I don't think it's worse UX if it prevents bugs that the old UX tricked the users into not noticing those bugs. another pointwhy don't we use typed dicts for out mappings? It would give nice autocomplete it's extra code but it's a plus on the UX and typing I'd say. |
Agreed, there should be no magic strings.
no, makes no sense. good catch for user-friendliness.
that was the motivation behind the initial design with I really don’t know if there’s an ideal fix, both options here have their trade-offs. If someone has a better idea today I’ll do it, but if nobody comes forth, I can’t wait forever.
no reason. could just live next to the |
|
all done.
|
|
Are there any constraints on out? For instance, let's take
|
|
It’s currently a bit overconstrained (mainly around
yup, that works now as well.
Pretty sure that not, I’m checking that the dimensions match in many cases (but might have missed a spot). What will be enabled in the future though is doing e.g. |
|
I can agree with @grst that you want to discourage people from using |
* Adopt scanpy's use/out parameters in pp, tl and get Match scanpy 1.13 (scverse/scanpy#4359): `use` names where a function reads from (an anndata.acc accessor or its string form) and `out` where it writes (False for the default place, True to return the result, an accessor, or a mapping for functions with several outputs). Functions with several outputs return a ...Result NamedTuple for out=True and take a ...Out TypedDict. Groupings and covariates take refs such as A.obs["batch"]. Deprecate layer, obsm, use_rep, use_raw, neighbors_key, obsp, adjacency, key_added, batch_key, inplace and copy as scanpy does; filter_cells and filter_genes keep copy. Signed-off-by: Intron7 <sdicks@nvidia.com> * Add PR number to release notes Signed-off-by: Intron7 <sdicks@nvidia.com> * Fix docs build for the use/out output types Signed-off-by: Intron7 <sdicks@nvidia.com> * Match scanpy's calculate_qc_metrics preset default and umap rep Signed-off-by: Intron7 <sdicks@nvidia.com> * Fix scanpy-main compat and sparse score_genes with duplicate var_names Signed-off-by: Intron7 <sdicks@nvidia.com> * Explain use/out and the calculate_qc_metrics default in the skill Signed-off-by: Intron7 <sdicks@nvidia.com> * Rename mask_var/mask_obs to mask in pca, as scanpy does Port scverse/scanpy#4333: pp.pca takes `mask` with anndata.acc references and deprecates `mask_var`; pca, scale and rank_genes_groups share one `_mask_arg`, the PCA mask is stored in `.uns` as scanpy stores it, and ingest reads that form. Signed-off-by: Intron7 <sdicks@nvidia.com> * Fix review findings for use/out - normalize_total: norm_factor on sparse and dense Dask input; results not written back to their source no longer modify it - anndata 0.12/0.13.0-0.13.2: guard the anndata.acc imports and resolve(vec=...), with a compat test that hides anndata.acc - scale(inplace=False) returns the matrix again - regress_out: ndarray/Index keys and vectors held on the GPU - normalize_shifted_clr(inplace=False) rejects a conflicting out - use=A.obsm["X"] no longer falls back to adata.X - embedding_density: reject non-obsm use and non-categorical groupby for out=True - mixscape: the PCA recompute rewrites uns as well - HVG and scrublet with use= no longer rebuild obs_names or copy the whole AnnData - release notes for the normalize_total, neighbors/bbknn and draw_graph alignments with scanpy Signed-off-by: Intron7 <sdicks@nvidia.com> * Test harmony_integrate with a list of batch accessors Signed-off-by: Intron7 <sdicks@nvidia.com> --------- Signed-off-by: Intron7 <sdicks@nvidia.com>
Note
since people are still providing input, this PR is pretty vibe-coded at the moment. I’ll go over it in detail once everything is hashed out.
Current design:
inplaceeverywhere,copyalmost everywhere:pp.sample,pp.subsample,pp.filter_cellsandpp.filter_genessubset the object (they change its shape).use(where to read from) andout(where to write to).out=False(default) writes to the default place (for matrix transforms: back touse),out=Truereturns the result instead of writing it, and an accessor writes there.strinuse/outis alwaysA.resolved (unless the v1 preset is set and the parameter took strings with another meaning in scanpy 1.12.x). Nosc.Prefix, and no guessing the meaning from the string’s content.k, and the others follow it:kin kinds of slots no other output goes to,k_{name}where several share a kind. Outputs that aren’t written by default stay unwritten. A mapping (out={"connectivities": A.obsp["c"]}) writes only what it names,out=Truereturns aNamedTuple(PcaOut,NeighborsOut, …). Naming the default places is identical toout=False. Functions without a primary output (scrublet,score_genes_cell_cycle,dpt) only take a mapping.usetakes what was passed to the producer’sout. Consumers of neighbors take the graph (use=A.obsp["nn"]), replacingneighbors_key/obsp/adjacency; distances and params are found wherepp.neighborsstored them next to it.umaponly needs data to lay out more than2 * n_componentsconnected components, and then asks for it explicitly (use=A.obsp["connectivities"], rep=A.obsm["pca"]) instead of silently guessing.batch,covariates, andgroupby/groupsacceptA.obs["x"]..uns:rank_genes_groups,filter_rank_genes_groups,dendrogram,paga) getout: booland keepkey_added; their readers keepkey. These switch toA.uns[k]once anndata has.unsaccessors.experimental.pp.normalize_pearson_residuals_pcaandrecipe_pearson_residuals(out=Truereplacesinplace=False; PCA destination viakwargs_pca)normalize_pearson_residuals_pcaandrecipe_pearson_residualsreturn an AnnData object and otherwise write to .unsExamples
Example for how this improves code:
Blocked on anndata (follow-ups)
.unsaccessors (A.unsaccessors anndata#2657):out=A.uns[k]for.uns-only functionsobsm/varmDataFrames: writing several outputs as one frame (out=A.obsm["cc"]), and chaining their columnsusein.unsparams instead ofuse_rep/layerstringsanndata.acc: accessor-only internals, and decorators replacing the inline legacy-parameter handlingFollow up after this:
Follow-ups once anndata ships …
…
.unsaccessors (scverse/anndata#2657)out=A.uns[k]forrank_genes_groups,filter_rank_genes_groups,dendrogram,paga; their readers takeuse=A.uns[k]. Deprecatekey_added/keythere. (See “Accessors stored in.uns” below forpp.scrublet,tl.score_genes_cell_cycleandtl.dpt)ingest(use=A.uns[k])for the neighbors model; deprecateneighbors_key.normalize_pearson_residuals_pcaandrecipe_pearson_residualshaveout: booland return AnnData. ChoosingA.uns[...]should write an AnnData there?paramsa real output field in the…Outtuples, and decide whetherout=Truereturns it.groupby/groupsofrank_genes_groups,filter_rank_genes_groups,dendrogram,embedding_densityandpagatake any obs-aligned vector (e.g.A.obsm["x"][:, 0]), not only.obscolumns: store the accessor in params so readers (pl.rank_genes_groups_*,pl.dendrogram,pl.paga) can fetch it. Delete_obs_col.… references into DataFrames in
obsm/varm(and scverse/anndata#2722)score_genes_cell_cycle,scrublet,dptandhvg(both) write a single frame toobsm[k]/varm[k]; this also brings back hvg'sout.groupby,covariates,maskandcoloraccept frame columns.pca(mask=…), cell cycle →regress_out, qc →color=A.obsm["qc"]["total_counts"].… Accessors stored in
.uns(scverse/anndata#2657)useaccessors in params; store accessors inNeighborsDict. Delete_use_to_uns/_use_from_uns/_ref_to_uns, keeping a reader for the old format.pp.scrublet,tl.score_genes_cell_cycleandtl.dptwill allowout=A.uns[k]to store accessors in there (see “.unsaccessors” above)Follow-up once minimum anndata ≥ 0.13.3
_dest,_SetRep,_Repand the**choicesbranches.@legacy_use/@legacy_outdecorators to replace the inline handling of old parameters; this is the PR's "decorators" TODO.own_deprecations()call sites.Separate PRs
log1pwriting tolayers["log1p"]as grst suggested.rank_genes_groups; not blocked on anndata)sc.pp.hvg.{seurat,cell_ranger,seurat_v3,pearson_residuals}with flavor-specific parameters. Keephighly_variable_genes(flavor=…)as a dispatcher.…Outtuple, with a shared helper for the batch fields.outwith an accessor (out=A.var["hv"]writeshv_*columns anduns["hv"]), a mapping, andout=Truereturning the tuple.pl.highly_variable_genesitsuseback.Notes
anndata.accreplacement for functions takingobsm,layer, … #4334, closes Remove inplace/copy #2583, closes don’t return dicts #17