cytocommunity2 1.0.3__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cytocommunity2-1.0.3/src/cytocommunity2.egg-info → cytocommunity2-1.0.4}/PKG-INFO +3 -1
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/README.md +2 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/pyproject.toml +1 -1
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/__init__.py +1 -1
- cytocommunity2-1.0.4/src/cytocommunity2/colors.py +34 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/config.py +1 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/cca.py +14 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/differential_composition.py +4 -10
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/dominant_celltype.py +4 -18
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/enrichment.py +22 -12
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/moran.py +2 -3
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/cn_selection.py +12 -27
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/coherence.py +4 -13
- cytocommunity2-1.0.4/src/cytocommunity2/downstream/plotting/common.py +146 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/communication.py +26 -39
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/dominant.py +15 -24
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/dotplots.py +53 -114
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/recurrence.py +63 -105
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/visualization/spatial.py +2 -1
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4/src/cytocommunity2.egg-info}/PKG-INFO +3 -1
- cytocommunity2-1.0.3/src/cytocommunity2/colors.py +0 -14
- cytocommunity2-1.0.3/src/cytocommunity2/downstream/plotting/common.py +0 -79
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/LICENSE +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/setup.cfg +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/_io.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/__init__.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/coherence.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/__init__.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/between.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/utils.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/within.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/__init__.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/recurrence.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/wilcoxon.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/ensemble.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/__init__.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/api.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/dataset.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/model.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/selection.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/learning/training.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/paths.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/visualization/__init__.py +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2.egg-info/SOURCES.txt +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2.egg-info/dependency_links.txt +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2.egg-info/requires.txt +0 -0
- {cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cytocommunity2
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Weakly supervised cellular-neighborhood learning for spatial omics
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -30,6 +30,8 @@ Dynamic: license-file
|
|
|
30
30
|
|
|
31
31
|
## Overview
|
|
32
32
|
|
|
33
|
+

|
|
34
|
+
|
|
33
35
|
To enhance CytoCommunity (https://github.com/huBioinfo/CytoCommunity), we
|
|
34
36
|
present CytoCommunity2, **a unified weakly-supervised framework** for
|
|
35
37
|
identifying and comparing tissue cellular neighborhoods (TCNs or CNs) across
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
## Overview
|
|
12
12
|
|
|
13
|
+

|
|
14
|
+
|
|
13
15
|
To enhance CytoCommunity (https://github.com/huBioinfo/CytoCommunity), we
|
|
14
16
|
present CytoCommunity2, **a unified weakly-supervised framework** for
|
|
15
17
|
identifying and comparing tissue cellular neighborhoods (TCNs or CNs) across
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Shared categorical colors used across CytoCommunity2 figures."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
CN_PALETTE = "d3_category20"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def cn_color_map(cns):
|
|
8
|
+
"""Map CN numbers or CN-prefixed labels to fixed spatial/OC colors."""
|
|
9
|
+
import sci_palettes
|
|
10
|
+
|
|
11
|
+
palette = list(sci_palettes.PALETTES[CN_PALETTE].values())
|
|
12
|
+
colors = {}
|
|
13
|
+
for cn in cns:
|
|
14
|
+
number = int(str(cn).removeprefix("CN"))
|
|
15
|
+
if not 1 <= number <= len(palette):
|
|
16
|
+
raise ValueError(
|
|
17
|
+
f"CN {cn!r} is outside the 1..{len(palette)} range of "
|
|
18
|
+
f"palette {CN_PALETTE!r}."
|
|
19
|
+
)
|
|
20
|
+
colors[cn] = palette[number - 1]
|
|
21
|
+
return colors
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def celltype_color_map(celltypes):
|
|
25
|
+
"""Return the canonical cell-type colors in the supplied display order."""
|
|
26
|
+
import matplotlib.pyplot as plt
|
|
27
|
+
|
|
28
|
+
palette = []
|
|
29
|
+
for name in ("tab20", "tab20b", "tab20c", "Set1", "Dark2", "Accent", "Paired"):
|
|
30
|
+
palette.extend(plt.get_cmap(name).colors)
|
|
31
|
+
return {
|
|
32
|
+
celltype: palette[index]
|
|
33
|
+
for index, celltype in enumerate(celltypes)
|
|
34
|
+
}
|
{cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/communication/cca.py
RENAMED
|
@@ -60,10 +60,23 @@ def build_score_list(frame, cn_list):
|
|
|
60
60
|
return matrices
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def select_top_features(matrix, top_n=5):
|
|
64
|
+
"""Select cell types by decreasing mean enrichment before variance filtering."""
|
|
65
|
+
if matrix is None or matrix.shape[1] == 0:
|
|
66
|
+
return matrix
|
|
67
|
+
means = matrix.mean(axis=0).fillna(-np.inf)
|
|
68
|
+
selected = means.sort_values(ascending=False, kind="stable").head(top_n).index
|
|
69
|
+
return matrix.loc[:, selected]
|
|
70
|
+
|
|
71
|
+
|
|
63
72
|
def run_cca_for_scores(
|
|
64
73
|
score_list, cn_list, communication_params, random_seed, condition=None
|
|
65
74
|
):
|
|
66
75
|
"""Run CCA and permutation tests on in-memory enrichment matrices."""
|
|
76
|
+
score_list = {
|
|
77
|
+
cn: select_top_features(matrix, communication_params["cca_top_celltypes"])
|
|
78
|
+
for cn, matrix in score_list.items()
|
|
79
|
+
}
|
|
67
80
|
result_rows = []
|
|
68
81
|
cache = {}
|
|
69
82
|
for cn_a, cn_b in combinations(map(str, cn_list), 2):
|
|
@@ -312,5 +325,6 @@ __all__ = [
|
|
|
312
325
|
"pair_rng",
|
|
313
326
|
"run_cca_analysis",
|
|
314
327
|
"run_cca_for_scores",
|
|
328
|
+
"select_top_features",
|
|
315
329
|
"write_cca_loadings",
|
|
316
330
|
]
|
|
@@ -7,31 +7,25 @@ import pandas as pd
|
|
|
7
7
|
|
|
8
8
|
from ..config import CytoCommunityConfig
|
|
9
9
|
from ..paths import build_paths
|
|
10
|
-
from .
|
|
10
|
+
from .enrichment import load_enrichment_by_sample
|
|
11
11
|
from .wilcoxon import rank_sum_test
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def load_condition_enrichment(enrichment_dir, input_dir):
|
|
15
15
|
"""Load enrichment matrices together with each sample's graph label."""
|
|
16
|
-
celltypes, matrices =
|
|
16
|
+
celltypes, matrices = load_enrichment_by_sample(
|
|
17
17
|
enrichment_dir, input_dir
|
|
18
18
|
)
|
|
19
19
|
input_dir = Path(input_dir)
|
|
20
|
-
samples = (
|
|
21
|
-
pd.read_csv(input_dir / "ImageNameList.txt", sep="\t", header=None)
|
|
22
|
-
.iloc[:, 0]
|
|
23
|
-
.astype(str)
|
|
24
|
-
.tolist()
|
|
25
|
-
)
|
|
26
20
|
records = [
|
|
27
21
|
{
|
|
28
22
|
"sample": sample,
|
|
29
23
|
"condition": int(
|
|
30
24
|
np.loadtxt(input_dir / f"{sample}_GraphLabel.txt", dtype="int64")
|
|
31
25
|
),
|
|
32
|
-
"matrix":
|
|
26
|
+
"matrix": matrix,
|
|
33
27
|
}
|
|
34
|
-
for
|
|
28
|
+
for sample, matrix in matrices.items()
|
|
35
29
|
]
|
|
36
30
|
return celltypes, records
|
|
37
31
|
|
{cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/dominant_celltype.py
RENAMED
|
@@ -9,6 +9,7 @@ from scipy.cluster.hierarchy import leaves_list, linkage
|
|
|
9
9
|
|
|
10
10
|
from ..config import CytoCommunityConfig
|
|
11
11
|
from ..paths import build_paths
|
|
12
|
+
from .enrichment import load_enrichment_by_sample
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclass(frozen=True)
|
|
@@ -22,24 +23,9 @@ class DominantCellTypeResult:
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def load_enrichment_matrices(enrichment_dir, input_dir):
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
source_celltypes = (
|
|
29
|
-
pd.read_csv(enrichment_dir / "CellTypeVec_List.csv")
|
|
30
|
-
.iloc[:, 0]
|
|
31
|
-
.astype(str)
|
|
32
|
-
.str.strip()
|
|
33
|
-
.tolist()
|
|
34
|
-
)
|
|
35
|
-
samples = pd.read_csv(input_dir / "ImageNameList.txt", header=None).iloc[:, 0]
|
|
36
|
-
matrices = [
|
|
37
|
-
pd.read_csv(
|
|
38
|
-
enrichment_dir / f"{sample}_EnrichScoreMatrix.csv", header=None
|
|
39
|
-
).to_numpy(dtype=float)
|
|
40
|
-
for sample in samples
|
|
41
|
-
]
|
|
42
|
-
return source_celltypes, np.stack(matrices, axis=0)
|
|
26
|
+
"""Retain the stacked-array interface for dominant-cell-type analysis."""
|
|
27
|
+
celltypes, matrices = load_enrichment_by_sample(enrichment_dir, input_dir)
|
|
28
|
+
return celltypes, np.stack(list(matrices.values()), axis=0)
|
|
43
29
|
|
|
44
30
|
|
|
45
31
|
def analyze_dominant_celltypes(
|
|
@@ -9,6 +9,26 @@ from scipy.stats import false_discovery_control, hypergeom
|
|
|
9
9
|
from .._io import read_nonempty_lines
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def load_enrichment_by_sample(enrichment_dir, input_dir):
|
|
13
|
+
"""Load enrichment matrices keyed by sample, in the input-list order."""
|
|
14
|
+
enrichment_dir = Path(enrichment_dir)
|
|
15
|
+
celltypes = (
|
|
16
|
+
pd.read_csv(enrichment_dir / "CellTypeVec_List.csv")
|
|
17
|
+
.iloc[:, 0]
|
|
18
|
+
.astype(str)
|
|
19
|
+
.str.strip()
|
|
20
|
+
.tolist()
|
|
21
|
+
)
|
|
22
|
+
samples = read_nonempty_lines(Path(input_dir) / "ImageNameList.txt")
|
|
23
|
+
matrices = {
|
|
24
|
+
sample: pd.read_csv(
|
|
25
|
+
enrichment_dir / f"{sample}_EnrichScoreMatrix.csv", header=None
|
|
26
|
+
).to_numpy(dtype=float)
|
|
27
|
+
for sample in samples
|
|
28
|
+
}
|
|
29
|
+
return celltypes, matrices
|
|
30
|
+
|
|
31
|
+
|
|
12
32
|
def calculate_enrichment_matrix(
|
|
13
33
|
cell_labels,
|
|
14
34
|
cn_labels,
|
|
@@ -126,19 +146,9 @@ def calculate_enrichment(
|
|
|
126
146
|
def export_enrichment_long(dataset, enrichment_dir, output_file):
|
|
127
147
|
"""Export the selected model's enrichment matrices for communication analysis."""
|
|
128
148
|
dataset = Path(dataset)
|
|
129
|
-
|
|
130
|
-
samples = read_nonempty_lines(dataset / "ImageNameList.txt")
|
|
131
|
-
celltypes = (
|
|
132
|
-
pd.read_csv(enrichment_dir / "CellTypeVec_List.csv")
|
|
133
|
-
.iloc[:, 0]
|
|
134
|
-
.astype(str)
|
|
135
|
-
.tolist()
|
|
136
|
-
)
|
|
149
|
+
celltypes, matrices = load_enrichment_by_sample(enrichment_dir, dataset)
|
|
137
150
|
tables = []
|
|
138
|
-
for sample in
|
|
139
|
-
matrix = pd.read_csv(
|
|
140
|
-
enrichment_dir / f"{sample}_EnrichScoreMatrix.csv", header=None
|
|
141
|
-
).to_numpy(dtype=float)
|
|
151
|
+
for sample, matrix in matrices.items():
|
|
142
152
|
table = (
|
|
143
153
|
pd.DataFrame(
|
|
144
154
|
matrix,
|
|
@@ -7,6 +7,7 @@ from libpysal.weights import DistanceBand
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
9
|
|
|
10
|
+
from .._io import read_nonempty_lines
|
|
10
11
|
from ..config import CytoCommunityConfig
|
|
11
12
|
from ..paths import build_paths
|
|
12
13
|
from .wilcoxon import rank_sum_test
|
|
@@ -25,9 +26,7 @@ MORAN_COLUMNS = [
|
|
|
25
26
|
def load_graph_labels(input_dir):
|
|
26
27
|
"""Read each sample's condition from its GraphLabel file."""
|
|
27
28
|
input_dir = Path(input_dir)
|
|
28
|
-
samples =
|
|
29
|
-
input_dir / "ImageNameList.txt", sep="\t", header=None, names=["Sample"]
|
|
30
|
-
)["Sample"].astype(str)
|
|
29
|
+
samples = read_nonempty_lines(input_dir / "ImageNameList.txt")
|
|
31
30
|
return {
|
|
32
31
|
sample: int(np.loadtxt(input_dir / f"{sample}_GraphLabel.txt", dtype="int64"))
|
|
33
32
|
for sample in samples
|
{cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/cn_selection.py
RENAMED
|
@@ -6,47 +6,32 @@ import matplotlib.pyplot as plt
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
from ...paths import build_paths
|
|
9
|
+
from .common import save_figure
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def
|
|
12
|
-
output_stem = Path(output_stem)
|
|
13
|
-
output_stem.parent.mkdir(parents=True, exist_ok=True)
|
|
14
|
-
figure.savefig(output_stem.with_suffix(".pdf"), bbox_inches="tight")
|
|
15
|
-
figure.savefig(output_stem.with_suffix(".png"), dpi=300, bbox_inches="tight")
|
|
16
|
-
plt.close(figure)
|
|
17
|
-
return output_stem
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _plot_cri(summary, output_stem):
|
|
12
|
+
def _plot_cri(summary, output_stem, *, png=False):
|
|
21
13
|
values = summary["CRI"]
|
|
22
14
|
k_values = summary["NumCN"].astype(int)
|
|
23
15
|
best_index = values.idxmax()
|
|
24
16
|
best_k = int(k_values.loc[best_index])
|
|
25
|
-
best_value = float(values.loc[best_index])
|
|
26
17
|
|
|
27
|
-
figure, axis = plt.subplots(figsize=(
|
|
18
|
+
figure, axis = plt.subplots(figsize=(7.4, 6.0))
|
|
28
19
|
axis.bar(k_values, values, width=0.62, color="#c9c9c9", edgecolor="none")
|
|
29
20
|
axis.plot(k_values, values, color="black", linestyle=(0, (2, 2)), marker="o")
|
|
30
|
-
axis.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
xytext=(0, 10),
|
|
34
|
-
textcoords="offset points",
|
|
35
|
-
ha="center",
|
|
36
|
-
va="bottom",
|
|
37
|
-
fontsize=11,
|
|
38
|
-
)
|
|
39
|
-
axis.set_xlabel(r"$K$", fontsize=13)
|
|
40
|
-
axis.set_ylabel("CN resolution index (CRI)", fontsize=12)
|
|
21
|
+
axis.set_title(rf"Optimal: $K$={best_k}", fontsize=18, pad=10, loc="center")
|
|
22
|
+
axis.set_xlabel(r"$K$", fontsize=18)
|
|
23
|
+
axis.set_ylabel("CN resolution index", fontsize=16)
|
|
41
24
|
axis.set_xticks(k_values)
|
|
42
25
|
axis.spines[["top", "right"]].set_visible(False)
|
|
43
|
-
axis.tick_params(labelsize=
|
|
26
|
+
axis.tick_params(labelsize=13)
|
|
44
27
|
axis.margins(x=0.04)
|
|
45
28
|
figure.tight_layout()
|
|
46
|
-
|
|
29
|
+
output = save_figure(figure, output_stem, png=png)
|
|
30
|
+
plt.close(figure)
|
|
31
|
+
return output
|
|
47
32
|
|
|
48
33
|
|
|
49
|
-
def plot_cn_selection_scores(selection_summary, config):
|
|
34
|
+
def plot_cn_selection_scores(selection_summary, config, *, png=False):
|
|
50
35
|
"""Plot the composite CRI score across candidate CN numbers."""
|
|
51
36
|
if isinstance(selection_summary, (str, Path)):
|
|
52
37
|
summary = pd.read_csv(selection_summary)
|
|
@@ -55,4 +40,4 @@ def plot_cn_selection_scores(selection_summary, config):
|
|
|
55
40
|
|
|
56
41
|
summary = summary.sort_values("NumCN").reset_index(drop=True)
|
|
57
42
|
output_dir = build_paths(config).downstream_plot / "CNSelection"
|
|
58
|
-
return _plot_cri(summary, output_dir / "CRI_by_K")
|
|
43
|
+
return _plot_cri(summary, output_dir / "CRI_by_K", png=png)
|
{cytocommunity2-1.0.3 → cytocommunity2-1.0.4}/src/cytocommunity2/downstream/plotting/coherence.py
RENAMED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
"""Spatial coherence figures."""
|
|
2
2
|
|
|
3
3
|
from ...paths import build_paths
|
|
4
|
+
from .common import configure_style, save_figure
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def plot_coherence_results(result, config):
|
|
7
8
|
"""Save CHAOS and PAS boxplots."""
|
|
8
|
-
import matplotlib as mpl
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
10
|
import seaborn as sns
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
"font.family": "Arial",
|
|
14
|
-
"font.size": 12,
|
|
15
|
-
"axes.linewidth": 0.8,
|
|
16
|
-
"lines.linewidth": 1.5,
|
|
17
|
-
"xtick.labelsize": 10,
|
|
18
|
-
"ytick.labelsize": 10,
|
|
19
|
-
"pdf.fonttype": 42,
|
|
20
|
-
})
|
|
12
|
+
configure_style()
|
|
21
13
|
output_dir = build_paths(config).downstream_plot / "Coherence"
|
|
22
14
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
23
15
|
frame = result.loc[
|
|
@@ -28,7 +20,7 @@ def plot_coherence_results(result, config):
|
|
|
28
20
|
plt.rcParams["grid.alpha"] = 0.25
|
|
29
21
|
outputs = {}
|
|
30
22
|
for column, axis_max in (("CHAOS", 0.02), ("PAS", 0.5)):
|
|
31
|
-
figure, axis = plt.subplots(figsize=(8, 6)
|
|
23
|
+
figure, axis = plt.subplots(figsize=(8, 6))
|
|
32
24
|
sns.boxplot(
|
|
33
25
|
data=frame,
|
|
34
26
|
y=column,
|
|
@@ -52,8 +44,7 @@ def plot_coherence_results(result, config):
|
|
|
52
44
|
axis.spines["top"].set_visible(False)
|
|
53
45
|
axis.spines["right"].set_visible(False)
|
|
54
46
|
figure.tight_layout()
|
|
55
|
-
output = output_dir / f"{column}.pdf"
|
|
56
|
-
figure.savefig(output, dpi=1200, bbox_inches="tight")
|
|
47
|
+
output = save_figure(figure, output_dir / f"{column}.pdf")
|
|
57
48
|
plt.close(figure)
|
|
58
49
|
outputs[column] = output
|
|
59
50
|
return outputs
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Plotting primitives shared by multiple downstream figures."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def configure_style(*, line_width=1.5, ps_fonttype=None):
|
|
9
|
+
"""Apply the shared figure style while allowing per-plot line widths."""
|
|
10
|
+
import matplotlib as mpl
|
|
11
|
+
|
|
12
|
+
style = {
|
|
13
|
+
"font.family": "Arial",
|
|
14
|
+
"font.size": 12,
|
|
15
|
+
"axes.linewidth": 0.8,
|
|
16
|
+
"lines.linewidth": line_width,
|
|
17
|
+
"xtick.labelsize": 10,
|
|
18
|
+
"ytick.labelsize": 10,
|
|
19
|
+
"pdf.fonttype": 42,
|
|
20
|
+
}
|
|
21
|
+
if ps_fonttype is not None:
|
|
22
|
+
style["ps.fonttype"] = ps_fonttype
|
|
23
|
+
mpl.rcParams.update(style)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def save_figure(figure, output, *, png=False):
|
|
27
|
+
"""Save a PDF, plus a PNG when explicitly requested for notebook display."""
|
|
28
|
+
output = Path(output).with_suffix(".pdf")
|
|
29
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
figure.savefig(output, bbox_inches="tight", dpi=1200)
|
|
31
|
+
if png:
|
|
32
|
+
figure.savefig(output.with_suffix(".png"), bbox_inches="tight", dpi=300)
|
|
33
|
+
return output
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def add_colorbar(figure, axis, norm, title, ticks=None, *, cmap, orientation="vertical"):
|
|
37
|
+
"""Draw a borderless color scale with a side label and visible ticks."""
|
|
38
|
+
import matplotlib as mpl
|
|
39
|
+
from matplotlib.ticker import MaxNLocator, StrMethodFormatter
|
|
40
|
+
|
|
41
|
+
scalar = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)
|
|
42
|
+
if ticks is None:
|
|
43
|
+
step = 1.0 if norm.vmax - norm.vmin > 4 else 0.5
|
|
44
|
+
ticks = np.arange(np.ceil(norm.vmin / step), np.floor(norm.vmax / step) + 1) * step
|
|
45
|
+
if len(ticks) < 2:
|
|
46
|
+
ticks = MaxNLocator(nbins=4, steps=[1, 2, 5, 10]).tick_values(norm.vmin, norm.vmax)
|
|
47
|
+
ticks = ticks[(ticks >= norm.vmin) & (ticks <= norm.vmax)]
|
|
48
|
+
colorbar = figure.colorbar(scalar, cax=axis, ticks=ticks, orientation=orientation)
|
|
49
|
+
colorbar.formatter = StrMethodFormatter("{x:g}")
|
|
50
|
+
colorbar.update_ticks()
|
|
51
|
+
colorbar.set_label(title, fontsize=12, labelpad=10)
|
|
52
|
+
if orientation == "vertical":
|
|
53
|
+
colorbar.ax.yaxis.set_label_position("left")
|
|
54
|
+
colorbar.ax.tick_params(
|
|
55
|
+
axis="y" if orientation == "vertical" else "x",
|
|
56
|
+
labelsize=10, length=3, width=0.8, pad=5, direction="out",
|
|
57
|
+
**({"right": True, "left": False, "labelright": True, "labelleft": False}
|
|
58
|
+
if orientation == "vertical" else
|
|
59
|
+
{"bottom": True, "top": False, "labelbottom": True, "labeltop": False}),
|
|
60
|
+
)
|
|
61
|
+
colorbar.outline.set_visible(False)
|
|
62
|
+
return colorbar
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def save_legend(handles, labels, output, *, title=None, columns=1):
|
|
66
|
+
"""Save a categorical legend separately from its main figure."""
|
|
67
|
+
import matplotlib.pyplot as plt
|
|
68
|
+
|
|
69
|
+
figure, axis = plt.subplots(figsize=(max(3.4, 1.7 * columns), 2.5))
|
|
70
|
+
axis.axis("off")
|
|
71
|
+
axis.legend(handles, labels, title=title, ncol=columns, loc="center", frameon=False)
|
|
72
|
+
output = save_figure(figure, output)
|
|
73
|
+
plt.close(figure)
|
|
74
|
+
return output
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def dot_size(pvalues):
|
|
78
|
+
strength = np.minimum(
|
|
79
|
+
-np.log10(np.clip(np.asarray(pvalues, dtype=float), 1e-300, 1.0)),
|
|
80
|
+
5.0,
|
|
81
|
+
)
|
|
82
|
+
return legend_size(strength)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def legend_size(strength):
|
|
86
|
+
return 30.0 + 600.0 * np.asarray(strength, dtype=float) / 5.0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def plot_enrichment_scatter(
|
|
90
|
+
x,
|
|
91
|
+
y,
|
|
92
|
+
xlabel,
|
|
93
|
+
ylabel,
|
|
94
|
+
rho,
|
|
95
|
+
pval,
|
|
96
|
+
out_pdf,
|
|
97
|
+
enrichment_score_max=20.0,
|
|
98
|
+
):
|
|
99
|
+
"""Draw the shared enrichment-score scatter style."""
|
|
100
|
+
import matplotlib.pyplot as plt
|
|
101
|
+
import matplotlib.ticker as ticker
|
|
102
|
+
|
|
103
|
+
figure, axis = plt.subplots(figsize=(6, 6))
|
|
104
|
+
x = np.asarray(x, dtype=float)
|
|
105
|
+
y = np.asarray(y, dtype=float)
|
|
106
|
+
valid = np.isfinite(x) & np.isfinite(y)
|
|
107
|
+
x, y = x[valid], y[valid]
|
|
108
|
+
if len(x) >= 2 and len(np.unique(x)) > 1 and len(np.unique(y)) > 1:
|
|
109
|
+
slope, intercept = np.polyfit(x, y, 1)
|
|
110
|
+
x_line = np.linspace(float(np.min(x)), float(np.max(x)), 200)
|
|
111
|
+
axis.plot(
|
|
112
|
+
x_line,
|
|
113
|
+
slope * x_line + intercept,
|
|
114
|
+
linewidth=1.2,
|
|
115
|
+
color="black",
|
|
116
|
+
zorder=1,
|
|
117
|
+
)
|
|
118
|
+
axis.scatter(x, y, s=30, edgecolor="black", alpha=0.9, zorder=2)
|
|
119
|
+
axis.set_xlabel(xlabel, fontsize=10)
|
|
120
|
+
axis.set_ylabel(ylabel, fontsize=10)
|
|
121
|
+
interval = enrichment_score_max / 8
|
|
122
|
+
margin = interval / 5
|
|
123
|
+
ticks = np.linspace(0.0, enrichment_score_max, 9)
|
|
124
|
+
axis.set_xlim(-margin, enrichment_score_max + margin)
|
|
125
|
+
axis.set_ylim(-margin, enrichment_score_max + margin)
|
|
126
|
+
axis.set_xticks(ticks)
|
|
127
|
+
axis.set_yticks(ticks)
|
|
128
|
+
axis.xaxis.set_major_formatter(ticker.FormatStrFormatter("%.1f"))
|
|
129
|
+
axis.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.1f"))
|
|
130
|
+
axis.tick_params(axis="x", labelrotation=30, labelsize=10)
|
|
131
|
+
axis.tick_params(axis="y", labelsize=10)
|
|
132
|
+
axis.spines["top"].set_visible(False)
|
|
133
|
+
axis.spines["right"].set_visible(False)
|
|
134
|
+
axis.text(
|
|
135
|
+
0.80,
|
|
136
|
+
0.98,
|
|
137
|
+
f"rho = {rho:.2f}\np = {pval:.2g}",
|
|
138
|
+
transform=axis.transAxes,
|
|
139
|
+
ha="left",
|
|
140
|
+
va="top",
|
|
141
|
+
fontsize=10,
|
|
142
|
+
)
|
|
143
|
+
figure.tight_layout()
|
|
144
|
+
output = save_figure(figure, out_pdf)
|
|
145
|
+
plt.close(figure)
|
|
146
|
+
return output
|