Skip to content

feat: add illicofor rank_genes_groups#4038

Draft
ilan-gold wants to merge 8 commits intoig/exp_post_aggfrom
ig/illico
Draft

feat: add illicofor rank_genes_groups#4038
ilan-gold wants to merge 8 commits intoig/exp_post_aggfrom
ig/illico

Conversation

@ilan-gold
Copy link
Copy Markdown
Contributor

@ilan-gold ilan-gold commented Apr 7, 2026

TODOs:

See: https://github.com/scverse/scanpy/actions/runs/24088645078/job/70268566419?pr=4038


@ilan-gold ilan-gold changed the title feat: add illico feat: add illicofor rank_genes_groups Apr 7, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

❌ 8 Tests Failed:

Tests completed Failed Passed Skipped
2410 8 2402 339
View the top 3 failed test(s) by shortest run time
tests/test_rank_genes_groups.py::test_illico[True-ovr-benjamini-hochberg]
Stack Traces | 0.094s run time
test = 'ovr', corr_method = 'benjamini-hochberg', exp_post_agg = True

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'logfoldchanges' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 2312 / 2312 (100%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 2.1622095108032227 (ACTUAL), 8.325858550559252 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 2.266411781311035 (ACTUAL), 6.944732752970783 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 2.150602340698242 (ACTUAL), 6.198874226594939 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 1.0153250694274902 (ACTUAL), 8.931626867097355 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 2.227607011795044 (ACTUAL), 7.944156338754546 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 40.99379987#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 243.68797012#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([  2.16221 ,   2.266412,   2.150602, ..., -32.089317,  -2.914431,#x1B[0m
#x1B[1m#x1B[31mE                   -4.218185], shape=(2312,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([8.325859, 6.944733, 6.198874, ..., 5.312698, 4.749682, 4.925329],#x1B[0m
#x1B[1m#x1B[31mE                 shape=(2312,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[True-ovo-bonferroni]
Stack Traces | 0.106s run time
test = 'ovo', corr_method = 'bonferroni', exp_post_agg = True

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'scores' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 3663 / 6885 (53.2%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 11.072574615478516 (ACTUAL), 4.644743632414249 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 4.6447434425354 (ACTUAL), 6.721074116167737 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 6.718207836151123 (ACTUAL), 10.398656939894483 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 10.398656845092773 (ACTUAL), 9.144648336760273 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 9.1428804397583 (ACTUAL), 0.0 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 9.80768871#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 30.41916947#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 11.072575,   4.644743,   6.718208, ...,  -5.931427,  -8.632237,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([  4.644744,   6.721074,  10.398657, ...,  -5.931426,  -8.632238,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[True-ovo-benjamini-hochberg]
Stack Traces | 0.113s run time
test = 'ovo', corr_method = 'benjamini-hochberg', exp_post_agg = True

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'scores' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 3663 / 6885 (53.2%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 11.072574615478516 (ACTUAL), 4.644743632414249 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 4.6447434425354 (ACTUAL), 6.721074116167737 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 6.718207836151123 (ACTUAL), 10.398656939894483 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 10.398656845092773 (ACTUAL), 9.144648336760273 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 9.1428804397583 (ACTUAL), 0.0 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 9.80768871#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 30.41916947#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 11.072575,   4.644743,   6.718208, ...,  -5.931427,  -8.632237,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([  4.644744,   6.721074,  10.398657, ...,  -5.931426,  -8.632238,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[False-ovo-benjamini-hochberg]
Stack Traces | 0.123s run time
test = 'ovo', corr_method = 'benjamini-hochberg', exp_post_agg = False

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'scores' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 3663 / 6885 (53.2%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 11.072574615478516 (ACTUAL), 4.644743632414249 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 4.6447434425354 (ACTUAL), 6.721074116167737 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 6.718207836151123 (ACTUAL), 10.398656939894483 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 10.398656845092773 (ACTUAL), 9.144648336760273 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 9.1428804397583 (ACTUAL), 0.0 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 9.80768871#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 30.41916947#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 11.072575,   4.644743,   6.718208, ...,  -5.931427,  -8.632237,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([  4.644744,   6.721074,  10.398657, ...,  -5.931426,  -8.632238,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[False-ovr-benjamini-hochberg]
Stack Traces | 0.153s run time
test = 'ovr', corr_method = 'benjamini-hochberg', exp_post_agg = False

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'logfoldchanges' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 5541 / 5557 (99.7%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 2.2793736457824707 (ACTUAL), 3.2955667155262343 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 2.5374081134796143 (ACTUAL), -2.136890187851761 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 1.7644909620285034 (ACTUAL), 3.3424527163983333 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 3.211545467376709 (ACTUAL), 3.397135551262183 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 1.7379240989685059 (ACTUAL), -1.045845431282527 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 31.65372012#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 462.6924988#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 2.279374,  2.537408,  1.764491, ..., -1.005601, -0.9857  ,#x1B[0m
#x1B[1m#x1B[31mE                  -0.697857], shape=(5557,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([ 3.295567, -2.13689 ,  3.342453, ..., -6.307728, -6.367315,#x1B[0m
#x1B[1m#x1B[31mE                  -0.450996], shape=(5557,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[False-ovr-bonferroni]
Stack Traces | 0.154s run time
test = 'ovr', corr_method = 'bonferroni', exp_post_agg = False

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'logfoldchanges' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 5541 / 5557 (99.7%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 2.2793736457824707 (ACTUAL), 3.2955667155262343 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 2.5374081134796143 (ACTUAL), -2.136890187851761 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 1.7644909620285034 (ACTUAL), 3.3424527163983333 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 3.211545467376709 (ACTUAL), 3.397135551262183 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 1.7379240989685059 (ACTUAL), -1.045845431282527 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 31.65372012#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 462.6924988#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 2.279374,  2.537408,  1.764491, ..., -1.005601, -0.9857  ,#x1B[0m
#x1B[1m#x1B[31mE                  -0.697857], shape=(5557,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([ 3.295567, -2.13689 ,  3.342453, ..., -6.307728, -6.367315,#x1B[0m
#x1B[1m#x1B[31mE                  -0.450996], shape=(5557,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[True-ovr-bonferroni]
Stack Traces | 5.21s run time
test = 'ovr', corr_method = 'bonferroni', exp_post_agg = True

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'logfoldchanges' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 2312 / 2312 (100%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 2.1622095108032227 (ACTUAL), 8.325858550559252 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 2.266411781311035 (ACTUAL), 6.944732752970783 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 2.150602340698242 (ACTUAL), 6.198874226594939 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 1.0153250694274902 (ACTUAL), 8.931626867097355 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 2.227607011795044 (ACTUAL), 7.944156338754546 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 40.99379987#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 243.68797012#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([  2.16221 ,   2.266412,   2.150602, ..., -32.089317,  -2.914431,#x1B[0m
#x1B[1m#x1B[31mE                   -4.218185], shape=(2312,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([8.325859, 6.944733, 6.198874, ..., 5.312698, 4.749682, 4.925329],#x1B[0m
#x1B[1m#x1B[31mE                 shape=(2312,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError
tests/test_rank_genes_groups.py::test_illico[False-ovo-bonferroni]
Stack Traces | 16.9s run time
test = 'ovo', corr_method = 'bonferroni', exp_post_agg = False

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[33m"#x1B[39;49;00m#x1B[33mcorr_method#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mbenjamini-hochberg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbonferroni#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[33mtest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33movr#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[33mexp_post_agg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[94mTrue#x1B[39;49;00m, #x1B[94mFalse#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
    #x1B[90m# Beause illico does not add 1e-9 to its values before log?#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:RuntimeWarning#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@needs#x1B[39;49;00m.illico#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_illico#x1B[39;49;00m(test, corr_method, exp_post_agg):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96millico#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96masymptotic_wilcoxon#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m asymptotic_wilcoxon#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
        reference = pbmc.obs[#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m].iloc[#x1B[94m0#x1B[39;49;00m] #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        asy_results = asymptotic_wilcoxon(#x1B[90m#x1B[39;49;00m
            adata=pbmc.copy(),#x1B[90m#x1B[39;49;00m
            group_keys=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            is_log1p=#x1B[94mTrue#x1B[39;49;00m,  #x1B[90m# Scanpy assumes log1p#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,  #x1B[90m# Post-aggregation exponentiation is needed to match Scanpy's fold change output#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            reference=reference,#x1B[90m#x1B[39;49;00m
            use_continuity=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy does not apply continuity correction#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,  #x1B[90m# False because scanpy takes a lot of time to adjust#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            n_threads=#x1B[94m1#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            batch_size=#x1B[94m16#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alternative=#x1B[33m"#x1B[39;49;00m#x1B[33mtwo-sided#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,  #x1B[90m# Scanpy only implments two-sided test#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            use_rust=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            return_as_scanpy=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        sc.tl.rank_genes_groups(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            groupby=#x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            method=#x1B[33m"#x1B[39;49;00m#x1B[33mwilcoxon#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            reference=reference #x1B[94mif#x1B[39;49;00m test == #x1B[33m"#x1B[39;49;00m#x1B[33movo#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m #x1B[94melse#x1B[39;49;00m #x1B[33m"#x1B[39;49;00m#x1B[33mrest#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            n_genes=pbmc.n_vars,#x1B[90m#x1B[39;49;00m
            tie_correct=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            corr_method=corr_method,#x1B[90m#x1B[39;49;00m
            exp_post_agg=exp_post_agg,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        scanpy_results = pbmc.uns[#x1B[33m"#x1B[39;49;00m#x1B[33mrank_genes_groups#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mset#x1B[39;49;00m(asy_results.keys()) == #x1B[96mset#x1B[39;49;00m(scanpy_results.keys()), (#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mOutput keys do not match Scanpy#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33ms output format.#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[94mfor#x1B[39;49;00m k, ref #x1B[95min#x1B[39;49;00m scanpy_results.items():#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m k #x1B[95min#x1B[39;49;00m [#x1B[33m"#x1B[39;49;00m#x1B[33mparams#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mnames#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
                #x1B[90m# We can skip names ordering check as if incorrect, other values will mismatch#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            res = np.array(asy_results[k].tolist())#x1B[90m#x1B[39;49;00m
            ref_arr = np.array(ref.tolist())#x1B[90m#x1B[39;49;00m
            mask = np.isfinite(ref_arr) * np.isfinite(#x1B[90m#x1B[39;49;00m
                res#x1B[90m#x1B[39;49;00m
            )  #x1B[90m# Mask to ignore inf values in the comparison#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           np.testing.assert_allclose(#x1B[90m#x1B[39;49;00m
                ref_arr[mask],#x1B[90m#x1B[39;49;00m
                res[mask],#x1B[90m#x1B[39;49;00m
                rtol=#x1B[94m0#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                atol=#x1B[94m1e-2#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                err_msg=#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mMismatch in #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mk#x1B[33m}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m values between asymptotic_wilcoxon and Scanpy outputs.#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           AssertionError: #x1B[0m
#x1B[1m#x1B[31mE           Not equal to tolerance rtol=0, atol=0.01#x1B[0m
#x1B[1m#x1B[31mE           Mismatch in 'scores' values between asymptotic_wilcoxon and Scanpy outputs.#x1B[0m
#x1B[1m#x1B[31mE           Mismatched elements: 3663 / 6885 (53.2%)#x1B[0m
#x1B[1m#x1B[31mE           First 5 mismatches are at indices:#x1B[0m
#x1B[1m#x1B[31mE            [0]: 11.072574615478516 (ACTUAL), 4.644743632414249 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [1]: 4.6447434425354 (ACTUAL), 6.721074116167737 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [2]: 6.718207836151123 (ACTUAL), 10.398656939894483 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [3]: 10.398656845092773 (ACTUAL), 9.144648336760273 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE            [4]: 9.1428804397583 (ACTUAL), 0.0 (DESIRED)#x1B[0m
#x1B[1m#x1B[31mE           Max absolute difference among violations: 9.80768871#x1B[0m
#x1B[1m#x1B[31mE           Max relative difference among violations: 30.41916947#x1B[0m
#x1B[1m#x1B[31mE            ACTUAL: array([ 11.072575,   4.644743,   6.718208, ...,  -5.931427,  -8.632237,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m
#x1B[1m#x1B[31mE            DESIRED: array([  4.644744,   6.721074,  10.398657, ...,  -5.931426,  -8.632238,#x1B[0m
#x1B[1m#x1B[31mE                  -12.954482], shape=(6885,))#x1B[0m

#x1B[1m#x1B[31mtests/test_rank_genes_groups.py#x1B[0m:369: AssertionError

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

@remydubois
Copy link
Copy Markdown

Hey, good catch for the OVO column ordering. I have no idea where the bug comes from, it seems to be related to group labels for PBMC that numpy sorts in a weird way. I will try to get a fix in the coming days.

@remydubois
Copy link
Copy Markdown

So it seems to be due to np.argsort and np.sort not sorting the weird characters (" ", "+", "/", "#", etc) of the bulk_labels the same way. I will ship a fix sometime today or this weekend.

NB: I spent a bit of time looking into the PBMC dataset and it seems rather unusual: a lot of values are negative leading to non-defined logfoldchanges, on top of which there seems to be very little value diversity in the dataset. As a result, a lot of genes end up with identical ranksum, but because illico and scanpy do not compute z-score the exact same way (mathematically equivalent but programmatically different), gene ordering is impacted. The gene ordering impacts the position of NaN values in the results, but non-NaN values do match because they are very similar. I am not sure as if this dataset is a good test case.

NB2: details of the programmatic differences:
illico does:

U = ranksum - n_tgt * (n_tgt+ 1)/2
z = U - n_ref * n_tgt / 2

scanpy does:

z = 
ranksum - n_tgt * (n_tgt + 1 + n_ref) / 2

Those are mathematically equivalent but result in differences of the order of 1.e-9 approximately, changing gene orders when ranksums are equal.

@ilan-gold
Copy link
Copy Markdown
Contributor Author

I am not sure as if this dataset is a good test case.

Interesting, good observation but glad we are aware of this now. This could be another target for scanpy 2.0.

I will try a different dataset but glad to have this documented.

@remydubois
Copy link
Copy Markdown

Forget my previous message which was actually kind of off topic.

This PBMC dataset was actually a very good test case.

  1. It unveiled a silent bug in illico as it seems np.argsort and np.sort are not sorting the weird characters (" ", "+", "/", "#", etc) of the bulk_labels the same way. I will ship a fix in the next few days.
  2. Because it has very limited diversity in the data (the sheer values in .X are not very diverse), a lot of genes end up with identical ranksums (see below example), hence, identical z-scores.
  3. The sorting method used by scanpy is more elaborate as it allows user to select only top n genes. As a result, it does not sort identical values in the same order as illico (even if all genes are returned). That explains the genes ordering difference that I was still facing after fixing the name sorting issue described in 1. I believe I can add the functionality to return n_genes only like scanpy does, and implement the same sorting routine as scanpy, which appears to solve that issue.
  4. Not related to PBMC, but z-scores also mismatched because scanpy casts them to float32, and illico keeps thems as float64. I will fix that in the next patch as well.
  5. For the 1.e-9 adjustment, I don't know what's the best way to go. Currently, illico handles this with a np.where(mu_ref == 0, np.inf, mu_tgt / mu_ref) but I'm quite open to even change what's currently in illico if this e-9 offset is the de-facto standard in other softwares like Seurat or so.

Example: let's take CD14+ Monocyte as control group, CD34+ as perturbed group, and look at the genes CDK6 and TCAEL8. Although they don't exactly have the same values, both CDK6 and TCEAL8 have equal ranksums, hence, equal z-scores. Due to the sorting methodology difference, they end up not sorted in the same order between illico and scanpy.
From the next release, the test suite in illico will not only test closeness of the values but also explicitly test matching ordering of the genes.

NB: what is the situation with the Dask issue ? Current version is not dask-compatible.

@ilan-gold
Copy link
Copy Markdown
Contributor Author

NB: what is the situation with the Dask issue ? Current version is not dask-compatible.

That should come next, I want to keep this scoped for now to the in-memory stuff.

Another thing if you're doing a batch of fixes this weekend - supporting cs{r,c}_array would be awesome. Should only be one line of code.

Also for your numba types, only if you want instead of named tuples, FAU has a plug-in for handling putting cs{c,r}_{array,matrix} into numba kernels: https://github.com/scverse/fast-array-utils/blob/main/src/fast_array_utils/_plugins/numba_sparse.py. But it might not be worth it to bring on the dependency just for this purpose

@ilan-gold
Copy link
Copy Markdown
Contributor Author

ilan-gold commented Apr 10, 2026

For the 1.e-9 adjustment, I don't know what's the best way to go. Currently, illico handles this with a np.where(mu_ref == 0, np.inf, mu_tgt / mu_ref) but I'm quite open to even change what's currently in illico if this e-9 offset is the de-facto standard in other softwares like Seurat or so.

I'm seeing some instances of https://github.com/satijalab/seurat/blob/main/R/differential_expression.R#L1089 i.e., they calculate the mean expression with this "+1" offset so don't need the correction.

So we have three different things here, I guess. I think just giving the option to match scanpy (since that seems a project goal) is good, and then we can maybe revisit what seurat does as a new parameter for scanpy 2.0.

Not related to PBMC, but z-scores also mismatched because scanpy casts them to float32, and illico keeps thems as float64. I will fix that in the next patch as well.

For this, I would be open to also making this part of a scanpy 2.0 set of default i.e., using float64, as there are other places we might benefit from this. So if you were to keep float64, I don't think that would be a blocker. Again, an option could be good so we can smooth out the transition, although this is so small as to maybe not warrant it.

Overall, my ideal scenario would be that illico and scanpy with wilcoxon match, and then we use illico by default in scanpy 2.0. I'm pushing to minimize result changes to make the transition smoother (since results changing are always no fun) and so far the changes don't seem particularly destructiv.

Short of exact matches, like I said we can just document changes. It sounds like the biggest things that would close the gap are:

  1. argsort bug
  2. 1e-9 option
  3. The option to return n_genes
  4. cs{r,c}_array support

I think we can patch the float{32,64} stuff in a general "numerical accuracy" scanpy 2.0 preset (which we have on main right now).

Overall, this is really cool and I'm super happy that things seem to be pretty close!

@remydubois
Copy link
Copy Markdown

remydubois commented Apr 12, 2026

Hey,

I just released version 0.5.0rc1 which should fix the issues identified on this test case:

  1. Perturbation (or group) names are no longer re-sorted when outputs are formatted for scanpy ensuring they are ordered the same everywhere.
  2. Fold change is now computed by adding the same 1.e-9 factor, on top of being accumulated into a f64 placeholder (regardless of the original data dtype) ensuring a better matching.
  3. New argument n_genes allowing to return only top n DE genes per perturbation (which, even if not specified, results in genes sorting methodology being identical. This solves the issue raised by genes having identical scores).
  4. Support for cs[cr]_array. I did not explicitely add test cases for those as the test suite is already quite heavy and illico does nothing more than accessing .data, .indices, and .indptr of those objects. I did test it manually quickly and it ran with no issue.

Regarding the numerical precision question: so far I force the recarrays to have the same dtype as what's currently implemented in scanpy, see FC for instance. I believe we could change that when the need comes as you say.

Testing locally, it seems everything now runs smoothly for PMBC. Let me know how it goes with the rest of the CI.

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