Source code for scviva.pl.scviva_de

from __future__ import annotations

from typing import TYPE_CHECKING

from scvi.utils import dependencies

if TYPE_CHECKING:
    from collections.abc import Iterable

    import pandas as pd
    from scipy.sparse import csr_matrix

    from scviva.model.utils._scviva_de import DifferentialExpressionResults


[docs] @dependencies("matplotlib") def plot_connectivity_distribution(csr_matrix: csr_matrix) -> None: """Plot a histogram of the number of non-zero entries per row of a sparse matrix.""" import matplotlib.pyplot as plt import numpy as np # Get the number of non-zero entries per row row_counts = np.diff(csr_matrix.indptr) # Create the histogram fig, ax = plt.subplots() ax.hist(row_counts, bins=np.max(row_counts) + 1, density=True) ax.set_xlabel("Number of non-zero entries") ax.set_ylabel("Number of rows") ax.set_title("Histogram of non-zero entries per row") plt.show()
[docs] @dependencies("matplotlib") def plot_niche_de_decision_boundary( results: DifferentialExpressionResults, X: pd.DataFrame | None = None, y: pd.Series | None = None, filter: Iterable | None = None, background_filter: Iterable | None = None, markersize: int = 50, fontsize: int = 10, chosen_colormap: str = "seismic", path_to_save: str | None = None, show_plot: bool = True, dpi: int = 1000, margin: float = 0.1, manual_limits: tuple | None = None, legend_loc: str = "upper right", ) -> None: """Plot the results of the niche differential expression analysis. Parameters ---------- results Fitted :class:`~scviva.model.utils._scviva_de.DifferentialExpressionResults`. X Design matrix: log fold changes for group 1 vs group 2 and neighbors 1 vs group 2. If None, the design matrix from the classifier is used. y Labels: FDR values for group 1 vs neighbors 1. If None, the labels from the classifier are used. filter Genes to include in the plot. background_filter Genes to include in the plot as background. markersize Size of the markers. fontsize Font size for the gene names. chosen_colormap Colormap to use for the decision boundary. path_to_save Path to save the plot. show_plot Whether to show the plot. Default: True. dpi Resolution of the saved plot. margin Margin to add to the plot limits. manual_limits Manual limits for the plot. legend_loc Location of the legend. Returns ------- None """ import matplotlib.colors as mcolors import matplotlib.pyplot as plt import numpy as np from matplotlib.font_manager import FontProperties from sklearn.inspection import DecisionBoundaryDisplay gpc = results.gpc # Set SVG font type to 'none' to keep text as text in SVG files plt.rcParams["svg.fonttype"] = "none" plt.rcParams["font.size"] = fontsize italic_font = FontProperties(style="italic", size=fontsize - 2) if X is None: X = gpc.X_ y = gpc.y_ if filter is None: filter = X.index X_display = X.loc[filter].values fdr_g1_n1_display = y.loc[filter] lfc_g1_g2_display = X.iloc[:, 0].loc[filter] lfc_n1_g2_display = X.iloc[:, 1].loc[filter] gpc.confident_genes = fdr_g1_n1_display.index[fdr_g1_n1_display] if background_filter is not None: X_background = X.loc[background_filter].values lfc_g1_g2_bg = X.iloc[:, 0].loc[background_filter] lfc_n1_g2_bg = X.iloc[:, 1].loc[background_filter] # Determine plot limits using both main and background data, with adjustable margin x_min = min(lfc_g1_g2_display.min(), lfc_g1_g2_bg.min()) - margin x_max = max(lfc_g1_g2_display.max(), lfc_g1_g2_bg.max()) + margin y_min = min(lfc_n1_g2_display.min(), lfc_n1_g2_bg.min()) - margin y_max = max(lfc_n1_g2_display.max(), lfc_n1_g2_bg.max()) + margin else: x_min, x_max = lfc_g1_g2_display.min() - margin, lfc_g1_g2_display.max() + margin y_min, y_max = lfc_n1_g2_display.min() - margin, lfc_n1_g2_display.max() + margin if manual_limits is not None: x_min, x_max, y_min, y_max = manual_limits # Create a larger figure and axes explicitly fig, ax = plt.subplots(figsize=(8, 8)) # Manually create mesh grid for the decision boundary xx, yy = np.meshgrid( np.linspace(x_min, x_max, 100), np.linspace(y_min, y_max, 100), ) # Create a custom colormap with alpha transparency cmap = plt.get_cmap(chosen_colormap) cmap_with_alpha = cmap(np.arange(cmap.N)) cmap_with_alpha[:, -1] = 0.6 # Set the alpha channel chosen_colormap = mcolors.ListedColormap(cmap_with_alpha) # Generate the decision boundary with custom limits disp = DecisionBoundaryDisplay.from_estimator( gpc, np.c_[xx.ravel(), yy.ravel()], response_method="predict_proba", xlabel="LFC group1-group2", ylabel="LFC neighbors1-group2", ax=ax, cmap=chosen_colormap, ) # Set the limits to ensure the decision boundary display matches the full range of data ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) # Plot the identity line using the retrieved limits with np.linspace line_points = np.linspace(min(x_min, y_min), max(x_max, y_max), 100) ax.plot(line_points, line_points, "k--", alpha=0.85, zorder=0) # Plot background points in light grey if background_filter is provided if background_filter is not None: disp.ax_.scatter( X_background[:, 0], X_background[:, 1], c="lightgrey", edgecolor="none", s=markersize, alpha=0.3, ) for _i, gene in enumerate(background_filter): ax.annotate( gene, (lfc_g1_g2_bg[gene], lfc_n1_g2_bg[gene]), xytext=(0, 5), textcoords="offset points", fontsize=fontsize - 2, fontproperties=italic_font, ) # Scatter plot with fixed colors for True (yellow) and False (green) DE genes colors = np.where(fdr_g1_n1_display, "yellow", "green") disp.ax_.scatter(X_display[:, 0], X_display[:, 1], c=colors, edgecolor="k", s=markersize) # Manually add a colorbar for the decision boundary sm = plt.cm.ScalarMappable(cmap=chosen_colormap, norm=mcolors.Normalize(vmin=0, vmax=1)) cbar = plt.colorbar(sm, ax=ax) cbar.set_label("Decision Boundary Probability", rotation=270, labelpad=15) # Add legend for scatter plot legend_elements = [ plt.Line2D( [0], [0], marker="o", color="w", label="DE g1_n1 True", markerfacecolor="yellow", markersize=8, ), plt.Line2D( [0], [0], marker="o", color="w", label="DE g1_n1 False", markerfacecolor="green", markersize=8, ), ] # Add legend for background points if background_filter is provided if background_filter is not None: legend_elements += [ plt.Line2D( [0], [0], marker="o", color="w", label="DE g1_g2 in background", markerfacecolor="lightgrey", markersize=8, ) ] disp.ax_.legend(handles=legend_elements, loc=legend_loc) # Annotate gene names for _i, gene in enumerate(fdr_g1_n1_display.index): ax.annotate( gene, ( lfc_g1_g2_display[gene], lfc_n1_g2_display[gene], ), xytext=(0, 5), textcoords="offset points", fontsize=fontsize, ) ax.set_aspect("auto") # Allow the plot to adjust freely # Adjust layout and save/show plot plt.tight_layout() if path_to_save is not None: plt.savefig( path_to_save, bbox_inches="tight", dpi=dpi, ) if show_plot: plt.show() else: plt.close()