Skip to content

fix: fix modulatity inconsistency by using Weighted_Adjacency directly#4113

Draft
flying-sheep wants to merge 2 commits intomainfrom
pa/graph-upstream
Draft

fix: fix modulatity inconsistency by using Weighted_Adjacency directly#4113
flying-sheep wants to merge 2 commits intomainfrom
pa/graph-upstream

Conversation

@flying-sheep
Copy link
Copy Markdown
Member

@flying-sheep flying-sheep commented May 8, 2026

As pointed out by @jpintar, sc._utils.get_igraph_from_adjacency(..., directed=False) created duplicate edges. This version switches to the upstream API directly which doesn’t do that, resulting in extremely small differences in connectivity scores compared to before.

If we see some performance gain, I think it’s worth the early breakage. I think scanpy 1.13 will have a few smaller breaking changes anyway …

@flying-sheep flying-sheep changed the base branch from pa/graph-construction to main May 8, 2026 12:43
@flying-sheep flying-sheep modified the milestones: 1.12.2, 1.13.0 May 8, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2318 1 2317 459
View the top 1 failed test(s) by shortest run time
tests/notebooks/test_pbmc3k.py::test_pbmc3k
Stack Traces | 6.71s run time
subtests = <_pytest.subtests.Subtests object at 0x7fbc90b149b0>
image_comparer = <function image_comparer.<locals>.save_and_compare at 0x7fbcbc17a0c0>

    #x1B[0m#x1B[37m@needs#x1B[39;49;00m.leidenalg#x1B[90m#x1B[39;49;00m
    #x1B[90m# https://github..../pandas/issues/61928#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.filterwarnings(#x1B[33m"#x1B[39;49;00m#x1B[33mignore:invalid value encountered in cast:RuntimeWarning#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_pbmc3k#x1B[39;49;00m(subtests: pytest.Subtests, image_comparer) -> #x1B[94mNone#x1B[39;49;00m:  #x1B[90m# noqa: PLR0915#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# ensure violin plots and other non-determinstic plots have deterministic behavior#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# TODO: rework this test for scanpy 2.0 so this is no longer necessary#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        np.random.seed(#x1B[94m0#x1B[39;49;00m)  #x1B[90m# noqa: NPY002#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        save_and_compare_images = partial(image_comparer, ROOT, tol=#x1B[94m20#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        adata = sc.datasets.pbmc3k()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# Preprocessing#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.pl.highest_expr_genes(adata, n_top=#x1B[94m20#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mhighest_expr_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.pp.filter_cells(adata, min_genes=#x1B[94m200#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        sc.pp.filter_genes(adata, min_cells=#x1B[94m3#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        mito_genes = [name #x1B[94mfor#x1B[39;49;00m name #x1B[95min#x1B[39;49;00m adata.var_names #x1B[94mif#x1B[39;49;00m name.startswith(#x1B[33m"#x1B[39;49;00m#x1B[33mMT-#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)]#x1B[90m#x1B[39;49;00m
        #x1B[90m# for each cell compute fraction of counts in mito genes vs. all genes#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# the `.A1` is only necessary as X is sparse to transform to a dense array after summing#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = (#x1B[90m#x1B[39;49;00m
            np.sum(adata[:, mito_genes].X, axis=#x1B[94m1#x1B[39;49;00m).A1 / np.sum(adata.X, axis=#x1B[94m1#x1B[39;49;00m).A1#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[90m# add the total counts per cell as observations-annotation to adata#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = adata.X.sum(axis=#x1B[94m1#x1B[39;49;00m).A1#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mviolin#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.violin(#x1B[90m#x1B[39;49;00m
                adata,#x1B[90m#x1B[39;49;00m
                [#x1B[33m"#x1B[39;49;00m#x1B[33mn_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
                jitter=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                multi_panel=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                show=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            )#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mviolin#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_1#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.scatter(adata, x=#x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, y=#x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_1#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_2#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.scatter(adata, x=#x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, y=#x1B[33m"#x1B[39;49;00m#x1B[33mn_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_2#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        adata = adata[adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mn_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] < #x1B[94m2500#x1B[39;49;00m, :]#x1B[90m#x1B[39;49;00m
        adata = adata[adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] < #x1B[94m0.05#x1B[39;49;00m, :]#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        adata.raw = sc.pp.log1p(adata, copy=#x1B[94mTrue#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.pp.normalize_total(adata, target_sum=#x1B[94m1e4#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        sc.pp.log1p(adata)#x1B[90m#x1B[39;49;00m
        filter_result = sc.pp.highly_variable_genes(#x1B[90m#x1B[39;49;00m
            adata, min_mean=#x1B[94m0.0125#x1B[39;49;00m, max_mean=#x1B[94m3#x1B[39;49;00m, min_disp=#x1B[94m0.5#x1B[39;49;00m, inplace=#x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m filter_result #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mhighly_variable_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.highly_variable_genes(filter_result, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mhighly_variable_genes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        adata = adata[:, filter_result[#x1B[33m"#x1B[39;49;00m#x1B[33mhighly_variable#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]].copy()#x1B[90m#x1B[39;49;00m
        sc.pp.regress_out(adata, [#x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
        sc.pp.scale(adata, max_value=#x1B[94m10#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# PCA#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.pp.pca(adata, svd_solver=#x1B[33m"#x1B[39;49;00m#x1B[33marpack#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mpca#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.pca(adata, color=#x1B[33m"#x1B[39;49;00m#x1B[33mCST3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mpca#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mpca_variance_ratio#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.pca_variance_ratio(adata, log=#x1B[94mTrue#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mpca_variance_ratio#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# Neighbors#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.pp.neighbors(adata, n_neighbors=#x1B[94m10#x1B[39;49;00m, n_pcs=#x1B[94m40#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# Clustering the graph#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.leiden(#x1B[90m#x1B[39;49;00m
            adata,#x1B[90m#x1B[39;49;00m
            resolution=#x1B[94m0.9#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            random_state=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            directed=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_iterations=#x1B[94m2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            flavor=#x1B[33m"#x1B[39;49;00m#x1B[33migraph#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            sc.pl.scatter(adata, #x1B[33m"#x1B[39;49;00m#x1B[33mCST3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mNKG7#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, color=#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, show=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            save_and_compare_images(#x1B[33m"#x1B[39;49;00m#x1B[33mscatter_3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# Finding marker genes#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# Due to incosistency with our test runner vs local, these clusters need to#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# be pre-annotated as the numbers for each cluster are not consistent.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        marker_genes = [#x1B[90m#x1B[39;49;00m
            *[#x1B[33m"#x1B[39;49;00m#x1B[33mRP11-18H21.1#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mGZMK#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mCD79A#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mFCGR3A#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
            *[#x1B[33m"#x1B[39;49;00m#x1B[33mGNLY#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mS100A8#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mFCER1A#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mPPBP#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        ]#x1B[90m#x1B[39;49;00m
        data_df = adata[:, marker_genes].to_df()#x1B[90m#x1B[39;49;00m
        data_df[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        max_idxs = data_df.groupby(#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, observed=#x1B[94mTrue#x1B[39;49;00m).mean().idxmax()#x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m subtests.test(#x1B[33m"#x1B[39;49;00m#x1B[33mmarker_genes_unique#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            #x1B[94massert#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m max_idxs[marker_genes][#x1B[90m#x1B[39;49;00m
                max_idxs[marker_genes].duplicated(keep=#x1B[94mFalse#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ].tolist(), #x1B[33m"#x1B[39;49;00m#x1B[33mNot all marker genes are unique per cluster#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        leiden_relabel = {#x1B[90m#x1B[39;49;00m
            max_idxs[marker_gene]: #x1B[96mstr#x1B[39;49;00m(i) #x1B[94mfor#x1B[39;49;00m i, marker_gene #x1B[95min#x1B[39;49;00m #x1B[96menumerate#x1B[39;49;00m(marker_genes)#x1B[90m#x1B[39;49;00m
        }#x1B[90m#x1B[39;49;00m
        adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden_old#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].copy()#x1B[90m#x1B[39;49;00m
        adata.rename_categories(#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [leiden_relabel[key] #x1B[94mfor#x1B[39;49;00m key #x1B[95min#x1B[39;49;00m #x1B[96msorted#x1B[39;49;00m(leiden_relabel.keys())]#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        #x1B[90m# ensure that the column can be sorted for consistent plotting since it is by default unordered#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].cat.reorder_categories(#x1B[90m#x1B[39;49;00m
            #x1B[96mlist#x1B[39;49;00m(#x1B[96mmap#x1B[39;49;00m(#x1B[96mstr#x1B[39;49;00m, #x1B[96mrange#x1B[39;49;00m(#x1B[96mlen#x1B[39;49;00m(adata.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mleiden#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].cat.categories)))), ordered=#x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/notebooks/test_pbmc3k.py#x1B[0m:147: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.low-vers/lib/python3.12.../pandas/core/accessor.py#x1B[0m:112: in f
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._delegate_method(name, *args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.low-vers/lib/python3.12.../core/arrays/categorical.py#x1B[0m:2974: in _delegate_method
    #x1B[0mres = method(*args, **kwargs)#x1B[90m#x1B[39;49;00m
          ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ['0', '2', '0', '3', '4', ..., '5', '2', '2', '2', '0']
Length: 2638
Categories (7, object): ['0', '2', '3', '5', '4', '6', '7']
new_categories = ['0', '1', '2', '3', '4', '5', ...], ordered = True

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mreorder_categories#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, new_categories, ordered=#x1B[94mNone#x1B[39;49;00m) -> Self:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""#x1B[39;49;00m
    #x1B[33m    Reorder categories as specified in new_categories.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    ``new_categories`` need to include all old categories and no new category#x1B[39;49;00m
    #x1B[33m    items.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Parameters#x1B[39;49;00m
    #x1B[33m    ----------#x1B[39;49;00m
    #x1B[33m    new_categories : Index-like#x1B[39;49;00m
    #x1B[33m       The categories in new order.#x1B[39;49;00m
    #x1B[33m    ordered : bool, optional#x1B[39;49;00m
    #x1B[33m       Whether or not the categorical is treated as a ordered categorical.#x1B[39;49;00m
    #x1B[33m       If not given, do not change the ordered information.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Returns#x1B[39;49;00m
    #x1B[33m    -------#x1B[39;49;00m
    #x1B[33m    Categorical#x1B[39;49;00m
    #x1B[33m        Categorical with reordered categories.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Raises#x1B[39;49;00m
    #x1B[33m    ------#x1B[39;49;00m
    #x1B[33m    ValueError#x1B[39;49;00m
    #x1B[33m        If the new categories do not contain all old category items or any#x1B[39;49;00m
    #x1B[33m        new ones#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    See Also#x1B[39;49;00m
    #x1B[33m    --------#x1B[39;49;00m
    #x1B[33m    rename_categories : Rename categories.#x1B[39;49;00m
    #x1B[33m    add_categories : Add new categories.#x1B[39;49;00m
    #x1B[33m    remove_categories : Remove the specified categories.#x1B[39;49;00m
    #x1B[33m    remove_unused_categories : Remove categories which are not used.#x1B[39;49;00m
    #x1B[33m    set_categories : Set the categories to the specified ones.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Examples#x1B[39;49;00m
    #x1B[33m    --------#x1B[39;49;00m
    #x1B[33m    For :class:`pandas.Series`:#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    >>> ser = pd.Series(['a', 'b', 'c', 'a'], dtype='category')#x1B[39;49;00m
    #x1B[33m    >>> ser = ser.cat.reorder_categories(['c', 'b', 'a'], ordered=True)#x1B[39;49;00m
    #x1B[33m    >>> ser#x1B[39;49;00m
    #x1B[33m    0   a#x1B[39;49;00m
    #x1B[33m    1   b#x1B[39;49;00m
    #x1B[33m    2   c#x1B[39;49;00m
    #x1B[33m    3   a#x1B[39;49;00m
    #x1B[33m    dtype: category#x1B[39;49;00m
    #x1B[33m    Categories (3, object): ['c' < 'b' < 'a']#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    >>> ser.sort_values()#x1B[39;49;00m
    #x1B[33m    2   c#x1B[39;49;00m
    #x1B[33m    1   b#x1B[39;49;00m
    #x1B[33m    0   a#x1B[39;49;00m
    #x1B[33m    3   a#x1B[39;49;00m
    #x1B[33m    dtype: category#x1B[39;49;00m
    #x1B[33m    Categories (3, object): ['c' < 'b' < 'a']#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    For :class:`pandas.CategoricalIndex`:#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    >>> ci = pd.CategoricalIndex(['a', 'b', 'c', 'a'])#x1B[39;49;00m
    #x1B[33m    >>> ci#x1B[39;49;00m
    #x1B[33m    CategoricalIndex(['a', 'b', 'c', 'a'], categories=['a', 'b', 'c'],#x1B[39;49;00m
    #x1B[33m                     ordered=False, dtype='category')#x1B[39;49;00m
    #x1B[33m    >>> ci.reorder_categories(['c', 'b', 'a'], ordered=True)#x1B[39;49;00m
    #x1B[33m    CategoricalIndex(['a', 'b', 'c', 'a'], categories=['c', 'b', 'a'],#x1B[39;49;00m
    #x1B[33m                     ordered=True, dtype='category')#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m (#x1B[90m#x1B[39;49;00m
            #x1B[96mlen#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.categories) != #x1B[96mlen#x1B[39;49;00m(new_categories)#x1B[90m#x1B[39;49;00m
            #x1B[95mor#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.categories.difference(new_categories).empty#x1B[90m#x1B[39;49;00m
        ):#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m #x1B[96mValueError#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mitems in new_categories are not the same as in old categories#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            )#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           ValueError: items in new_categories are not the same as in old categories#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.low-vers/lib/python3.12.../core/arrays/categorical.py#x1B[0m:1279: ValueError

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

@scverse-benchmark
Copy link
Copy Markdown

scverse-benchmark Bot commented May 8, 2026

Benchmark changes

Change Before [6af627e] After [a89d635] Ratio Benchmark (Parameter)
+ 20.0±0.08ms 22.3±0.5ms 1.12 tools.ToolsSuite.time_leiden

Comparison: https://github.com/scverse/scanpy/compare/6af627e3de1d7ee2468ea717f6b965bc4a492d89..a89d635b78223c14ccb423b20f84115f4da301bc
Last changed: Fri, 8 May 2026 16:01:21 +0000

More details: https://github.com/scverse/scanpy/pull/4113/checks?check_run_id=75040758121

@flying-sheep
Copy link
Copy Markdown
Member Author

flying-sheep commented May 8, 2026

OK wild, I didn’t expect this to be slower wtf. Maybe just a fluke?

/edit: tried the new Python 3.15 profiler from profiling.sampling and it gave me more expected results:

(for scipy.sparse.random(10_000, 10_000, format="csr"))

directed undirected
igraph 0.41–0.44 ms 1.01–1.03 ms
scanpy 1.7–1.9 ms 1.7–1.9 ms

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.

Inconsistency in graphs used by sc.tl.leiden and sc.metrics.modularity leads to modularity mismatch

1 participant