cytocommunity2 1.0.1__tar.gz → 1.0.2__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.1/src/cytocommunity2.egg-info → cytocommunity2-1.0.2}/PKG-INFO +1 -1
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/pyproject.toml +1 -1
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/__init__.py +1 -1
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/config.py +1 -1
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/differential_composition.py +30 -11
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/dominant_celltype.py +19 -2
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/dominant.py +31 -5
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/dotplots.py +83 -15
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/selection.py +39 -12
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2/src/cytocommunity2.egg-info}/PKG-INFO +1 -1
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/LICENSE +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/README.md +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/setup.cfg +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/colors.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/__init__.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/coherence.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/__init__.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/between.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/cca.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/utils.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/within.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/moran.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/__init__.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/cn_selection.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/coherence.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/common.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/communication.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/composition.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/recurrence.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/runtime.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/ensemble.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/__init__.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/api.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/dataset.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/model.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/learning/training.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/paths.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/visualization/__init__.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/visualization/spatial.py +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2.egg-info/SOURCES.txt +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2.egg-info/dependency_links.txt +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2.egg-info/requires.txt +0 -0
- {cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2.egg-info/top_level.txt +0 -0
|
@@ -13,7 +13,7 @@ DEFAULT_CONFIG_FILE = "hyperparameters.json"
|
|
|
13
13
|
# belong in hyperparameters.json.
|
|
14
14
|
FIXED_DOWNSTREAM = {
|
|
15
15
|
"dominant_cell_type": {"frequency_threshold": 0.5},
|
|
16
|
-
"differential_composed": {"p_value_threshold": 0.05},
|
|
16
|
+
"differential_composed": {"p_value_threshold": 0.05, "minimum_samples": 3},
|
|
17
17
|
"moran": {
|
|
18
18
|
"minimum_positive_count": 2,
|
|
19
19
|
"minimum_cn_cells": 10,
|
|
@@ -66,11 +66,13 @@ def build_label_comparisons(labels, mode):
|
|
|
66
66
|
raise ValueError(f"Unknown comparison mode: {mode}")
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def composition_wilcoxon_tests(
|
|
69
|
+
def composition_wilcoxon_tests(
|
|
70
|
+
cell_type, target_values, reference_values, *, minimum_samples=3
|
|
71
|
+
):
|
|
70
72
|
"""Run one-sided asymptotic Wilcoxon rank-sum tests."""
|
|
71
73
|
target = np.asarray(target_values, dtype=float)
|
|
72
74
|
reference = np.asarray(reference_values, dtype=float)
|
|
73
|
-
if len(target) <
|
|
75
|
+
if len(target) < minimum_samples or len(reference) < minimum_samples:
|
|
74
76
|
return None
|
|
75
77
|
if not np.isfinite(target).all() or not np.isfinite(reference).all():
|
|
76
78
|
return None
|
|
@@ -100,10 +102,12 @@ def composition_wilcoxon_tests(cell_type, target_values, reference_values):
|
|
|
100
102
|
return rows
|
|
101
103
|
|
|
102
104
|
|
|
103
|
-
def analyze_differential_composition(matrix_records, celltypes, *, mode):
|
|
105
|
+
def analyze_differential_composition(matrix_records, celltypes, *, mode, minimum_samples=3):
|
|
104
106
|
"""Calculate all directional composition comparisons in memory.
|
|
105
107
|
|
|
106
108
|
The result maps comparison names to maps of CN names and result tables.
|
|
109
|
+
Comparisons with fewer than ``minimum_samples`` samples on either side
|
|
110
|
+
have empty maps.
|
|
107
111
|
"""
|
|
108
112
|
normalized_records = [
|
|
109
113
|
{**record, "matrix": np.asarray(record["matrix"], dtype=float)}
|
|
@@ -114,8 +118,20 @@ def analyze_differential_composition(matrix_records, celltypes, *, mode):
|
|
|
114
118
|
labels = sorted({record["condition"] for record in normalized_records})
|
|
115
119
|
comparisons = build_label_comparisons(labels, mode)
|
|
116
120
|
outputs = {comparison["name"]: {} for comparison in comparisons}
|
|
121
|
+
eligible = [
|
|
122
|
+
comparison
|
|
123
|
+
for comparison in comparisons
|
|
124
|
+
if sum(
|
|
125
|
+
record["condition"] in comparison["target_labels"]
|
|
126
|
+
for record in normalized_records
|
|
127
|
+
) >= minimum_samples
|
|
128
|
+
and sum(
|
|
129
|
+
record["condition"] in comparison["reference_labels"]
|
|
130
|
+
for record in normalized_records
|
|
131
|
+
) >= minimum_samples
|
|
132
|
+
]
|
|
117
133
|
for cn_index in range(expected_shape[1]):
|
|
118
|
-
for comparison in
|
|
134
|
+
for comparison in eligible:
|
|
119
135
|
rows = []
|
|
120
136
|
for cell_index, cell_type in enumerate(celltypes):
|
|
121
137
|
target = [
|
|
@@ -129,7 +145,7 @@ def analyze_differential_composition(matrix_records, celltypes, *, mode):
|
|
|
129
145
|
if record["condition"] in comparison["reference_labels"]
|
|
130
146
|
]
|
|
131
147
|
result = composition_wilcoxon_tests(
|
|
132
|
-
cell_type, target, reference
|
|
148
|
+
cell_type, target, reference, minimum_samples=minimum_samples
|
|
133
149
|
)
|
|
134
150
|
if result is not None:
|
|
135
151
|
rows.extend(result)
|
|
@@ -147,16 +163,19 @@ def run_differential_composition_analysis(
|
|
|
147
163
|
paths.enrichment, paths.input_dir
|
|
148
164
|
)
|
|
149
165
|
outputs = analyze_differential_composition(
|
|
150
|
-
records, celltypes, mode=config.downstream["ttest_mode"]
|
|
166
|
+
records, celltypes, mode=config.downstream["ttest_mode"],
|
|
167
|
+
minimum_samples=config.downstream["differential_composed"]["minimum_samples"],
|
|
151
168
|
)
|
|
152
169
|
output_dir = Path(output_dir or paths.downstream_data / "DifferentialComposed")
|
|
153
170
|
for comparison, cn_tables in outputs.items():
|
|
154
171
|
comparison_dir = output_dir / comparison
|
|
155
|
-
comparison_dir.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
if comparison_dir.exists():
|
|
173
|
+
for old_file in comparison_dir.glob("CN*.csv"):
|
|
174
|
+
old_file.unlink()
|
|
175
|
+
if cn_tables:
|
|
176
|
+
comparison_dir.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
for cn, table in cn_tables.items():
|
|
178
|
+
table.to_csv(comparison_dir / f"{cn}_{comparison}.csv", index=False)
|
|
160
179
|
return outputs
|
|
161
180
|
|
|
162
181
|
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/dominant_celltype.py
RENAMED
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
|
+
from scipy.cluster.hierarchy import leaves_list, linkage
|
|
8
9
|
|
|
9
10
|
from ..config import CytoCommunityConfig
|
|
10
11
|
from ..paths import build_paths
|
|
@@ -16,6 +17,8 @@ class DominantCellTypeResult:
|
|
|
16
17
|
|
|
17
18
|
frequency: pd.DataFrame
|
|
18
19
|
dominant_cell_types: pd.DataFrame
|
|
20
|
+
cn_order: tuple[str, ...] = ()
|
|
21
|
+
cn_linkage: np.ndarray | None = None
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
def load_enrichment_matrices(enrichment_dir, input_dir):
|
|
@@ -46,12 +49,26 @@ def analyze_dominant_celltypes(
|
|
|
46
49
|
score_threshold,
|
|
47
50
|
frequency_threshold,
|
|
48
51
|
):
|
|
49
|
-
"""Compute dominant cell types
|
|
52
|
+
"""Compute dominant cell types and cluster CNs by enrichment frequency."""
|
|
50
53
|
matrices = np.asarray(enrichment_matrices, dtype=float)
|
|
51
54
|
|
|
52
55
|
frequencies = (matrices > score_threshold).mean(axis=0)
|
|
53
56
|
cn_names = [f"CN{index}" for index in range(1, matrices.shape[2] + 1)]
|
|
54
57
|
frequency = pd.DataFrame(frequencies, index=celltypes, columns=cn_names)
|
|
58
|
+
# Each CN is represented by its across-sample enrichment frequency for
|
|
59
|
+
# every cell type, the same values displayed in the heatmap.
|
|
60
|
+
cn_linkage = None
|
|
61
|
+
if len(cn_names) > 1:
|
|
62
|
+
cn_linkage = linkage(
|
|
63
|
+
frequency.to_numpy().T,
|
|
64
|
+
method="ward",
|
|
65
|
+
metric="euclidean",
|
|
66
|
+
optimal_ordering=True,
|
|
67
|
+
)
|
|
68
|
+
cn_order = (
|
|
69
|
+
tuple(cn_names[index] for index in leaves_list(cn_linkage))
|
|
70
|
+
if cn_linkage is not None else tuple(cn_names)
|
|
71
|
+
)
|
|
55
72
|
dominant = pd.DataFrame(
|
|
56
73
|
{
|
|
57
74
|
"CN": cn_names,
|
|
@@ -63,7 +80,7 @@ def analyze_dominant_celltypes(
|
|
|
63
80
|
],
|
|
64
81
|
}
|
|
65
82
|
)
|
|
66
|
-
return DominantCellTypeResult(frequency, dominant)
|
|
83
|
+
return DominantCellTypeResult(frequency, dominant, cn_order, cn_linkage)
|
|
67
84
|
|
|
68
85
|
|
|
69
86
|
def run_dominant_celltype_analysis(config: CytoCommunityConfig, *, output_dir=None):
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/dominant.py
RENAMED
|
@@ -9,13 +9,25 @@ def _prepare(result):
|
|
|
9
9
|
frequency = result.frequency.copy()
|
|
10
10
|
frequency.columns = frequency.columns.astype(str).str.strip()
|
|
11
11
|
frequency.index = frequency.index.astype(str).str.strip()
|
|
12
|
-
cn_order = sorted(
|
|
12
|
+
cn_order = result.cn_order or sorted(
|
|
13
13
|
frequency.columns,
|
|
14
14
|
key=lambda column: int(column.removeprefix("CN")),
|
|
15
15
|
)
|
|
16
16
|
return frequency.loc[:, cn_order]
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _equal_level_linkage(linkage):
|
|
20
|
+
"""Give each dendrogram merge level equal display height."""
|
|
21
|
+
display_linkage = linkage.copy()
|
|
22
|
+
n_leaves = len(linkage) + 1
|
|
23
|
+
levels = np.zeros(2 * n_leaves - 1, dtype=int)
|
|
24
|
+
for index, (left, right, _, _) in enumerate(linkage):
|
|
25
|
+
level = max(levels[int(left)], levels[int(right)]) + 1
|
|
26
|
+
levels[n_leaves + index] = level
|
|
27
|
+
display_linkage[index, 2] = level
|
|
28
|
+
return display_linkage
|
|
29
|
+
|
|
30
|
+
|
|
19
31
|
def plot_dominant_celltype_results(result, config):
|
|
20
32
|
"""Save the frequency heatmap and its colorbar."""
|
|
21
33
|
import matplotlib as mpl
|
|
@@ -35,12 +47,26 @@ def plot_dominant_celltype_results(result, config):
|
|
|
35
47
|
frequency = _prepare(result)
|
|
36
48
|
|
|
37
49
|
rows, columns = frequency.shape
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
cell_fraction = min(0.68 / columns, 0.68 / rows)
|
|
51
|
+
heat_width = columns * cell_fraction
|
|
52
|
+
heat_height = rows * cell_fraction
|
|
53
|
+
figure = plt.figure(figsize=(8, 8))
|
|
54
|
+
heat_axis = figure.add_axes([0.10, 0.10, heat_width, heat_height])
|
|
55
|
+
if result.cn_linkage is not None:
|
|
56
|
+
from scipy.cluster.hierarchy import dendrogram
|
|
57
|
+
|
|
58
|
+
tree_axis = figure.add_axes([0.10, 0.108 + heat_height, heat_width, 0.075])
|
|
59
|
+
tree = dendrogram(_equal_level_linkage(result.cn_linkage), no_plot=True)
|
|
60
|
+
for x, y in zip(tree["icoord"], tree["dcoord"]):
|
|
61
|
+
tree_axis.plot(
|
|
62
|
+
[(value - 5) / 10 for value in x], y,
|
|
63
|
+
color="#4c5966", linewidth=0.8,
|
|
64
|
+
)
|
|
65
|
+
tree_axis.set_xlim(-0.5, columns - 0.5)
|
|
66
|
+
tree_axis.axis("off")
|
|
41
67
|
heat_axis.imshow(
|
|
42
68
|
frequency.to_numpy(), cmap="RdYlBu_r", vmin=0, vmax=1,
|
|
43
|
-
aspect="
|
|
69
|
+
aspect="equal", interpolation="nearest", origin="upper",
|
|
44
70
|
)
|
|
45
71
|
heat_axis.set_xticks(np.arange(columns))
|
|
46
72
|
heat_axis.set_xticklabels(frequency.columns, rotation=90, fontsize=10)
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/dotplots.py
RENAMED
|
@@ -8,6 +8,9 @@ import pandas as pd
|
|
|
8
8
|
from .common import dot_size, legend_size
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
DCF_STAR_THRESHOLDS = (0.1, 0.05)
|
|
12
|
+
|
|
13
|
+
|
|
11
14
|
def _configure_style():
|
|
12
15
|
import matplotlib as mpl
|
|
13
16
|
|
|
@@ -22,7 +25,10 @@ def _configure_style():
|
|
|
22
25
|
})
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
def _draw_panel(
|
|
28
|
+
def _draw_panel(
|
|
29
|
+
axis, record, celltypes, cn_order, norm, threshold, rotation, title,
|
|
30
|
+
star_thresholds=None,
|
|
31
|
+
):
|
|
26
32
|
effect, pvalues = record["effect"].T, record["pvalues"].T
|
|
27
33
|
x_grid, y_grid = np.meshgrid(np.arange(len(celltypes)), np.arange(len(cn_order)))
|
|
28
34
|
tested = np.isfinite(effect) & np.isfinite(pvalues)
|
|
@@ -37,14 +43,31 @@ def _draw_panel(axis, record, celltypes, cn_order, norm, threshold, rotation, ti
|
|
|
37
43
|
c=effect[mask], cmap="coolwarm", norm=norm, alpha=alpha,
|
|
38
44
|
edgecolors=edgecolor, linewidths=linewidth, zorder=zorder,
|
|
39
45
|
)
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if star_thresholds is None:
|
|
47
|
+
if np.any(significant):
|
|
48
|
+
axis.scatter(
|
|
49
|
+
x_grid[significant], y_grid[significant], s=60.0, marker="*",
|
|
50
|
+
c="#1d2630", edgecolors="none", zorder=4,
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
weak_threshold, strong_threshold = star_thresholds
|
|
54
|
+
one_star = tested & (pvalues < weak_threshold) & (pvalues >= strong_threshold)
|
|
55
|
+
two_stars = tested & (pvalues < strong_threshold)
|
|
56
|
+
if np.any(one_star):
|
|
57
|
+
axis.scatter(
|
|
58
|
+
x_grid[one_star], y_grid[one_star], s=60.0, marker="*",
|
|
59
|
+
c="#1d2630", edgecolors="none", zorder=4,
|
|
60
|
+
)
|
|
61
|
+
if np.any(two_stars):
|
|
62
|
+
for x_offset in (-0.075, 0.075):
|
|
63
|
+
axis.scatter(
|
|
64
|
+
x_grid[two_stars] + x_offset, y_grid[two_stars],
|
|
65
|
+
s=45.0, marker="*", c="#1d2630", edgecolors="none",
|
|
66
|
+
zorder=4,
|
|
67
|
+
)
|
|
45
68
|
axis.set_title(title, fontsize=13, fontweight="bold", pad=10)
|
|
46
69
|
if not np.any(tested):
|
|
47
|
-
axis.text(0.5, 0.5, "Not tested
|
|
70
|
+
axis.text(0.5, 0.5, "Not tested", transform=axis.transAxes, ha="center", va="center", color="#6d7781")
|
|
48
71
|
axis.set_xticks(np.arange(len(celltypes)))
|
|
49
72
|
axis.set_xticklabels(celltypes, rotation=rotation, ha="right" if rotation != 90 else "center", fontsize=8.5)
|
|
50
73
|
axis.set_yticks(np.arange(len(cn_order)))
|
|
@@ -58,9 +81,13 @@ def _draw_panel(axis, record, celltypes, cn_order, norm, threshold, rotation, ti
|
|
|
58
81
|
spine.set_visible(False)
|
|
59
82
|
|
|
60
83
|
|
|
61
|
-
def _add_legends(
|
|
84
|
+
def _add_legends(
|
|
85
|
+
figure, axis, norm, threshold, colorbar_title, star_thresholds=None,
|
|
86
|
+
significance_title="-log10(p-value)",
|
|
87
|
+
):
|
|
62
88
|
import matplotlib as mpl
|
|
63
89
|
import matplotlib.pyplot as plt
|
|
90
|
+
from matplotlib.legend_handler import HandlerTuple
|
|
64
91
|
|
|
65
92
|
axis.axis("off")
|
|
66
93
|
scalar_map = mpl.cm.ScalarMappable(norm=norm, cmap=plt.get_cmap("coolwarm"))
|
|
@@ -70,12 +97,31 @@ def _add_legends(figure, axis, norm, threshold, colorbar_title):
|
|
|
70
97
|
colorbar.ax.tick_params(labelsize=9)
|
|
71
98
|
values = (1.3, 2.0, 3.0)
|
|
72
99
|
handles = [axis.scatter([], [], s=legend_size(value), color="#b9414b") for value in values]
|
|
73
|
-
|
|
100
|
+
labels = [f"{value:g}" for value in values]
|
|
101
|
+
handler_map = None
|
|
102
|
+
if star_thresholds is None:
|
|
103
|
+
handles.append(axis.scatter([], [], s=60.0, marker="*", color="#1d2630"))
|
|
104
|
+
labels.append(f"p-value < {threshold:g}")
|
|
105
|
+
else:
|
|
106
|
+
weak_threshold, strong_threshold = star_thresholds
|
|
107
|
+
handles.extend([
|
|
108
|
+
axis.scatter([], [], s=60.0, marker="*", color="#1d2630"),
|
|
109
|
+
(
|
|
110
|
+
axis.scatter([], [], s=45.0, marker="*", color="#1d2630"),
|
|
111
|
+
axis.scatter([], [], s=45.0, marker="*", color="#1d2630"),
|
|
112
|
+
),
|
|
113
|
+
])
|
|
114
|
+
labels.extend([
|
|
115
|
+
f"{strong_threshold:g} <= p-value < {weak_threshold:g}",
|
|
116
|
+
f"p-value < {strong_threshold:g}",
|
|
117
|
+
])
|
|
118
|
+
handler_map = {tuple: HandlerTuple(ndivide=None, pad=0.0)}
|
|
74
119
|
axis.legend(
|
|
75
120
|
handles,
|
|
76
|
-
|
|
77
|
-
title=
|
|
121
|
+
labels,
|
|
122
|
+
title=significance_title, frameon=False, loc="center left",
|
|
78
123
|
bbox_to_anchor=(0.36, 0.50), labelspacing=1.0, handletextpad=0.75,
|
|
124
|
+
handler_map=handler_map,
|
|
79
125
|
)
|
|
80
126
|
|
|
81
127
|
|
|
@@ -97,7 +143,8 @@ def _normalization(records, *, cap=None):
|
|
|
97
143
|
|
|
98
144
|
def _save_dotplots(
|
|
99
145
|
records, celltypes, cn_order, norm, threshold, output_dir, prefix,
|
|
100
|
-
colorbar_title, *, grid_name=None, grid_title=None,
|
|
146
|
+
colorbar_title, *, grid_name=None, grid_title=None, star_thresholds=None,
|
|
147
|
+
significance_title="-log10(p-value)",
|
|
101
148
|
):
|
|
102
149
|
import matplotlib.pyplot as plt
|
|
103
150
|
|
|
@@ -109,6 +156,7 @@ def _save_dotplots(
|
|
|
109
156
|
_draw_panel(
|
|
110
157
|
axis, record, celltypes, cn_order, norm, threshold, 90,
|
|
111
158
|
f"{record['target']} vs {record['reference']}",
|
|
159
|
+
star_thresholds,
|
|
112
160
|
)
|
|
113
161
|
output = output_dir / f"{prefix}{record['name']}.pdf"
|
|
114
162
|
figure.savefig(output, bbox_inches="tight", dpi=1200)
|
|
@@ -116,7 +164,10 @@ def _save_dotplots(
|
|
|
116
164
|
plt.close(figure)
|
|
117
165
|
outputs.append(output)
|
|
118
166
|
figure, axis = plt.subplots(figsize=(4.6, 3.6))
|
|
119
|
-
_add_legends(
|
|
167
|
+
_add_legends(
|
|
168
|
+
figure, axis, norm, threshold, colorbar_title, star_thresholds,
|
|
169
|
+
significance_title,
|
|
170
|
+
)
|
|
120
171
|
legend = output_dir / "LEGEND.pdf"
|
|
121
172
|
figure.savefig(legend, bbox_inches="tight", dpi=1200)
|
|
122
173
|
plt.close(figure)
|
|
@@ -130,8 +181,12 @@ def _save_dotplots(
|
|
|
130
181
|
_draw_panel(
|
|
131
182
|
axis, record, celltypes, cn_order, norm, threshold, 55,
|
|
132
183
|
grid_title(record) if grid_title else record["target"],
|
|
184
|
+
star_thresholds,
|
|
133
185
|
)
|
|
134
|
-
_add_legends(
|
|
186
|
+
_add_legends(
|
|
187
|
+
figure, axes[len(records)], norm, threshold, colorbar_title,
|
|
188
|
+
star_thresholds, significance_title,
|
|
189
|
+
)
|
|
135
190
|
for axis in axes[len(records) + 1:]:
|
|
136
191
|
axis.set_visible(False)
|
|
137
192
|
figure.subplots_adjust(left=0.07, right=0.98, bottom=0.10, top=0.96, hspace=0.60, wspace=0.25)
|
|
@@ -170,6 +225,8 @@ def _differential_records(config, results):
|
|
|
170
225
|
celltype_index = {celltype: index for index, celltype in enumerate(celltypes)}
|
|
171
226
|
records = []
|
|
172
227
|
for name in names:
|
|
228
|
+
if not results.get(name):
|
|
229
|
+
continue
|
|
173
230
|
target, reference = name.split("_gt_", 1)
|
|
174
231
|
reference_mean = means[reference] if reference != "rest" else np.nanmean(np.stack([
|
|
175
232
|
matrix for condition, matrices in condition_matrices.items()
|
|
@@ -208,14 +265,25 @@ def plot_differential_composition_results(config, results, output_dir=None):
|
|
|
208
265
|
"""Save the original differential-composition dot plots."""
|
|
209
266
|
_configure_style()
|
|
210
267
|
celltypes, records = _differential_records(config, results)
|
|
268
|
+
output_dir = Path(output_dir or config.project_root / "plot" / "DifferentialComposed")
|
|
269
|
+
plotted_names = {record["name"] for record in records}
|
|
270
|
+
for name in results:
|
|
271
|
+
if name not in plotted_names:
|
|
272
|
+
for old_plot in (output_dir / f"{name}.pdf", output_dir / f"{name}.png"):
|
|
273
|
+
old_plot.unlink(missing_ok=True)
|
|
274
|
+
if not records:
|
|
275
|
+
for old_plot in (output_dir / "LEGEND.pdf", output_dir / "DotPlot_all.pdf"):
|
|
276
|
+
old_plot.unlink(missing_ok=True)
|
|
277
|
+
return {"plots": [], "legend": None, "grid": None}
|
|
211
278
|
threshold = config.downstream["differential_composed"]["p_value_threshold"]
|
|
212
279
|
return _save_dotplots(
|
|
213
280
|
records, celltypes, range(1, records[0]["effect"].shape[1] + 1),
|
|
214
281
|
_normalization(records, cap=4.0), threshold,
|
|
215
|
-
output_dir
|
|
282
|
+
output_dir,
|
|
216
283
|
"", "log2FC",
|
|
217
284
|
grid_name="DotPlot_all.pdf" if config.downstream["ttest_mode"] == "one_vs_rest" else None,
|
|
218
285
|
grid_title=lambda record: f"{record['target']} (n={record['sample_count']})",
|
|
286
|
+
star_thresholds=DCF_STAR_THRESHOLDS,
|
|
219
287
|
)
|
|
220
288
|
|
|
221
289
|
|
|
@@ -28,16 +28,42 @@ def calculate_enrichment_matrix(
|
|
|
28
28
|
num_cn,
|
|
29
29
|
minimum_adjusted_p,
|
|
30
30
|
):
|
|
31
|
-
"""Calculate
|
|
31
|
+
"""Calculate enrichment while retaining cells with an unassigned CN."""
|
|
32
32
|
cell_labels = np.asarray(cell_labels, dtype=str)
|
|
33
|
-
cn_labels =
|
|
33
|
+
cn_labels = pd.to_numeric(
|
|
34
|
+
pd.Series(np.asarray(cn_labels)).replace("NA", np.nan),
|
|
35
|
+
errors="raise",
|
|
36
|
+
).to_numpy(dtype=float)
|
|
37
|
+
if cell_labels.size != cn_labels.size:
|
|
38
|
+
raise ValueError("Cell-type and CN-label vectors must have equal lengths")
|
|
34
39
|
|
|
35
40
|
categories = pd.Categorical(cell_labels, categories=cell_types)
|
|
36
41
|
cell_type_codes = categories.codes
|
|
42
|
+
if np.any(cell_type_codes < 0):
|
|
43
|
+
unknown = sorted(np.unique(cell_labels[cell_type_codes < 0]).tolist())
|
|
44
|
+
raise ValueError(f"Unknown cell types: {unknown}")
|
|
45
|
+
|
|
46
|
+
assigned = np.isfinite(cn_labels)
|
|
47
|
+
assigned_labels = cn_labels[assigned]
|
|
48
|
+
if np.any(assigned_labels != np.floor(assigned_labels)) or np.any(
|
|
49
|
+
(assigned_labels < 1) | (assigned_labels > num_cn)
|
|
50
|
+
):
|
|
51
|
+
raise ValueError(f"CN labels must be integers from 1 to {num_cn}, or NA")
|
|
37
52
|
|
|
38
53
|
counts = np.zeros((len(cell_types), num_cn), dtype=np.int64)
|
|
39
|
-
np.add.at(
|
|
40
|
-
|
|
54
|
+
np.add.at(
|
|
55
|
+
counts,
|
|
56
|
+
(
|
|
57
|
+
cell_type_codes[assigned],
|
|
58
|
+
assigned_labels.astype(np.int64) - 1,
|
|
59
|
+
),
|
|
60
|
+
1,
|
|
61
|
+
)
|
|
62
|
+
# Cells without a CN assignment remain part of the sample and their cell
|
|
63
|
+
# type totals, but they do not contribute to any CN-specific overlap.
|
|
64
|
+
cell_type_totals = np.bincount(
|
|
65
|
+
cell_type_codes, minlength=len(cell_types)
|
|
66
|
+
)
|
|
41
67
|
cn_totals = counts.sum(axis=0)
|
|
42
68
|
raw_pvalues = np.ones(counts.shape, dtype=np.float64)
|
|
43
69
|
|
|
@@ -88,7 +114,7 @@ def calculate_enrichment(
|
|
|
88
114
|
.iloc[:, 0]
|
|
89
115
|
.to_numpy()
|
|
90
116
|
)
|
|
91
|
-
cn_labels = np.asarray(cn_labels_by_sample[sample]
|
|
117
|
+
cn_labels = np.asarray(cn_labels_by_sample[sample])
|
|
92
118
|
matrix = calculate_enrichment_matrix(
|
|
93
119
|
cell_labels,
|
|
94
120
|
cn_labels,
|
|
@@ -109,13 +135,14 @@ def calculate_enrichment(
|
|
|
109
135
|
def _load_cn_labels(samples, result_table_dir):
|
|
110
136
|
result_table_dir = Path(result_table_dir)
|
|
111
137
|
return {
|
|
112
|
-
sample: pd.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
sample: pd.to_numeric(
|
|
139
|
+
pd.read_csv(
|
|
140
|
+
result_table_dir / f"ResultTable_{sample}.csv",
|
|
141
|
+
usecols=["CN_Label"],
|
|
142
|
+
na_values=["NA"],
|
|
143
|
+
)["CN_Label"],
|
|
144
|
+
errors="raise",
|
|
145
|
+
).to_numpy(dtype=float)
|
|
119
146
|
for sample in samples
|
|
120
147
|
}
|
|
121
148
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/between.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/cca.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/utils.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/communication/within.py
RENAMED
|
File without changes
|
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/__init__.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/cn_selection.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/coherence.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/common.py
RENAMED
|
File without changes
|
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/composition.py
RENAMED
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2/downstream/plotting/recurrence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cytocommunity2-1.0.1 → cytocommunity2-1.0.2}/src/cytocommunity2.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|