From 2a908e02f7d8b8df2a456c9638e6452088eb158e Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 18 May 2026 08:41:12 +0200 Subject: [PATCH 1/9] work in progress --- .../_differential_gene_expression/_base.py | 5 +++++ pertpy/tools/_enrichment.py | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pertpy/tools/_differential_gene_expression/_base.py b/pertpy/tools/_differential_gene_expression/_base.py index a2f4c2ce..c7585d18 100644 --- a/pertpy/tools/_differential_gene_expression/_base.py +++ b/pertpy/tools/_differential_gene_expression/_base.py @@ -675,6 +675,8 @@ def plot_fold_change( # pragma: no cover # noqa: D417 *, var_names: Sequence[str] = None, n_top_vars: int = 15, + padj_threshold: float = 0.01, + padj_threshold_col: str = "adj_p_value", log2fc_col: str = "log_fc", symbol_col: str = "variable", y_label: str = "Log2 fold change", @@ -688,6 +690,8 @@ def plot_fold_change( # pragma: no cover # noqa: D417 results_df: DataFrame with results from DE analysis. var_names: Variables to plot. If None, the top n_top_vars variables based on the log2 fold change are plotted. n_top_vars: Number of top variables to plot. The top and bottom n_top_vars variables are plotted, respectively. + padj_threshold: Only variables with adjusted p-values below this threshold are included in the plot. + padj_threshold_col: Column name of adjusted p-values, log2fc_col: Column name of log2 Fold-Change values. symbol_col: Column name of gene IDs. y_label: Label for the y-axis. @@ -729,6 +733,7 @@ def plot_fold_change( # pragma: no cover # noqa: D417 assert len(var_names) == 2 * n_top_vars df = results_df[results_df[symbol_col].isin(var_names)] + df = df[df[padj_threshold_col] < padj_threshold] df.sort_values(log2fc_col, ascending=False, inplace=True) plt.figure(figsize=figsize) diff --git a/pertpy/tools/_enrichment.py b/pertpy/tools/_enrichment.py index 1064d0fb..dcd5c465 100644 --- a/pertpy/tools/_enrichment.py +++ b/pertpy/tools/_enrichment.py @@ -153,9 +153,10 @@ def hypergeometric( targets: dict[str, list[str] | dict[str, list[str]]] | None = None, nested: bool = False, categories: str | list[str] | None = None, - pvals_adj_thresh: float = 0.05, + padj_threshold: float = 0.05, direction: str = "both", corr_method: Literal["benjamini-hochberg", "bonferroni"] = "benjamini-hochberg", + **kwargs, ): """Perform a hypergeometric test to assess the overrepresentation of gene group members. @@ -170,7 +171,7 @@ def hypergeometric( nested: Whether `targets` is a dictionary of dictionaries with group categories as keys. categories: If `targets=None` or `nested=True`, this argument can be used to subset the gene groups to one or more categories (keys of the original dictionary). In case of the ChEMBL drug targets, these are ATC level 1/level 2 category codes. - pvals_adj_thresh: The `pvals_adj` cutoff to use on the `sc.tl.rank_genes_groups()` output to identify markers. + padj_threshold: The `pvals_adj` cutoff to use on the `sc.tl.rank_genes_groups()` output to identify markers. direction: Whether to seek out up/down-regulated genes for the groups, based on the values from `scores`. Can be `up`, `down`, or `both` (for no selection). corr_method: Which FDR correction to apply to the p-values of the hypergeometric test. @@ -180,6 +181,17 @@ def hypergeometric( Dictionary with clusters for which the original object markers were computed as the keys, and data frames of test results sorted on q-value as the items. """ + if "pvals_adj_thresh" in kwargs: + import warnings + + warnings.warn( + "`pvals_adj_thresh` is deprecated and will be removed in a future release; use `padj_threshold`", + DeprecationWarning, + ) + padj_threshold = kwargs.pop("pvals_adj_thresh") + if kwargs: + raise TypeError(f"hypergeometric() got unexpected keyword arguments {list(kwargs.keys())}") + universe = set(adata.var_names) targets = _prepare_targets(targets=targets, nested=nested, categories=categories) # type: ignore for group in targets: @@ -201,7 +213,7 @@ def hypergeometric( "pvals_adj", ], ) - mask = adata.uns["rank_genes_groups"]["pvals_adj"][cluster] < pvals_adj_thresh + mask = adata.uns["rank_genes_groups"]["pvals_adj"][cluster] < padj_threshold if direction == "up": mask = mask & (adata.uns["rank_genes_groups"]["scores"][cluster] > 0) elif direction == "down": From 7612986bc36874a20f9d85a0d7683526b01c70b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 06:44:34 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pertpy/tools/_enrichment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pertpy/tools/_enrichment.py b/pertpy/tools/_enrichment.py index dcd5c465..a9daec26 100644 --- a/pertpy/tools/_enrichment.py +++ b/pertpy/tools/_enrichment.py @@ -187,6 +187,7 @@ def hypergeometric( warnings.warn( "`pvals_adj_thresh` is deprecated and will be removed in a future release; use `padj_threshold`", DeprecationWarning, + stacklevel=2, ) padj_threshold = kwargs.pop("pvals_adj_thresh") if kwargs: From 031302b01dd56887086c2dba810720e6dc8ef2c8 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 18 May 2026 16:45:40 +0200 Subject: [PATCH 3/9] test deprecated_arg() --- pertpy/tools/_enrichment.py | 14 ++++++++------ pyproject.toml | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pertpy/tools/_enrichment.py b/pertpy/tools/_enrichment.py index dcd5c465..8a885b09 100644 --- a/pertpy/tools/_enrichment.py +++ b/pertpy/tools/_enrichment.py @@ -13,6 +13,7 @@ from scanpy.tools._score_genes import _sparse_nanmean from scipy.sparse import issparse from scipy.stats import hypergeom +from scverse_misc import Deprecation, deprecated_arg from statsmodels.stats.multitest import multipletests from pertpy._doc import _doc_params, doc_common_plot_args @@ -147,6 +148,12 @@ def score( adata.uns[f"{key_added}_genes"]["var"].loc[drug, "genes"] = "|".join(adata.var_names[targets[drug]]) adata.uns[f"{key_added}_all_genes"]["var"].loc[drug, "all_genes"] = "|".join(full_targets[drug]) + @deprecated_arg( + "pvals_adj_thresh", + Deprecation( + "1.0.6", "`pvals_adj_thresh` is deprecated and will be removed in a future release. Use `padj_threshold`." + ), + ) def hypergeometric( self, adata: AnnData, @@ -176,18 +183,13 @@ def hypergeometric( Can be `up`, `down`, or `both` (for no selection). corr_method: Which FDR correction to apply to the p-values of the hypergeometric test. Can be `benjamini-hochberg` or `bonferroni`. + kwargs: Used only for the deprecated `pvals_adj_thresh` argument. Returns: Dictionary with clusters for which the original object markers were computed as the keys, and data frames of test results sorted on q-value as the items. """ if "pvals_adj_thresh" in kwargs: - import warnings - - warnings.warn( - "`pvals_adj_thresh` is deprecated and will be removed in a future release; use `padj_threshold`", - DeprecationWarning, - ) padj_threshold = kwargs.pop("pvals_adj_thresh") if kwargs: raise TypeError(f"hypergeometric() got unexpected keyword arguments {list(kwargs.keys())}") diff --git a/pyproject.toml b/pyproject.toml index 9e6f04d7..dd35cf7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,8 @@ dependencies = [ "scikit-learn>=1.4", "fast-array-utils[accel,sparse]", "arviz>=1.0.0", - "filelock" + "filelock", + "scverse-misc" ] [project.optional-dependencies] From 65521178dbff750be13c6546e3e63472bc236261 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Tue, 19 May 2026 07:48:58 +0200 Subject: [PATCH 4/9] final deprecation warning after testing --- pertpy/tools/_enrichment.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pertpy/tools/_enrichment.py b/pertpy/tools/_enrichment.py index 8a885b09..81d956cd 100644 --- a/pertpy/tools/_enrichment.py +++ b/pertpy/tools/_enrichment.py @@ -150,9 +150,7 @@ def score( @deprecated_arg( "pvals_adj_thresh", - Deprecation( - "1.0.6", "`pvals_adj_thresh` is deprecated and will be removed in a future release. Use `padj_threshold`." - ), + Deprecation("1.0.6", "Use `padj_threshold`."), ) def hypergeometric( self, @@ -161,9 +159,9 @@ def hypergeometric( nested: bool = False, categories: str | list[str] | None = None, padj_threshold: float = 0.05, + pvals_adj_thresh: float | None = None, direction: str = "both", corr_method: Literal["benjamini-hochberg", "bonferroni"] = "benjamini-hochberg", - **kwargs, ): """Perform a hypergeometric test to assess the overrepresentation of gene group members. @@ -183,16 +181,14 @@ def hypergeometric( Can be `up`, `down`, or `both` (for no selection). corr_method: Which FDR correction to apply to the p-values of the hypergeometric test. Can be `benjamini-hochberg` or `bonferroni`. - kwargs: Used only for the deprecated `pvals_adj_thresh` argument. + pvals_adj_thresh: Deprecated and will be removed in a future release. Use `padj_threshold`. Returns: Dictionary with clusters for which the original object markers were computed as the keys, and data frames of test results sorted on q-value as the items. """ - if "pvals_adj_thresh" in kwargs: - padj_threshold = kwargs.pop("pvals_adj_thresh") - if kwargs: - raise TypeError(f"hypergeometric() got unexpected keyword arguments {list(kwargs.keys())}") + if pvals_adj_thresh is not None: + padj_threshold = pvals_adj_thresh universe = set(adata.var_names) targets = _prepare_targets(targets=targets, nested=nested, categories=categories) # type: ignore From 1a17df1fed6d2cc1f362720cad66a5bc7d9c9ea8 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Wed, 27 May 2026 18:39:26 +0200 Subject: [PATCH 5/9] add deprecated_arg() to plot_volcano() --- .../_differential_gene_expression/_base.py | 98 ++++++++++++------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/pertpy/tools/_differential_gene_expression/_base.py b/pertpy/tools/_differential_gene_expression/_base.py index c7585d18..aad61d6a 100644 --- a/pertpy/tools/_differential_gene_expression/_base.py +++ b/pertpy/tools/_differential_gene_expression/_base.py @@ -14,6 +14,7 @@ import seaborn as sns from matplotlib.pyplot import Figure from matplotlib.ticker import MaxNLocator +from scverse_misc import Deprecation, deprecated_arg from pertpy._doc import _doc_params, doc_common_plot_args from pertpy._logger import logger @@ -98,15 +99,27 @@ def compare_groups( ... @_doc_params(common_plot_args=doc_common_plot_args) + @deprecated_arg( + "pval_thresh", + Deprecation("1.1.0", "Use `padj_threshold`."), + ) + @deprecated_arg( + "pvalue_col", + Deprecation("1.1.0", "Use `padj_col`."), + ) + @deprecated_arg( + "log2fc_thresh", + Deprecation("1.1.0", "Use `log2fc_threshold`."), + ) def plot_volcano( # pragma: no cover # noqa: D417 self, data: pd.DataFrame | ad.AnnData, *, + log2fc_threshold: float = 0.75, + padj_threshold: float = 0.01, log2fc_col: str = "log_fc", - pvalue_col: str = "adj_p_value", + padj_col: str = "adj_p_value", symbol_col: str = "variable", - pval_thresh: float = 0.05, - log2fc_thresh: float = 0.75, to_label: int | list[str] = 5, s_curve: bool | None = False, colors: list[str] = None, @@ -124,20 +137,23 @@ def plot_volcano( # pragma: no cover # noqa: D417 x_label: str | None = None, y_label: str | None = None, return_fig: bool = False, + log2fc_thresh: float | None = None, + pval_thresh: float | None = None, + pvalue_col: str | None = None, **kwargs: int, ) -> Figure | None: """Creates a volcano plot from a pandas DataFrame or Anndata. Args: data: DataFrame or Anndata to plot. + log2fc_threshold: Threshold for log2 fold change significance. + padj_threshold: Adjusted p-values for significance. log2fc_col: Column name of log2 Fold-Change values. - pvalue_col: Column name of the p values. + padj_col: Column name of adjusted p-values. symbol_col: Column name of gene IDs. varm_key: Key in Anndata.varm slot to use for plotting if an Anndata object was passed. size_col: Column name to size points by. point_sizes: Lower and upper bounds of point sizes. - pval_thresh: Threshold p value for significance. - log2fc_thresh: Threshold for log2 fold change significance. to_label: Number of top genes or list of genes to label. s_curve: Whether to use a reciprocal threshold for up and down gene determination. color_dict: Dictionary for coloring dots by categories. @@ -151,6 +167,9 @@ def plot_volcano( # pragma: no cover # noqa: D417 shape_order: Order of categories for shapes. x_label: Label for the x-axis. y_label: Label for the y-axis. + log2fc_thresh: Deprecated and will be removed in a future release. Use `log2fc_threshold`. + pval_thresh: Deprecated and will be removed in a future release. Use `padj_threshold`. + pvalue_col: Deprecated and will be removed in a future release. Use `padj_col`. {common_plot_args} **kwargs: Additional arguments for seaborn.scatterplot. @@ -177,11 +196,18 @@ def plot_volcano( # pragma: no cover # noqa: D417 >>> res_df = edgr.test_contrasts( ... edgr.contrast(column="Treatment", baseline="Chemo", group_to_compare="Anti-PD-L1+Chemo") ... ) - >>> edgr.plot_volcano(res_df, log2fc_thresh=0) + >>> edgr.plot_volcano(res_df, log2fc_threshold=0) Preview: .. image:: /_static/docstring_previews/de_volcano.png """ + if pvalue_col is not None: + padj_col = pvalue_col + if pval_thresh is not None: + padj_threshold = pval_thresh + if log2fc_thresh is not None: + log2fc_threshold = log2fc_thresh + if colors is None: colors = ["gray", "#D62728", "#1F77B4"] @@ -190,7 +216,7 @@ def _pval_reciprocal(lfc: float) -> float: Used for plotting the S-curve """ - return pval_thresh / (lfc - log2fc_thresh) + return padj_threshold / (lfc - log2fc_threshold) def _map_shape(symbol: str) -> str: if shape_dict is not None: @@ -204,8 +230,8 @@ def _map_genes_categories( row: pd.Series, log2fc_col: str, nlog10_col: str, - log2fc_thresh: float, - pval_thresh: float = None, + log2fc_threshold: float, + padj_threshold: float = None, s_curve: bool = False, ) -> str: """Map genes to categorize based on log2fc and pvalue. @@ -219,16 +245,16 @@ def _map_genes_categories( if s_curve: # S-curve condition for Up or Down categorization reciprocal_thresh = _pval_reciprocal(abs(log2fc)) - if log2fc > log2fc_thresh and nlog10 > reciprocal_thresh: + if log2fc > log2fc_threshold and nlog10 > reciprocal_thresh: return "Up" - elif log2fc < -log2fc_thresh and nlog10 > reciprocal_thresh: + elif log2fc < -log2fc_threshold and nlog10 > reciprocal_thresh: return "Down" else: return "not DE" # Standard condition for Up or Down categorization - elif log2fc > log2fc_thresh and nlog10 > pval_thresh: + elif log2fc > log2fc_threshold and nlog10 > padj_threshold: return "Up" - elif log2fc < -log2fc_thresh and nlog10 > pval_thresh: + elif log2fc < -log2fc_threshold and nlog10 > padj_threshold: return "Down" else: return "not DE" @@ -237,8 +263,8 @@ def _map_genes_categories_highlight( row: pd.Series, log2fc_col: str, nlog10_col: str, - log2fc_thresh: float, - pval_thresh: float = None, + log2fc_threshold: float, + padj_threshold: float = None, s_curve: bool = False, symbol_col: str = None, ) -> str: @@ -258,12 +284,12 @@ def _map_genes_categories_highlight( if s_curve: # Use S-curve condition for filtering DE - if nlog10 > _pval_reciprocal(abs(log2fc)) and abs(log2fc) > log2fc_thresh: + if nlog10 > _pval_reciprocal(abs(log2fc)) and abs(log2fc) > log2fc_threshold: return "DE" return "not DE" else: # Use standard condition for filtering DE - if abs(log2fc) < log2fc_thresh or nlog10 < pval_thresh: + if abs(log2fc) < log2fc_threshold or nlog10 < padj_threshold: return "not DE" return "DE" @@ -277,18 +303,18 @@ def _map_genes_categories_highlight( df = data.copy(deep=True) # clean and replace 0s as they would lead to -inf - if df[[log2fc_col, pvalue_col]].isnull().values.any(): + if df[[log2fc_col, padj_col]].isnull().values.any(): print("NaNs encountered, dropping rows with NaNs") - df = df.dropna(subset=[log2fc_col, pvalue_col]) + df = df.dropna(subset=[log2fc_col, padj_col]) - if df[pvalue_col].min() == 0: + if df[padj_col].min() == 0: print("0s encountered for p value, replacing with 1e-323") - df.loc[df[pvalue_col] == 0, pvalue_col] = 1e-323 + df.loc[df[padj_col] == 0, padj_col] = 1e-323 # convert p value threshold to nlog10 - pval_thresh = -np.log10(pval_thresh) + padj_threshold = -np.log10(padj_threshold) # make nlog10 column - df["nlog10"] = -np.log10(df[pvalue_col]) + df["nlog10"] = -np.log10(df[padj_col]) y_max = df["nlog10"].max() + 1 # make a column to pick top genes df["top_genes"] = df["nlog10"] * df[log2fc_col] @@ -323,8 +349,8 @@ def _map_genes_categories_highlight( row, log2fc_col=log2fc_col, nlog10_col="nlog10", - log2fc_thresh=log2fc_thresh, - pval_thresh=pval_thresh, + log2fc_threshold=log2fc_threshold, + padj_threshold=padj_threshold, s_curve=s_curve, ), axis=1, @@ -339,8 +365,8 @@ def _map_genes_categories_highlight( row, log2fc_col=log2fc_col, nlog10_col="nlog10", - log2fc_thresh=log2fc_thresh, - pval_thresh=pval_thresh, + log2fc_threshold=log2fc_threshold, + padj_threshold=padj_threshold, symbol_col=symbol_col, s_curve=s_curve, ), @@ -427,15 +453,15 @@ def _map_genes_categories_highlight( # plot vertical and horizontal lines if s_curve: - x = np.arange((log2fc_thresh + 0.000001), y_max, 0.01) + x = np.arange((log2fc_threshold + 0.000001), y_max, 0.01) y = _pval_reciprocal(x) ax.plot(x, y, zorder=1, c="k", lw=2, ls="--") ax.plot(-x, y, zorder=1, c="k", lw=2, ls="--") else: - ax.axhline(pval_thresh, zorder=1, c="k", lw=2, ls="--") - ax.axvline(log2fc_thresh, zorder=1, c="k", lw=2, ls="--") - ax.axvline(log2fc_thresh * -1, zorder=1, c="k", lw=2, ls="--") + ax.axhline(padj_threshold, zorder=1, c="k", lw=2, ls="--") + ax.axvline(log2fc_threshold, zorder=1, c="k", lw=2, ls="--") + ax.axvline(log2fc_threshold * -1, zorder=1, c="k", lw=2, ls="--") plt.ylim(0, y_max) ax.xaxis.set_major_locator(MaxNLocator(integer=True)) @@ -470,7 +496,7 @@ def _map_genes_categories_highlight( if x_label is None: x_label = log2fc_col if y_label is None: - y_label = f"-$log_{{10}}$ {pvalue_col}" + y_label = f"-$log_{{10}}$ {padj_col}" plt.xlabel(x_label, size=15) plt.ylabel(y_label, size=15) @@ -676,7 +702,7 @@ def plot_fold_change( # pragma: no cover # noqa: D417 var_names: Sequence[str] = None, n_top_vars: int = 15, padj_threshold: float = 0.01, - padj_threshold_col: str = "adj_p_value", + padj_col: str = "adj_p_value", log2fc_col: str = "log_fc", symbol_col: str = "variable", y_label: str = "Log2 fold change", @@ -691,7 +717,7 @@ def plot_fold_change( # pragma: no cover # noqa: D417 var_names: Variables to plot. If None, the top n_top_vars variables based on the log2 fold change are plotted. n_top_vars: Number of top variables to plot. The top and bottom n_top_vars variables are plotted, respectively. padj_threshold: Only variables with adjusted p-values below this threshold are included in the plot. - padj_threshold_col: Column name of adjusted p-values, + padj_col: Column name of adjusted p-values. log2fc_col: Column name of log2 Fold-Change values. symbol_col: Column name of gene IDs. y_label: Label for the y-axis. @@ -733,7 +759,7 @@ def plot_fold_change( # pragma: no cover # noqa: D417 assert len(var_names) == 2 * n_top_vars df = results_df[results_df[symbol_col].isin(var_names)] - df = df[df[padj_threshold_col] < padj_threshold] + df = df[df[padj_col] < padj_threshold] df.sort_values(log2fc_col, ascending=False, inplace=True) plt.figure(figsize=figsize) From 795c7f7d8bc94327b4578dab314fa7767c762098 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Wed, 27 May 2026 19:36:59 +0200 Subject: [PATCH 6/9] replace alpha with padj_threshold --- pertpy/tools/_milo.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pertpy/tools/_milo.py b/pertpy/tools/_milo.py index cb8a3ab3..e657e57b 100644 --- a/pertpy/tools/_milo.py +++ b/pertpy/tools/_milo.py @@ -844,11 +844,15 @@ def _graph_spatial_fdr( sample_adata.var["SpatialFDR"] = sample_adata.var["SpatialFDR"].fillna(1) @_doc_params(common_plot_args=doc_common_plot_args) + @deprecated_arg( + "alpha", + Deprecation("1.1.0", "Use `padj_threshold`."), + ) def plot_nhood_graph( # pragma: no cover # noqa: D417 self, mdata: MuData, *, - alpha: float = 0.1, + padj_threshold: float = 0.1, min_logFC: float = 0, min_size: int = 10, plot_edges: bool = False, @@ -857,17 +861,19 @@ def plot_nhood_graph( # pragma: no cover # noqa: D417 palette: str | Sequence[str] | None = None, ax: Axes | None = None, return_fig: bool = False, + alpha: float | None = None, **kwargs, ) -> Figure | None: """Visualize DA results on abstracted graph (wrapper around sc.pl.embedding). Args: mdata: MuData object - alpha: Significance threshold. (default: 0.1) + padj_threshold: Significance threshold. (default: 0.1) min_logFC: Minimum absolute log-Fold Change to show results. If is 0, show all significant neighbourhoods. min_size: Minimum size of nodes in visualization. (default: 10) plot_edges: If edges for neighbourhood overlaps whould be plotted. title: Plot title. + alpha: Deprecated and will be removed in a future release. Use `padj_threshold`. {common_plot_args} **kwargs: Additional arguments to `scanpy.pl.embedding`. @@ -890,6 +896,9 @@ def plot_nhood_graph( # pragma: no cover # noqa: D417 Preview: .. image:: /_static/docstring_previews/milo_nhood_graph.png """ + if alpha is not None: + padj_threshold = alpha + nhood_adata = mdata["milo"].T.copy() if "Nhood_size" not in nhood_adata.obs.columns: @@ -899,7 +908,7 @@ def plot_nhood_graph( # pragma: no cover # noqa: D417 ) nhood_adata.obs["graph_color"] = nhood_adata.obs["logFC"] - nhood_adata.obs.loc[nhood_adata.obs["SpatialFDR"] > alpha, "graph_color"] = np.nan + nhood_adata.obs.loc[nhood_adata.obs["SpatialFDR"] > padj_threshold, "graph_color"] = np.nan nhood_adata.obs["abs_logFC"] = abs(nhood_adata.obs["logFC"]) nhood_adata.obs.loc[nhood_adata.obs["abs_logFC"] < min_logFC, "graph_color"] = np.nan @@ -998,16 +1007,21 @@ def plot_nhood( # pragma: no cover # noqa: D417 return None @_doc_params(common_plot_args=doc_common_plot_args) + @deprecated_arg( + "alpha", + Deprecation("1.1.0", "Use `padj_threshold`."), + ) def plot_da_beeswarm( # pragma: no cover # noqa: D417 self, mdata: MuData, *, feature_key: str | None = "rna", anno_col: str = "nhood_annotation", - alpha: float = 0.1, + padj_threshold: float = 0.1, subset_nhoods: list[str] = None, palette: str | Sequence[str] | dict[str, str] | None = None, return_fig: bool = False, + alpha: float | None = None, ) -> Figure | None: """Plot beeswarm plot of logFC against nhood labels. @@ -1015,10 +1029,11 @@ def plot_da_beeswarm( # pragma: no cover # noqa: D417 mdata: MuData object feature_key: Key in mdata to the cell-level AnnData object. anno_col: Column in adata.uns['nhood_adata'].obs to use as annotation. (default: 'nhood_annotation'.) - alpha: Significance threshold. (default: 0.1) + padj_threshold: Significance threshold. (default: 0.1) subset_nhoods: List of nhoods to plot. If None, plot all nhoods. palette: Name of Seaborn color palette for violinplots. Defaults to pre-defined category colors for violinplots. + alpha: Deprecated and will be removed in a future release. Use `padj_threshold`. {common_plot_args} Returns: @@ -1040,6 +1055,9 @@ def plot_da_beeswarm( # pragma: no cover # noqa: D417 Preview: .. image:: /_static/docstring_previews/milo_da_beeswarm.png """ + if alpha is not None: + padj_threshold = alpha + try: nhood_adata = mdata["milo"].T.copy() except KeyError: @@ -1069,7 +1087,7 @@ def plot_da_beeswarm( # pragma: no cover # noqa: D417 ) anno_df = nhood_adata.obs[[anno_col, "logFC", "SpatialFDR"]].copy() - anno_df["is_signif"] = anno_df["SpatialFDR"] < alpha + anno_df["is_signif"] = anno_df["SpatialFDR"] < padj_threshold anno_df = anno_df[anno_df[anno_col] != "nan"] try: @@ -1115,7 +1133,9 @@ def plot_da_beeswarm( # pragma: no cover # noqa: D417 orient="h", alpha=0.5, ) - plt.legend(loc="upper left", title=f"< {int(alpha * 100)}% SpatialFDR", bbox_to_anchor=(1, 1), frameon=False) + plt.legend( + loc="upper left", title=f"< {int(padj_threshold * 100)}% SpatialFDR", bbox_to_anchor=(1, 1), frameon=False + ) plt.axvline(x=0, ymin=0, ymax=1, color="black", linestyle="--") if return_fig: From 631ea7f6ee8034e07cf61cf8e89693663716aaac Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Fri, 29 May 2026 06:29:04 +0200 Subject: [PATCH 7/9] small fixes during testing --- .../tools/_differential_gene_expression/_base.py | 14 ++++---------- .../_differential_gene_expression/_pydeseq2.py | 3 +++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pertpy/tools/_differential_gene_expression/_base.py b/pertpy/tools/_differential_gene_expression/_base.py index 4c229869..e19cb211 100644 --- a/pertpy/tools/_differential_gene_expression/_base.py +++ b/pertpy/tools/_differential_gene_expression/_base.py @@ -95,14 +95,8 @@ def compare_groups( "pval_thresh", Deprecation("1.1.0", "Use `padj_threshold`."), ) - @deprecated_arg( - "pvalue_col", - Deprecation("1.1.0", "Use `padj_col`."), - ) - @deprecated_arg( - "log2fc_thresh", - Deprecation("1.1.0", "Use `log2fc_threshold`."), - ) + @deprecated_arg("pvalue_col", Deprecation("1.1.0", "Use `padj_col`."), stacklevel=2) + @deprecated_arg("log2fc_thresh", Deprecation("1.1.0", "Use `log2fc_threshold`."), stacklevel=3) def plot_volcano( # pragma: no cover # noqa: D417 self, data: pd.DataFrame | ad.AnnData, @@ -721,13 +715,13 @@ def plot_fold_change( # pragma: no cover # noqa: D417 Preview: .. image:: /_static/docstring_previews/de_fold_change.png """ + results_df = results_df[results_df[padj_col] < padj_threshold] if var_names is None: var_names = results_df.sort_values(log2fc_col, ascending=False).head(n_top_vars)[symbol_col].tolist() var_names += results_df.sort_values(log2fc_col, ascending=True).head(n_top_vars)[symbol_col].tolist() assert len(var_names) == 2 * n_top_vars - df = results_df[results_df[symbol_col].isin(var_names)] - df = df[df[padj_col] < padj_threshold] + df = results_df[results_df[symbol_col].isin(var_names)].copy() df.sort_values(log2fc_col, ascending=False, inplace=True) plt.figure(figsize=figsize) diff --git a/pertpy/tools/_differential_gene_expression/_pydeseq2.py b/pertpy/tools/_differential_gene_expression/_pydeseq2.py index fd6bed4f..600ca627 100644 --- a/pertpy/tools/_differential_gene_expression/_pydeseq2.py +++ b/pertpy/tools/_differential_gene_expression/_pydeseq2.py @@ -1,4 +1,5 @@ import os +import warnings import numpy as np import pandas as pd @@ -12,6 +13,8 @@ from ._base import LinearModelBase from ._checks import check_is_integer_matrix +warnings.filterwarnings("always", message=".*(pval_thresh|pvalue_col|log2fc_thresh).*") + class PyDESeq2(LinearModelBase): """Differential expression test using a PyDESeq2.""" From 6571d3832d1193a97491ddfb789cde2db63d8bd9 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Fri, 29 May 2026 06:40:40 +0200 Subject: [PATCH 8/9] keep old default value --- pertpy/tools/_differential_gene_expression/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pertpy/tools/_differential_gene_expression/_base.py b/pertpy/tools/_differential_gene_expression/_base.py index e19cb211..509bacdf 100644 --- a/pertpy/tools/_differential_gene_expression/_base.py +++ b/pertpy/tools/_differential_gene_expression/_base.py @@ -102,7 +102,7 @@ def plot_volcano( # pragma: no cover # noqa: D417 data: pd.DataFrame | ad.AnnData, *, log2fc_threshold: float = 0.75, - padj_threshold: float = 0.01, + padj_threshold: float = 0.05, log2fc_col: str = "log_fc", padj_col: str = "adj_p_value", symbol_col: str = "variable", From c934878f06fb6bce7f108bb4de94913b90a69685 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Fri, 29 May 2026 09:27:15 +0200 Subject: [PATCH 9/9] add warning filter for milo deprecation --- pertpy/tools/_milo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pertpy/tools/_milo.py b/pertpy/tools/_milo.py index 4842e033..da5164f4 100644 --- a/pertpy/tools/_milo.py +++ b/pertpy/tools/_milo.py @@ -466,9 +466,13 @@ def da_nhoods( if find_spec("pydeseq2") is None: raise ImportError("pydeseq2 is required but not installed. Install with: pip install pydeseq2") + import warnings + from pydeseq2.dds import DeseqDataSet from pydeseq2.ds import DeseqStats + warnings.filterwarnings("always", message=".*(alpha).*") + counts_filtered = count_mat[np.ix_(keep_nhoods, keep_smp)] design_df_filtered = design_df.iloc[keep_smp].copy()