Skip to content

Commit 008740f

Browse files
committed
spatial plotting
1 parent 935af04 commit 008740f

3 files changed

Lines changed: 103 additions & 40 deletions

File tree

src/pySingleCellNet/plotting/dot.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,109 @@
1919
from palettable.scientific.sequential import Batlow_20
2020
from anndata import AnnData
2121
from scipy.sparse import csr_matrix
22-
from sklearn.metrics import f1_score
22+
from scipy import sparse
23+
from typing import Optional, Callable
2324
from ..utils import *
2425

26+
from scipy import sparse
27+
from typing import Optional, Callable
28+
29+
def spatial_two_genes(
30+
adata: AnnData,
31+
gene1: str,
32+
gene2: str,
33+
title: Optional[str] = None,
34+
scale_max_value: float = 2.0,
35+
spot_size: float = 35,
36+
cmap: str = 'RdBu_r',
37+
alpha: float = 0.5,
38+
copy_adata: bool = True,
39+
combine_fun: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None,
40+
**plot_kwargs
41+
) -> None:
42+
"""Plot a custom combination of two gene expressions on a spatial scatter,
43+
scaling only those two genes.
44+
45+
This will (optionally) make a copy of your AnnData, extract expression for
46+
gene1 and gene2, scale each gene vector (zero‐center, unit variance,
47+
clipped to ±scale_max_value), compute a per‐cell combined metric, store it
48+
in `.obs[title]`, and then call `sc.pl.spatial`.
49+
50+
Args:
51+
adata: AnnData with spatial coords and expression in `.X` (or `.layers`).
52+
gene1: Name of the first gene (must be in `adata.var_names`).
53+
gene2: Name of the second gene.
54+
title: Key under which to store the combined metric in `adata.obs` and
55+
plot title. Defaults to `"gene1_gene2"`.
56+
scale_max_value: Maximum absolute value to clip the scaled gene vectors.
57+
Defaults to 2.0.
58+
spot_size: Passed to `sc.pl.spatial(..., spot_size=...)`. Default 35.
59+
cmap: Colormap for `sc.pl.spatial`. Default `'RdBu_r'`.
60+
alpha: Spot transparency for `sc.pl.spatial`. Default 0.5.
61+
copy_adata: If True, operate on a copy. Otherwise overwrite `adata.obs`.
62+
combine_fun: Function `(g1, g2) -> combined`. If None, uses
63+
`(g1 * g2) + g1 - g2`.
64+
**plot_kwargs: Any additional args forwarded to `sc.pl.spatial`.
65+
66+
Returns:
67+
None. Displays a spatial scatter of the combined metric.
68+
"""
69+
# determine obs key / title
70+
if title is None:
71+
title = f"{gene1}_{gene2}"
72+
73+
# copy or in-place
74+
ad = adata.copy() if copy_adata else adata
75+
76+
# extract raw expression
77+
X1 = ad[:, gene1].X
78+
X2 = ad[:, gene2].X
79+
80+
# to 1D numpy arrays
81+
def to_array(mat):
82+
if sparse.issparse(mat):
83+
arr = mat.A.flatten()
84+
else:
85+
arr = np.asarray(mat).flatten()
86+
return arr
87+
88+
g1 = to_array(X1)
89+
g2 = to_array(X2)
90+
91+
# scale each gene individually
92+
def scale_vec(x):
93+
m = x.mean()
94+
s = x.std(ddof=0)
95+
if s == 0:
96+
# avoid divide by zero
97+
scaled = x - m
98+
else:
99+
scaled = (x - m) / s
100+
return np.clip(scaled, -scale_max_value, scale_max_value)
101+
102+
g1_scaled = scale_vec(g1)
103+
g2_scaled = scale_vec(g2)
104+
105+
# combine
106+
if combine_fun is None:
107+
expr = (g1_scaled * g2_scaled) + g1_scaled - g2_scaled
108+
else:
109+
expr = combine_fun(g1_scaled, g2_scaled)
110+
111+
# store and plot
112+
ad.obs[title] = pd.Series(expr, index=ad.obs.index)
113+
sc.pl.spatial(
114+
ad,
115+
color=title,
116+
spot_size=spot_size,
117+
cmap=cmap,
118+
alpha=alpha,
119+
**plot_kwargs
120+
)
121+
122+
123+
124+
25125
def umi_counts_ranked(adata, total_counts_column="total_counts"):
26126
"""
27127
Identifies and plors the knee point of the UMI count distribution in an AnnData object.

src/pySingleCellNet/plotting/scatter.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -73,41 +73,3 @@ def scatter_qc_adata(adata, title_suffix=""):
7373
plt.show()
7474

7575

76-
77-
78-
79-
80-
81-
82-
83-
84-
def scatter_qc_adata(adata, title_suffix=""):
85-
# Extract necessary columns from the adata object
86-
total_counts = adata.obs['total_counts']
87-
n_genes_by_counts = adata.obs['n_genes_by_counts']
88-
pct_counts_mt = adata.obs['pct_counts_mt']
89-
90-
# Create a figure with two subplots (1 row, 2 columns)
91-
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
92-
93-
# First subplot: total_counts vs n_genes_by_counts, colored by pct_counts_mt
94-
scatter1 = axes[0].scatter(total_counts, n_genes_by_counts, c=pct_counts_mt, cmap='viridis', alpha=0.5, s=1)
95-
axes[0].set_xlabel(f'Total Counts ({title_suffix})')
96-
axes[0].set_ylabel(f'Number of Genes by Counts ({title_suffix})')
97-
axes[0].set_title(f'Total Counts vs Genes ({title_suffix})')
98-
# Add a colorbar
99-
fig.colorbar(scatter1, ax=axes[0], label='% Mito')
100-
101-
# Second subplot: n_genes_by_counts vs pct_counts_mt
102-
scatter2 = axes[1].scatter(n_genes_by_counts, pct_counts_mt, alpha=0.5, s=1)
103-
axes[1].set_xlabel(f'Number of Genes by Counts ({title_suffix})')
104-
axes[1].set_ylabel(f'% Mito ({title_suffix})')
105-
axes[1].set_title(f'Genes vs % Mito ({title_suffix})')
106-
107-
# Adjust layout to avoid overlap
108-
plt.tight_layout()
109-
110-
# Show the plot
111-
plt.show()
112-
113-

src/pySingleCellNet/utils/gene.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def find_knn_modules(
9292
n_pcs = elbow + npcs_adjust
9393

9494
# Compute the kNN graph using correlation as the metric
95-
sc.pp.neighbors(adata_transposed, n_neighbors=knn, n_pcs=n_pcs, metric='correlation')
95+
# sc.pp.neighbors(adata_transposed, n_neighbors=knn, n_pcs=n_pcs, metric='correlation')
96+
sc.pp.neighbors(adata_transposed, n_neighbors=knn, n_pcs=n_pcs, metric='euclidean')
9697

9798
# Perform Leiden clustering with an explicit resolution keyword
9899
sc.tl.leiden(adata_transposed, resolution=leiden_resolution)

0 commit comments

Comments
 (0)