Skip to content

Rank refactor 1#4118

Draft
zboldyga wants to merge 9 commits into
scverse:ig/illicofrom
zboldyga:rank-refactor-1
Draft

Rank refactor 1#4118
zboldyga wants to merge 9 commits into
scverse:ig/illicofrom
zboldyga:rank-refactor-1

Conversation

@zboldyga
Copy link
Copy Markdown
Contributor

  • Closes #
  • Tests included or not required because:

@zboldyga
Copy link
Copy Markdown
Contributor Author

zboldyga commented May 12, 2026

@ilan-gold here's a proof of concept for the stats speedup.

The stats are the biggest performance improvement remaining on the scanpy side of the illico integration -- here's the total scanpy illico time before vs. after the patch.

Note that this is only relevant to vs_rest mode (all other cells). Using an individual group as a reference was already fine.

vs_rest

dataset n_cells n_groups n_genes original (s) latest (s) speedup
adamson16 62,673 89 20,616 65.38 30.31 2.16×
wessels23 30,636 184 16,775 48.56 15.33 3.17×
sunshine23 38,431 194 23,570 69.19 21.60 3.20×
norman19 111,545 238 22,608 224.67 47.98 4.68×
nadig25hepg2 133,757 1,819 9,623 1,838.24 72.96 25.20×
replogle22k562 308,646 1,972 8,563 4,025.46 137.68 29.24×

I used aggregate as you mentioned.

That said, two additional points:

  1. You'll notice that I preserved some prior stats computation logic for the t tests in: _compute_rest_stats_for_t_test , which means we now have different computations for t test and wilcoxon . There are two issues with significant digits / numerical stability that prevent changes to this without changing the outputs:
    -- using 'aggregate' has a variance calculation that is numerically unstable, e.g. it can suffer from significant digit cancellation
    -- X[~self.groups_masks_obs[g]] is inefficient, but the alternative used for the sped-up variant for wilcoxon is numerically unstable for the variance computation needed for t test.

There's a better algorithm for calculating variance that doesn't have these issues: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm . I did an implementation of this (didn't commit), and in my initial tests it was roughly the same speed as this current approach using 'aggregate' (possibly 20% faster but too early to say). I would need to think more carefully about where this fits in scanpy, e.g. it might be best as a util in get alongside aggregate, or a replacement for aggregate. So that in itself is a separate issue, perhaps it needs to be addressed before we can finish this basic stats speedup work... e.g. with that in place, I can simplify this code a bit more, and we avoid numerical stability issues.

(note that this issue already exist in 'aggregate').

  1. The _RankGenes class uses side effects and it makes the code complex (e.g. functions that act on the class's self objects, it's unclear what functions require/return). I think it would be helpful to refactor this to a more pure functional approach, which also seems in line with other scanpy code I reviewed. It should be straightforward, but I wanted to check with you whether this is something you'd accept, and when and off which branch to implement this?

Thoughts on these 2 points and the current PR?

@ilan-gold
Copy link
Copy Markdown
Contributor

So to your points

  1. I would wholesale replace the variance calculation if possible in aggregate. There's a comment in the dask implementation about how strangely unstable the variance calculation can be so presumably that is less a dask thing than a "data big enough to require dask" thing i.e, triggering instability due to more summation ops, in which case, I think the calculation can be considered "wrong" and replaced. The online algorithm looks to be compatible with a single-pass approach to calculating mean at the same time i.e., only one data access and reuse the value. I would just replace our current sparse numba kernel for mean var calculation with one that does this. That seems possible, right? Does this appear in dense data as well? Presumably.
  2. Yeah, this was a separate issue and predates me. I specifically didn't open that can of worms in my other PR but yes, I think we would absolutely take a refactor of this.

zboldyga and others added 2 commits May 13, 2026 18:21
With the cast-to-float64 fix in aggregate's variance computation
(arriving via main rebase from PR aggregate-welford), three
workarounds in _rank_genes_groups can go:

  1. The float32 cast-back in _aggregate_group_stats that downgraded
     aggregate's float64 output to match legacy mean_var precision.
  2. The _compute_rest_stats_for_t_test slow path that recomputed
     vars_rest via direct mean_var(X[~mask]) because the previous
     sum-decomp couldn't produce accurate-enough values.
  3. The previously zero-initialized vars_rest in _derive_rest_stats
     is now computed via sum-decomp from group/global totals, with a
     max(var, 0) clamp for the all-values-equal cancellation edge
     case (mirrors the band-aid in Aggregate.mean_var).

Net effect: dropped ~25 lines of dead workaround, simpler control
flow in compute_statistics. Existing 70 tests pass (224 subtests).

One test-tolerance bump: added atol=1e-10 alongside the existing
rtol=1e-5 in test_results' score assertion. The new code produces
sub-machine-precision noise (~1e-15) at a position where the legacy
path produced exact 0.0 (one gene where group and rest means are
equal). Both represent the same mathematical zero; atol accepts both
without weakening the non-zero-score tolerance.

This commit assumes the kernel fix is in scanpy main. Until that
merges, this branch's CI may fail; rebase on main pulls in the fix.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

❌ 5 Tests Failed:

Tests completed Failed Passed Skipped
1884 5 1879 941
View the top 3 failed test(s) by shortest run time
tests/test_readwrite.py::test_write_strings_to_cats[s2c-zarr]
Stack Traces | 0.008s run time
fmt = 'zarr'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(#x1B[90m#x1B[39;49;00m
        pkg_version(#x1B[33m"#x1B[39;49;00m#x1B[33manndata#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m) < Version(#x1B[33m"#x1B[39;49;00m#x1B[33m0.11.0rc2#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        reason=#x1B[33m"#x1B[39;49;00m#x1B[33mOlder AnnData has no convert_strings_to_categoricals#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mfmt#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, pytest.param(#x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, marks=needs.zarr)])#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33ms2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m], ids=[#x1B[33m"#x1B[39;49;00m#x1B[33ms2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mno_s2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_write_strings_to_cats#x1B[39;49;00m(fmt: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], *, s2c: #x1B[96mbool#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        adata = AnnData(np.array([[#x1B[94m1#x1B[39;49;00m, #x1B[94m2#x1B[39;49;00m], [#x1B[94m3#x1B[39;49;00m, #x1B[94m4#x1B[39;49;00m], [#x1B[94m5#x1B[39;49;00m, #x1B[94m6#x1B[39;49;00m]]), obs=#x1B[96mdict#x1B[39;49;00m(a=[#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       sc.write(#x1B[33m"#x1B[39;49;00m#x1B[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, convert_strings_to_categoricals=s2c, ext=fmt)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_readwrite.py#x1B[0m:110: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/scanpy/readwrite.py#x1B[0m:748: in write
    #x1B[0mwrite_zarr(filename, adata, **extra_kw)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:349: in raise_error_if_dataset_2d_present
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m write(store, adata, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:59: in write_zarr
    #x1B[0mwrite_dispatched(f, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, callback=callback, dataset_kwargs=ds_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/experimental/_dispatch_io.py#x1B[0m:74: in write_dispatched
    #x1B[0mwriter.write_elem(store, key, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:359: in write_anndata
    #x1B[0m_writer.write_elem(g, #x1B[33m"#x1B[39;49;00m#x1B[33mX#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata.X, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:332: in func_wrapper
    #x1B[0mfunc(f, k, elem, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:519: in write_basic
    #x1B[0m#x1B[94mwith#x1B[39;49;00m zarr_v3_sharding(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../...../_temp/uv-python-dir/cpython-3.14.5-linux-x86_64-gnu/lib/python3.14/contextlib.py#x1B[0m:141: in __enter__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mnext#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.gen)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

dataset_kwargs = {}, format = 3

    #x1B[0m#x1B[37m@contextmanager#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mzarr_v3_sharding#x1B[39;49;00m(dataset_kwargs: #x1B[96mdict#x1B[39;49;00m, #x1B[96mformat#x1B[39;49;00m: Literal[#x1B[94m2#x1B[39;49;00m, #x1B[94m3#x1B[39;49;00m]) -> Generator[#x1B[96mdict#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
        auto_sharding = (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mshards#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[95min#x1B[39;49;00m dataset_kwargs#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m ad.settings.auto_shard_zarr_v3#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m ad.settings.auto_shard_zarr_v3 #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           warnings.warn(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mzarr v3 autosharding will be the default in the next minor release.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[96mUserWarning#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                stacklevel=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               UserWarning: zarr v3 autosharding will be the default in the next minor release.#x1B[0m
#x1B[1m#x1B[31mE               Error raised while writing key 'X' of <class 'zarr.core.group.Group'> to /#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:129: UserWarning
tests/test_readwrite.py::test_write[default-zarr]
Stack Traces | 0.009s run time
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7fcc6819e200>
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_write_default_zarr_0')
ext = 'zarr', style = 'default'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, pytest.param(#x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, marks=needs.zarr), #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mstyle#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_write#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        monkeypatch: pytest.MonkeyPatch,#x1B[90m#x1B[39;49;00m
        tmp_path: Path,#x1B[90m#x1B[39;49;00m
        ext: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        style: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        monkeypatch.chdir(tmp_path)#x1B[90m#x1B[39;49;00m
        adata = AnnData(np.array([[#x1B[94m1#x1B[39;49;00m, #x1B[94m2#x1B[39;49;00m], [#x1B[94m3#x1B[39;49;00m, #x1B[94m4#x1B[39;49;00m]]))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# test that writing works (except style="default" and ext="csv")#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        ctx = (#x1B[90m#x1B[39;49;00m
            pytest.warns(#x1B[96mFutureWarning#x1B[39;49;00m, match=#x1B[33mr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mremoved from this function#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m ext == #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m nullcontext()#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mmatch#x1B[39;49;00m style, ext:#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
                    sc.write(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtest.#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mext#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata)#x1B[90m#x1B[39;49;00m
                d = tmp_path#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
                    sc.write(#x1B[33m"#x1B[39;49;00m#x1B[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, ext=ext)#x1B[90m#x1B[39;49;00m
                d = sc.settings.writedir#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# check that it throws an error instead#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                ff = sc.settings.file_format_data#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m pytest.raises(#x1B[96mValueError#x1B[39;49;00m, match=#x1B[33mr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mCannot set file_format_data to csv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                    sc.settings.file_format_data = ext  #x1B[90m# type: ignore[assignment]#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94massert#x1B[39;49;00m sc.settings.file_format_data == ff#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m  #x1B[90m# return early#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                sc.settings.file_format_data, old = ext, sc.settings.file_format_data#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
>                       sc.write(#x1B[33m"#x1B[39;49;00m#x1B[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../scanpy/tests/test_readwrite.py#x1B[0m:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../src/scanpy/readwrite.py#x1B[0m:748: in write
    #x1B[0mwrite_zarr(filename, adata, **extra_kw)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:349: in raise_error_if_dataset_2d_present
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m write(store, adata, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:59: in write_zarr
    #x1B[0mwrite_dispatched(f, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, callback=callback, dataset_kwargs=ds_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/experimental/_dispatch_io.py#x1B[0m:74: in write_dispatched
    #x1B[0mwriter.write_elem(store, key, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:359: in write_anndata
    #x1B[0m_writer.write_elem(g, #x1B[33m"#x1B[39;49;00m#x1B[33mX#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata.X, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:332: in func_wrapper
    #x1B[0mfunc(f, k, elem, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:519: in write_basic
    #x1B[0m#x1B[94mwith#x1B[39;49;00m zarr_v3_sharding(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../_temp/uv-python-dir/cpython-3.14.5-linux-x86_64-gnu/lib/python3.14/contextlib.py#x1B[0m:141: in __enter__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mnext#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.gen)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

dataset_kwargs = {}, format = 3

    #x1B[0m#x1B[37m@contextmanager#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mzarr_v3_sharding#x1B[39;49;00m(dataset_kwargs: #x1B[96mdict#x1B[39;49;00m, #x1B[96mformat#x1B[39;49;00m: Literal[#x1B[94m2#x1B[39;49;00m, #x1B[94m3#x1B[39;49;00m]) -> Generator[#x1B[96mdict#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
        auto_sharding = (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mshards#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[95min#x1B[39;49;00m dataset_kwargs#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m ad.settings.auto_shard_zarr_v3#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m ad.settings.auto_shard_zarr_v3 #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           warnings.warn(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mzarr v3 autosharding will be the default in the next minor release.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[96mUserWarning#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                stacklevel=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               UserWarning: zarr v3 autosharding will be the default in the next minor release.#x1B[0m
#x1B[1m#x1B[31mE               Error raised while writing key 'X' of <class 'zarr.core.group.Group'> to /#x1B[0m

#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:129: UserWarning
tests/test_readwrite.py::test_write[ext-zarr]
Stack Traces | 0.009s run time
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7fcc68173380>
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_write_ext_zarr_0')
ext = 'zarr', style = 'ext'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, pytest.param(#x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, marks=needs.zarr), #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mstyle#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_write#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        monkeypatch: pytest.MonkeyPatch,#x1B[90m#x1B[39;49;00m
        tmp_path: Path,#x1B[90m#x1B[39;49;00m
        ext: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        style: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        monkeypatch.chdir(tmp_path)#x1B[90m#x1B[39;49;00m
        adata = AnnData(np.array([[#x1B[94m1#x1B[39;49;00m, #x1B[94m2#x1B[39;49;00m], [#x1B[94m3#x1B[39;49;00m, #x1B[94m4#x1B[39;49;00m]]))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# test that writing works (except style="default" and ext="csv")#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        ctx = (#x1B[90m#x1B[39;49;00m
            pytest.warns(#x1B[96mFutureWarning#x1B[39;49;00m, match=#x1B[33mr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mremoved from this function#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m ext == #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m nullcontext()#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mmatch#x1B[39;49;00m style, ext:#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
                    sc.write(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtest.#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mext#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata)#x1B[90m#x1B[39;49;00m
                d = tmp_path#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
>                   sc.write(#x1B[33m"#x1B[39;49;00m#x1B[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, ext=ext)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../scanpy/tests/test_readwrite.py#x1B[0m:71: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../src/scanpy/readwrite.py#x1B[0m:748: in write
    #x1B[0mwrite_zarr(filename, adata, **extra_kw)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:349: in raise_error_if_dataset_2d_present
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m write(store, adata, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:59: in write_zarr
    #x1B[0mwrite_dispatched(f, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, callback=callback, dataset_kwargs=ds_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/experimental/_dispatch_io.py#x1B[0m:74: in write_dispatched
    #x1B[0mwriter.write_elem(store, key, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:359: in write_anndata
    #x1B[0m_writer.write_elem(g, #x1B[33m"#x1B[39;49;00m#x1B[33mX#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata.X, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:332: in func_wrapper
    #x1B[0mfunc(f, k, elem, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:519: in write_basic
    #x1B[0m#x1B[94mwith#x1B[39;49;00m zarr_v3_sharding(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../_temp/uv-python-dir/cpython-3.14.5-linux-x86_64-gnu/lib/python3.14/contextlib.py#x1B[0m:141: in __enter__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mnext#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.gen)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

dataset_kwargs = {}, format = 3

    #x1B[0m#x1B[37m@contextmanager#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mzarr_v3_sharding#x1B[39;49;00m(dataset_kwargs: #x1B[96mdict#x1B[39;49;00m, #x1B[96mformat#x1B[39;49;00m: Literal[#x1B[94m2#x1B[39;49;00m, #x1B[94m3#x1B[39;49;00m]) -> Generator[#x1B[96mdict#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
        auto_sharding = (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mshards#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[95min#x1B[39;49;00m dataset_kwargs#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m ad.settings.auto_shard_zarr_v3#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m ad.settings.auto_shard_zarr_v3 #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           warnings.warn(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mzarr v3 autosharding will be the default in the next minor release.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[96mUserWarning#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                stacklevel=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               UserWarning: zarr v3 autosharding will be the default in the next minor release.#x1B[0m
#x1B[1m#x1B[31mE               Error raised while writing key 'X' of <class 'zarr.core.group.Group'> to /#x1B[0m

#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:129: UserWarning
tests/test_readwrite.py::test_write_strings_to_cats[no_s2c-zarr]
Stack Traces | 0.009s run time
fmt = 'zarr'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(#x1B[90m#x1B[39;49;00m
        pkg_version(#x1B[33m"#x1B[39;49;00m#x1B[33manndata#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m) < Version(#x1B[33m"#x1B[39;49;00m#x1B[33m0.11.0rc2#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        reason=#x1B[33m"#x1B[39;49;00m#x1B[33mOlder AnnData has no convert_strings_to_categoricals#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mfmt#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, pytest.param(#x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, marks=needs.zarr)])#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33ms2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m], ids=[#x1B[33m"#x1B[39;49;00m#x1B[33ms2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mno_s2c#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_write_strings_to_cats#x1B[39;49;00m(fmt: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], *, s2c: #x1B[96mbool#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        adata = AnnData(np.array([[#x1B[94m1#x1B[39;49;00m, #x1B[94m2#x1B[39;49;00m], [#x1B[94m3#x1B[39;49;00m, #x1B[94m4#x1B[39;49;00m], [#x1B[94m5#x1B[39;49;00m, #x1B[94m6#x1B[39;49;00m]]), obs=#x1B[96mdict#x1B[39;49;00m(a=[#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       sc.write(#x1B[33m"#x1B[39;49;00m#x1B[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, convert_strings_to_categoricals=s2c, ext=fmt)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_readwrite.py#x1B[0m:110: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/scanpy/readwrite.py#x1B[0m:748: in write
    #x1B[0mwrite_zarr(filename, adata, **extra_kw)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:349: in raise_error_if_dataset_2d_present
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m write(store, adata, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:59: in write_zarr
    #x1B[0mwrite_dispatched(f, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, callback=callback, dataset_kwargs=ds_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/experimental/_dispatch_io.py#x1B[0m:74: in write_dispatched
    #x1B[0mwriter.write_elem(store, key, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:359: in write_anndata
    #x1B[0m_writer.write_elem(g, #x1B[33m"#x1B[39;49;00m#x1B[33mX#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata.X, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:332: in func_wrapper
    #x1B[0mfunc(f, k, elem, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:519: in write_basic
    #x1B[0m#x1B[94mwith#x1B[39;49;00m zarr_v3_sharding(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../...../_temp/uv-python-dir/cpython-3.14.5-linux-x86_64-gnu/lib/python3.14/contextlib.py#x1B[0m:141: in __enter__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mnext#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.gen)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

dataset_kwargs = {}, format = 3

    #x1B[0m#x1B[37m@contextmanager#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mzarr_v3_sharding#x1B[39;49;00m(dataset_kwargs: #x1B[96mdict#x1B[39;49;00m, #x1B[96mformat#x1B[39;49;00m: Literal[#x1B[94m2#x1B[39;49;00m, #x1B[94m3#x1B[39;49;00m]) -> Generator[#x1B[96mdict#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
        auto_sharding = (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mshards#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[95min#x1B[39;49;00m dataset_kwargs#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m ad.settings.auto_shard_zarr_v3#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m ad.settings.auto_shard_zarr_v3 #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           warnings.warn(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mzarr v3 autosharding will be the default in the next minor release.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[96mUserWarning#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                stacklevel=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               UserWarning: zarr v3 autosharding will be the default in the next minor release.#x1B[0m
#x1B[1m#x1B[31mE               Error raised while writing key 'X' of <class 'zarr.core.group.Group'> to /#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:129: UserWarning
tests/test_readwrite.py::test_write[path-zarr]
Stack Traces | 0.01s run time
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7fcc76985c50>
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_write_path_zarr_0')
ext = 'zarr', style = 'path'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, pytest.param(#x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, marks=needs.zarr), #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mstyle#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_write#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        monkeypatch: pytest.MonkeyPatch,#x1B[90m#x1B[39;49;00m
        tmp_path: Path,#x1B[90m#x1B[39;49;00m
        ext: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mh5ad#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mzarr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        style: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mext#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mdefault#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        monkeypatch.chdir(tmp_path)#x1B[90m#x1B[39;49;00m
        adata = AnnData(np.array([[#x1B[94m1#x1B[39;49;00m, #x1B[94m2#x1B[39;49;00m], [#x1B[94m3#x1B[39;49;00m, #x1B[94m4#x1B[39;49;00m]]))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# test that writing works (except style="default" and ext="csv")#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        ctx = (#x1B[90m#x1B[39;49;00m
            pytest.warns(#x1B[96mFutureWarning#x1B[39;49;00m, match=#x1B[33mr#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mremoved from this function#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m ext == #x1B[33m"#x1B[39;49;00m#x1B[33mcsv#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m nullcontext()#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mmatch#x1B[39;49;00m style, ext:#x1B[90m#x1B[39;49;00m
            #x1B[94mcase#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94m_#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mwith#x1B[39;49;00m ctx:#x1B[90m#x1B[39;49;00m
>                   sc.write(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtest.#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mext#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../scanpy/tests/test_readwrite.py#x1B[0m:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../src/scanpy/readwrite.py#x1B[0m:748: in write
    #x1B[0mwrite_zarr(filename, adata, **extra_kw)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:349: in raise_error_if_dataset_2d_present
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m write(store, adata, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:59: in write_zarr
    #x1B[0mwrite_dispatched(f, #x1B[33m"#x1B[39;49;00m#x1B[33m/#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata, callback=callback, dataset_kwargs=ds_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/experimental/_dispatch_io.py#x1B[0m:74: in write_dispatched
    #x1B[0mwriter.write_elem(store, key, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:359: in write_anndata
    #x1B[0m_writer.write_elem(g, #x1B[33m"#x1B[39;49;00m#x1B[33mX#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, adata.X, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:272: in func_wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:391: in write_elem
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.callback(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/zarr.py#x1B[0m:57: in callback
    #x1B[0mwrite_func(store, elem_name, elem, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/registry.py#x1B[0m:78: in wrapper
    #x1B[0mresult = func(g, k, *args, **kwargs)#x1B[90m#x1B[39;49;00m
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:200: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m func(f, k, val, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../anndata/_io/utils.py#x1B[0m:332: in func_wrapper
    #x1B[0mfunc(f, k, elem, _writer=_writer, dataset_kwargs=dataset_kwargs)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:519: in write_basic
    #x1B[0m#x1B[94mwith#x1B[39;49;00m zarr_v3_sharding(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../_temp/uv-python-dir/cpython-3.14.5-linux-x86_64-gnu/lib/python3.14/contextlib.py#x1B[0m:141: in __enter__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mnext#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.gen)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

dataset_kwargs = {}, format = 3

    #x1B[0m#x1B[37m@contextmanager#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mzarr_v3_sharding#x1B[39;49;00m(dataset_kwargs: #x1B[96mdict#x1B[39;49;00m, #x1B[96mformat#x1B[39;49;00m: Literal[#x1B[94m2#x1B[39;49;00m, #x1B[94m3#x1B[39;49;00m]) -> Generator[#x1B[96mdict#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
        auto_sharding = (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mshards#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[95min#x1B[39;49;00m dataset_kwargs#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m ad.settings.auto_shard_zarr_v3#x1B[90m#x1B[39;49;00m
            #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m ad.settings.auto_shard_zarr_v3 #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m #x1B[96mformat#x1B[39;49;00m == #x1B[94m3#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           warnings.warn(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mzarr v3 autosharding will be the default in the next minor release.#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[96mUserWarning#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                stacklevel=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               UserWarning: zarr v3 autosharding will be the default in the next minor release.#x1B[0m
#x1B[1m#x1B[31mE               Error raised while writing key 'X' of <class 'zarr.core.group.Group'> to /#x1B[0m

#x1B[1m#x1B[.../home/runner/.local.../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../_io/specs/methods.py#x1B[0m:129: UserWarning

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@zboldyga
Copy link
Copy Markdown
Contributor Author

zboldyga commented May 14, 2026

am still working this one, simplifying and limiting the scope properly. will push some more changes tomorrow most likely to wrap it up.

sum_total = sum_g.sum(0) if mask_all else np.asarray(X.sum(axis=0)).ravel()
self.means_rest = (sum_total - sum_g) / n_r

# TODO: if `aggregate` exposed `sum_of_squares` (an additive
Copy link
Copy Markdown
Contributor Author

@zboldyga zboldyga May 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilan-gold thoughts on this?

Basically we need to compute the variance for the 'rest' sets somehow for t test. I kept it as old code for now, to minimize surface area. So it's slow, and mean_var does suffer from the same (rare) 'catastrophic cancellation' situation.

We could defer this to another PR to limit scope now

I figured I'd bring to your attention because exposing the sum_of_square values in aggregate enables us to do a quicker var computation here

My impression is it didn't seem like aggregate itself would be something we'd want to manipulate to allow the vs_rest logic, so it seems like for the vs_rest stats we're doing the computation in here (and exposing sum of squares reduces computation)

Additionally, we could also abstract some of the chan stuff into a small util and use that both in aggregate and here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we implemented Welford's algorithm inside https://github.com/scverse/fast-array-utils/ for mean_var, would that take care of the catastrophic cancellation potential? Then it seems like we wouldn't have to worry about the numeric stability here.

So if I have that right, I would think that the route forward would be then, as you say, put that in a separate PR there and then we'll automatically start benefitting from it everywhere in the scanpy codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants