pythonflex 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- pythonflex/__init__.py +18 -0
- pythonflex/analysis.py +1299 -0
- pythonflex/data/dataset/liver_cell_lines_500_genes.csv +501 -0
- pythonflex/data/dataset/melanoma_cell_lines_500_genes.csv +501 -0
- pythonflex/data/dataset/neuroblastoma_cell_lines_500_genes.csv +501 -0
- pythonflex/data/gold_standard/CORUM.parquet +0 -0
- pythonflex/data/gold_standard/GOBP.parquet +0 -0
- pythonflex/data/gold_standard/PATHWAY.parquet +0 -0
- pythonflex/data/gold_standard/corum.csv +2917 -0
- pythonflex/data/gold_standard/gobp.csv +4829 -0
- pythonflex/data/gold_standard/pathway.csv +1330 -0
- pythonflex/examples/basic_usage.py +108 -0
- pythonflex/examples/dataset_filtering.py +29 -0
- pythonflex/logging_config.py +56 -0
- pythonflex/plotting.py +510 -0
- pythonflex/preprocessing.py +221 -0
- pythonflex/utils.py +100 -0
- {pythonflex-0.1.2.dist-info → pythonflex-0.1.3.dist-info}/METADATA +1 -1
- pythonflex-0.1.3.dist-info/RECORD +21 -0
- pythonflex-0.1.2.dist-info/RECORD +0 -4
- {pythonflex-0.1.2.dist-info → pythonflex-0.1.3.dist-info}/WHEEL +0 -0
- {pythonflex-0.1.2.dist-info → pythonflex-0.1.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic usage example of the pythonFLEX package.
|
|
3
|
+
Demonstrates initialization, data loading, analysis, and plotting.
|
|
4
|
+
"""
|
|
5
|
+
#%%
|
|
6
|
+
import pythonflex as flex
|
|
7
|
+
|
|
8
|
+
inputs = {
|
|
9
|
+
"SNF": {
|
|
10
|
+
"path": "C:/Users/yd/Desktop/projects/datasets/fused_similarity_network.csv",
|
|
11
|
+
"sort": "high"
|
|
12
|
+
},
|
|
13
|
+
"miss_SNF": {
|
|
14
|
+
"path": "C:/Users/yd/Desktop/projects/datasets/miss_snf_fused_similarity_network.csv",
|
|
15
|
+
"sort": "high"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#%%
|
|
20
|
+
|
|
21
|
+
default_config = {
|
|
22
|
+
"min_genes_in_complex": 0,
|
|
23
|
+
"min_genes_per_complex_analysis": 3,
|
|
24
|
+
"output_folder": "output",
|
|
25
|
+
"gold_standard": "CORUM",
|
|
26
|
+
"color_map": "RdYlBu",
|
|
27
|
+
"jaccard": True,
|
|
28
|
+
"plotting": {
|
|
29
|
+
"save_plot": True,
|
|
30
|
+
"output_type": "PNG",
|
|
31
|
+
},
|
|
32
|
+
"preprocessing": {
|
|
33
|
+
"fill_na": True,
|
|
34
|
+
"normalize": False,
|
|
35
|
+
},
|
|
36
|
+
"corr_function": "numpy",
|
|
37
|
+
"logging": {
|
|
38
|
+
"visible_levels": ["DONE","STARTED"] # "PROGRESS", "STARTED", ,"INFO","WARNING"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Initialize logger, config, and output folder
|
|
43
|
+
flex.initialize(default_config)
|
|
44
|
+
|
|
45
|
+
# Load datasets and gold standard terms
|
|
46
|
+
|
|
47
|
+
data, _ = flex.load_datasets(inputs)
|
|
48
|
+
terms, genes_in_terms = flex.load_gold_standard()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
#%%
|
|
52
|
+
# Run analysis
|
|
53
|
+
for name, dataset in data.items():
|
|
54
|
+
df, pr_auc = flex.pra(name, dataset, is_corr=True)
|
|
55
|
+
fpc = flex.pra_percomplex(name, dataset, is_corr=True)
|
|
56
|
+
cc = flex.complex_contributions(name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
#%%
|
|
60
|
+
# Generate plots
|
|
61
|
+
flex.plot_auc_scores()
|
|
62
|
+
flex.plot_precision_recall_curve()
|
|
63
|
+
flex.plot_percomplex_scatter()
|
|
64
|
+
flex.plot_percomplex_scatter_bysize()
|
|
65
|
+
flex.plot_significant_complexes()
|
|
66
|
+
flex.plot_complex_contributions()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
#%%
|
|
70
|
+
# Save results to CSV
|
|
71
|
+
flex.save_results_to_csv()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# %%
|
|
86
|
+
import os
|
|
87
|
+
import glob
|
|
88
|
+
|
|
89
|
+
inputs = {
|
|
90
|
+
"depmap all": {
|
|
91
|
+
"path": "../../../../datasets/depmap/24Q4/depmap_geneeffect_all_cellines.csv",
|
|
92
|
+
"sort": "high"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Now auto-discover the rest of the CSVs in the folder
|
|
97
|
+
DATA_DIR = "../../../../datasets/depmap/24Q4/subset/"
|
|
98
|
+
for path in glob.glob(os.path.join(DATA_DIR, "*.csv")):
|
|
99
|
+
|
|
100
|
+
# Derive the key name from filename (without extension)
|
|
101
|
+
key = os.path.splitext(os.path.basename(path))[0]
|
|
102
|
+
inputs[key] = {
|
|
103
|
+
"path": path,
|
|
104
|
+
"sort": "high"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# inputs now has "depmap all" first, then one entry per CSV in DATA_DIR
|
|
108
|
+
print(inputs)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
# %%
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
df = pd.read_csv("../../../../datasets/depmap/24Q4/CRISPRGeneEffect.csv",index_col=0)
|
|
6
|
+
model = pd.read_csv("../../../../datasets/depmap/24Q4/Model.csv",index_col=0)
|
|
7
|
+
|
|
8
|
+
df.columns = df.columns.str.split(" \\(").str[0]
|
|
9
|
+
df = df.T
|
|
10
|
+
|
|
11
|
+
# %%
|
|
12
|
+
# get ModelID of selected disease for example OncotreePrimaryDisease==Melanoma
|
|
13
|
+
melanoma = model[model.OncotreePrimaryDisease=="Melanoma"].index.unique().values
|
|
14
|
+
liver = model[model.OncotreeLineage=="Liver"].index.unique().values
|
|
15
|
+
neuroblastoma = model[model.OncotreePrimaryDisease=="Neuroblastoma"].index.unique().values
|
|
16
|
+
|
|
17
|
+
# %%
|
|
18
|
+
# mel.index is model ids, filter that ids in the columns of df
|
|
19
|
+
mel_df = df.loc[:,df.columns.isin(melanoma)]
|
|
20
|
+
liver_df = df.loc[:,df.columns.isin(liver)]
|
|
21
|
+
neuro_df = df.loc[:,df.columns.isin(neuroblastoma)]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# %%
|
|
25
|
+
mel_df.to_csv("melanoma.csv")
|
|
26
|
+
liver_df.to_csv("liver.csv")
|
|
27
|
+
neuro_df.to_csv("neuroblastoma.csv")
|
|
28
|
+
df.to_csv("depmap_geneeffect_all_cellines.csv")
|
|
29
|
+
# %%
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import emoji
|
|
3
|
+
from loguru import logger as global_logger
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
pkg_logger = global_logger.bind(name="pyflex")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CustomLogger:
|
|
10
|
+
def __init__(self, logger):
|
|
11
|
+
self._log = logger
|
|
12
|
+
self.visible_levels = {"DONE", "PROGRESS", "STARTED", "INFO"} # Default: Show all custom levels
|
|
13
|
+
self._define_custom_levels()
|
|
14
|
+
self._configure_output()
|
|
15
|
+
|
|
16
|
+
def _safe_set_level(self, name: str, **kwargs):
|
|
17
|
+
try:
|
|
18
|
+
self._log.level(name, **kwargs)
|
|
19
|
+
except ValueError:
|
|
20
|
+
pass # Level already exists, ignore
|
|
21
|
+
|
|
22
|
+
def _define_custom_levels(self):
|
|
23
|
+
self._safe_set_level("DONE", no=25, color="<green>", icon=emoji.emojize(":flexed_biceps:"))
|
|
24
|
+
self._safe_set_level("PROGRESS", no=22, color="<red>", icon=emoji.emojize(":horse:"))
|
|
25
|
+
self._safe_set_level("STARTED", no=21, color="<blue>", icon=emoji.emojize(":rocket:"))
|
|
26
|
+
self._safe_set_level("INFO", color="<blue>", icon=emoji.emojize(":placard:"))
|
|
27
|
+
|
|
28
|
+
def _configure_output(self):
|
|
29
|
+
self._log.remove()
|
|
30
|
+
|
|
31
|
+
def level_filter(record):
|
|
32
|
+
return record["level"].name in self.visible_levels
|
|
33
|
+
|
|
34
|
+
self._log.add(sys.stderr, format="{time:HH:mm:ss} | {level.icon} {level.name:<8} | {message}", filter=level_filter)
|
|
35
|
+
|
|
36
|
+
def set_visible_levels(self, levels):
|
|
37
|
+
self.visible_levels = set(levels)
|
|
38
|
+
self._configure_output() # Reconfigure the sink with the new filter
|
|
39
|
+
|
|
40
|
+
def started(self, message, *args, **kwargs):
|
|
41
|
+
self._log.log("STARTED", message, *args, **kwargs)
|
|
42
|
+
|
|
43
|
+
def done(self, message, *args, **kwargs):
|
|
44
|
+
self._log.log("DONE", message, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def progress(self, message, *args, **kwargs):
|
|
47
|
+
self._log.log("PROGRESS", message, *args, **kwargs)
|
|
48
|
+
|
|
49
|
+
def __getattr__(self, name):
|
|
50
|
+
return getattr(self._log, name)
|
|
51
|
+
|
|
52
|
+
# Type checker compatibility
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
log: CustomLogger
|
|
55
|
+
else:
|
|
56
|
+
log = CustomLogger(pkg_logger)
|
pythonflex/plotting.py
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# Standard library
|
|
2
|
+
from itertools import combinations
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Third-party libraries
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
from matplotlib import patches
|
|
10
|
+
from matplotlib.cm import get_cmap
|
|
11
|
+
from IPython.display import set_matplotlib_formats
|
|
12
|
+
|
|
13
|
+
# Local modules
|
|
14
|
+
from .utils import dload
|
|
15
|
+
from .logging_config import log
|
|
16
|
+
|
|
17
|
+
# Configuration
|
|
18
|
+
set_matplotlib_formats('svg', 'pdf')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def plot_precision_recall_curve():
|
|
24
|
+
pra = dload("pra")
|
|
25
|
+
config = dload("config")
|
|
26
|
+
plot_config = config["plotting"]
|
|
27
|
+
|
|
28
|
+
# Create figure using rcParams defaults (figsize and dpi are already set)
|
|
29
|
+
fig, ax = plt.subplots()
|
|
30
|
+
ax.set_xscale("log")
|
|
31
|
+
|
|
32
|
+
# Color map from rcParams (no need to get from config again)
|
|
33
|
+
cmap = get_cmap() # Uses rcParams['image.cmap'] by default
|
|
34
|
+
num_colors = len(pra) if isinstance(pra, dict) else 1
|
|
35
|
+
colors = [cmap(float(i) / max(num_colors - 1, 1)) for i in range(num_colors)]
|
|
36
|
+
|
|
37
|
+
if isinstance(pra, dict):
|
|
38
|
+
for (key, val), color in zip(pra.items(), colors):
|
|
39
|
+
val = val[val.tp > 10]
|
|
40
|
+
ax.plot(val.tp, val.precision, c=color, label=key, linewidth=2, alpha=0.8)
|
|
41
|
+
else:
|
|
42
|
+
pra = pra[pra.tp > 10]
|
|
43
|
+
ax.plot(pra.tp, pra.precision, c="black", label="Precision Recall Curve", linewidth=2, alpha=0.8)
|
|
44
|
+
|
|
45
|
+
# Labels and title (sizes handled by rcParams)
|
|
46
|
+
ax.set(title="Precision-Recall Performance of Datasets",
|
|
47
|
+
xlabel="Number of True Positives (TP)",
|
|
48
|
+
ylabel="Precision")
|
|
49
|
+
ax.legend(loc="upper right", frameon=True)
|
|
50
|
+
|
|
51
|
+
# Fix Y-axis to always go from 0 to 1
|
|
52
|
+
ax.set_ylim(0, 1)
|
|
53
|
+
|
|
54
|
+
# Grid and spines (styles handled by rcParams)
|
|
55
|
+
ax.grid(True) # Style comes from rcParams
|
|
56
|
+
# Spines visibility handled by rcParams ('axes.spines.right' and 'axes.spines.top')
|
|
57
|
+
|
|
58
|
+
# Save handling (output config still needed)
|
|
59
|
+
if plot_config["save_plot"]:
|
|
60
|
+
output_type = plot_config["output_type"]
|
|
61
|
+
output_path = Path(config["output_folder"]) / f"precision_recall_curve.{output_type}"
|
|
62
|
+
fig.savefig(output_path, bbox_inches="tight", format=output_type) # dpi comes from rcParams
|
|
63
|
+
|
|
64
|
+
if plot_config.get("show_plot", True):
|
|
65
|
+
plt.show()
|
|
66
|
+
|
|
67
|
+
plt.close(fig)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def plot_percomplex_scatter(n_top=10):
|
|
72
|
+
config = dload("config")
|
|
73
|
+
plot_config = config["plotting"]
|
|
74
|
+
rdict = dload("pra_percomplex")
|
|
75
|
+
|
|
76
|
+
# Ensure there are at least two datasets to compare
|
|
77
|
+
if len(rdict) < 2:
|
|
78
|
+
print("Skipping plot: At least two datasets are required for per-complex scatter plot.")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
column_pairs = list(combinations(rdict.keys(), 2))
|
|
82
|
+
df = pd.DataFrame()
|
|
83
|
+
|
|
84
|
+
# Data loading
|
|
85
|
+
for i, (key, val) in enumerate(rdict.items()):
|
|
86
|
+
val = val.rename(columns={"auc_score": key})
|
|
87
|
+
if i == 0:
|
|
88
|
+
df = val.copy().drop(columns=["Genes", "Length", "used_genes"])
|
|
89
|
+
else:
|
|
90
|
+
df = pd.concat([df, val[key]], axis=1)
|
|
91
|
+
|
|
92
|
+
# Plotting
|
|
93
|
+
for pair in column_pairs:
|
|
94
|
+
extreme_indices_0 = df[pair[0]].sort_values(ascending=False).head(n_top).index
|
|
95
|
+
extreme_indices_1 = df[pair[1]].sort_values(ascending=False).head(n_top).index
|
|
96
|
+
|
|
97
|
+
# Figure created with rcParams defaults
|
|
98
|
+
fig, ax = plt.subplots()
|
|
99
|
+
|
|
100
|
+
# Base scatter plot (keep color overrides)
|
|
101
|
+
sizes = df['n_used_genes'] * 8
|
|
102
|
+
ax.scatter(df[pair[0]], df[pair[1]],
|
|
103
|
+
edgecolors="black",
|
|
104
|
+
marker='o',
|
|
105
|
+
s=sizes,
|
|
106
|
+
linewidth=0.7,
|
|
107
|
+
zorder=1)
|
|
108
|
+
|
|
109
|
+
# Highlight significant points
|
|
110
|
+
significant_indices = extreme_indices_0.union(extreme_indices_1)
|
|
111
|
+
sig_sizes = df.loc[significant_indices, 'n_used_genes'] * 8
|
|
112
|
+
ax.scatter(df.loc[significant_indices, pair[0]],
|
|
113
|
+
df.loc[significant_indices, pair[1]],
|
|
114
|
+
facecolors='black',
|
|
115
|
+
edgecolors='black',
|
|
116
|
+
s=sig_sizes,
|
|
117
|
+
linewidth=0.1,
|
|
118
|
+
zorder=2)
|
|
119
|
+
|
|
120
|
+
all_points = list(zip(df[pair[0]], df[pair[1]]))
|
|
121
|
+
coords = sorted([(df.loc[idx, pair[0]], df.loc[idx, pair[1]], idx)
|
|
122
|
+
for idx in significant_indices], key=lambda c: (-c[1], -c[0]))
|
|
123
|
+
|
|
124
|
+
adjusted_coords = adjust_text_positions(coords, sig_sizes)
|
|
125
|
+
|
|
126
|
+
# Draw vertical lines and right-aligned text
|
|
127
|
+
for x, adj_y, idx in adjusted_coords:
|
|
128
|
+
y = df.loc[idx, pair[1]]
|
|
129
|
+
ax.plot([x, x], [y, adj_y],
|
|
130
|
+
color='black',
|
|
131
|
+
linewidth=0.7,
|
|
132
|
+
alpha=0.3,
|
|
133
|
+
zorder=3)
|
|
134
|
+
|
|
135
|
+
ax.text(x, adj_y + 0.005,
|
|
136
|
+
df.loc[idx, 'Name'][:20] + '.',
|
|
137
|
+
fontsize=6,
|
|
138
|
+
ha='left',
|
|
139
|
+
va='bottom',
|
|
140
|
+
linespacing=1.5,
|
|
141
|
+
zorder=4,
|
|
142
|
+
bbox=dict(facecolor="white", alpha=0.8, edgecolor="none", pad=1.5)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Reference line and labels
|
|
146
|
+
ax.plot([0, 1], [0, 1],
|
|
147
|
+
linestyle='-',
|
|
148
|
+
color='lightgray',
|
|
149
|
+
alpha=0.4,
|
|
150
|
+
zorder=0)
|
|
151
|
+
|
|
152
|
+
# Add padding to axes for better visibility of points near edges
|
|
153
|
+
padding = 0.02 # Small offset (adjust as needed, e.g., 0.05 for more space)
|
|
154
|
+
ax.set_xlim(-padding, 1 + padding)
|
|
155
|
+
ax.set_ylim(-padding, 1 + padding)
|
|
156
|
+
|
|
157
|
+
# Labels use rcParams sizes automatically
|
|
158
|
+
ax.set_xlabel(f"{pair[0]} PR-AUC score")
|
|
159
|
+
ax.set_ylabel(f"{pair[1]} PR-AUC score")
|
|
160
|
+
ax.set_title(f"{pair[0]} vs {pair[1]} - Comparison of complex performance")
|
|
161
|
+
|
|
162
|
+
plt.tight_layout()
|
|
163
|
+
|
|
164
|
+
# Save handling
|
|
165
|
+
if plot_config["save_plot"]:
|
|
166
|
+
output_type = plot_config["output_type"]
|
|
167
|
+
output_path = Path(config["output_folder"]) / f"percomplex_scatter_{pair[0]}_vs_{pair[1]}.{output_type}"
|
|
168
|
+
fig.savefig(output_path, bbox_inches="tight", format=output_type)
|
|
169
|
+
|
|
170
|
+
if plot_config.get("show_plot", True):
|
|
171
|
+
plt.show()
|
|
172
|
+
|
|
173
|
+
plt.close(fig)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def adjust_text_positions(coords, sizes, min_distance=0.08, max_y=1.0, scale_factor=1.0):
|
|
182
|
+
adjusted = []
|
|
183
|
+
text_height = 0.04 * scale_factor # Scaled text height
|
|
184
|
+
|
|
185
|
+
for (x, y, idx) in coords:
|
|
186
|
+
base_offset = np.sqrt(sizes.loc[idx]) * 0.04 * scale_factor if idx in sizes else 0.04 * scale_factor
|
|
187
|
+
adj_y = y + base_offset # Move text upwards (scaled)
|
|
188
|
+
safety = 0
|
|
189
|
+
|
|
190
|
+
# Ensure text stays within plot area
|
|
191
|
+
max_possible_y = max_y - text_height
|
|
192
|
+
|
|
193
|
+
while safety < 20:
|
|
194
|
+
conflict = False
|
|
195
|
+
# Check against existing labels
|
|
196
|
+
for (ax, aay, _) in adjusted:
|
|
197
|
+
if abs(x - ax) < 0.01 and abs(adj_y - aay) < min_distance:
|
|
198
|
+
conflict = True
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
if conflict:
|
|
202
|
+
adj_y += (0.03 + (base_offset * 0.1)) # Move further upwards (base_offset already scaled)
|
|
203
|
+
safety += 1
|
|
204
|
+
|
|
205
|
+
# If we're going beyond plot area, stop
|
|
206
|
+
if adj_y > max_possible_y:
|
|
207
|
+
adj_y = max_possible_y
|
|
208
|
+
break
|
|
209
|
+
else:
|
|
210
|
+
# Clamp final position
|
|
211
|
+
adj_y = min(adj_y, max_possible_y)
|
|
212
|
+
adjusted.append((x, adj_y, idx))
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
return adjusted
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def plot_percomplex_scatter_bysize():
|
|
220
|
+
config = dload("config")
|
|
221
|
+
plot_config = config["plotting"]
|
|
222
|
+
rdict = dload("pra_percomplex")
|
|
223
|
+
|
|
224
|
+
for key, per_complex in rdict.items():
|
|
225
|
+
sorted_pc = per_complex.sort_values(by="auc_score", ascending=False, na_position="last")
|
|
226
|
+
top_10, rest = sorted_pc.head(10), sorted_pc.iloc[10:]
|
|
227
|
+
|
|
228
|
+
# Create figure using rcParams defaults
|
|
229
|
+
fig, ax = plt.subplots()
|
|
230
|
+
|
|
231
|
+
# Base scatter plot (simple swap of X/Y data)
|
|
232
|
+
ax.scatter(
|
|
233
|
+
rest.auc_score, rest.n_used_genes, # auc_score on X, n_used_genes on Y
|
|
234
|
+
edgecolors="black",
|
|
235
|
+
linewidth=0.5,
|
|
236
|
+
s=rest.n_used_genes * 10,
|
|
237
|
+
label="Other Complexes"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Top 10 scatter plot (simple swap of X/Y data)
|
|
241
|
+
ax.scatter(
|
|
242
|
+
top_10.auc_score, top_10.n_used_genes, # auc_score on X, n_used_genes on Y
|
|
243
|
+
facecolors='black',
|
|
244
|
+
edgecolors='black',
|
|
245
|
+
linewidth=0.5,
|
|
246
|
+
s=top_10.n_used_genes * 10,
|
|
247
|
+
label="Top 10 AUC Scores"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# Text annotation handling (swapped coords: auc on X, size on Y)
|
|
252
|
+
coords = [(row.auc_score, row.n_used_genes, idx) for idx, row in top_10.iterrows()]
|
|
253
|
+
sizes = top_10.n_used_genes * 10
|
|
254
|
+
|
|
255
|
+
# Dynamically set max_y and scale_factor to make lines visible/long like original
|
|
256
|
+
max_y = sorted_pc.n_used_genes.max() + 50 # Larger buffer (increase to +100 if lines still short)
|
|
257
|
+
scale_factor = max_y / 1.0 # Scale offsets for visibility on new range
|
|
258
|
+
adjusted_coords = adjust_text_positions(
|
|
259
|
+
coords, sizes,
|
|
260
|
+
min_distance=0.08 * scale_factor,
|
|
261
|
+
max_y=max_y,
|
|
262
|
+
scale_factor=scale_factor # New param to make lengths visible
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
for x, adj_y, idx in adjusted_coords:
|
|
266
|
+
y = top_10.loc[idx, "n_used_genes"] # Pull from n_used_genes (now Y)
|
|
267
|
+
ax.plot([x, x], [y, adj_y],
|
|
268
|
+
color='black',
|
|
269
|
+
linewidth=0.7,
|
|
270
|
+
alpha=0.3,
|
|
271
|
+
zorder=3)
|
|
272
|
+
|
|
273
|
+
# Dynamic alignment: left if x < 0.5 (extends right), right if x >= 0.5 (extends left)
|
|
274
|
+
if x < 0.5:
|
|
275
|
+
ha = 'left'
|
|
276
|
+
text_x = x + 0.01 # Small rightward offset to avoid line overlap
|
|
277
|
+
else:
|
|
278
|
+
ha = 'right'
|
|
279
|
+
text_x = x - 0.01 # Small leftward offset to avoid line overlap
|
|
280
|
+
|
|
281
|
+
ax.text(text_x, adj_y + (0.005 * scale_factor),
|
|
282
|
+
top_10.loc[idx, 'Name'][:20] + '.',
|
|
283
|
+
fontsize=6,
|
|
284
|
+
ha=ha,
|
|
285
|
+
va='bottom',
|
|
286
|
+
linespacing=1.5,
|
|
287
|
+
zorder=4,
|
|
288
|
+
bbox=dict(facecolor="white", alpha=0.8, edgecolor="none", pad=1.5))
|
|
289
|
+
|
|
290
|
+
# Axis configuration (integer ticks now on Y)
|
|
291
|
+
ax.yaxis.get_major_locator().set_params(integer=True)
|
|
292
|
+
ax.set_xlabel("PR-AUC score") # Swapped label
|
|
293
|
+
ax.set_ylabel("Number of genes in the complex") # Swapped label
|
|
294
|
+
ax.set_title(f"{key} - Complex performance: PR-AUC score vs complex size")
|
|
295
|
+
ax.grid(False)
|
|
296
|
+
|
|
297
|
+
# Fixed limits (X fixed to 0-1 for AUC, Y with buffer for lines/labels)
|
|
298
|
+
ax.set_xlim(0, 1.0)
|
|
299
|
+
ax.set_ylim(0, max_y)
|
|
300
|
+
|
|
301
|
+
# Adjust subplot margins to give extra space on right for labels (without extending axis; optional)
|
|
302
|
+
plt.subplots_adjust(right=0.8) # Adjust to 0.7 or remove if not needed
|
|
303
|
+
|
|
304
|
+
plt.tight_layout()
|
|
305
|
+
|
|
306
|
+
# Save handling (dpi comes from rcParams)
|
|
307
|
+
if plot_config["save_plot"]:
|
|
308
|
+
output_type = plot_config["output_type"]
|
|
309
|
+
output_path = Path(config["output_folder"]) / f"percomplex_scatter_by_complexsize_{key}.{output_type}"
|
|
310
|
+
fig.savefig(output_path, bbox_inches="tight", format=output_type)
|
|
311
|
+
|
|
312
|
+
if plot_config.get("show_plot", True):
|
|
313
|
+
plt.show()
|
|
314
|
+
|
|
315
|
+
plt.close(fig)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def plot_complex_contributions(min_pairs=10, min_precision_cutoff=0.5, num_complex_to_show=10, y_lim=None, fig_title=None, fig_labs=['Fraction of TP', 'Precision']):
|
|
320
|
+
config = dload("config")
|
|
321
|
+
plot_config = config["plotting"]
|
|
322
|
+
plot_data_dict = dload("complex_contributions")
|
|
323
|
+
for key, plot_data in plot_data_dict.items():
|
|
324
|
+
s = plot_data.set_index('Name').sum()
|
|
325
|
+
find_last_precision = s[s > min_pairs].index[-1]
|
|
326
|
+
last_prec_value = float(find_last_precision.split('_')[1]) # Parse the float value
|
|
327
|
+
|
|
328
|
+
plot_data = plot_data.drop_duplicates(subset='Name')
|
|
329
|
+
cont_stepwise_anno = plot_data['Name']
|
|
330
|
+
cont_stepwise_mat = plot_data.drop(columns=['Name'])
|
|
331
|
+
tmp_TP = cont_stepwise_mat.sum(axis=0)
|
|
332
|
+
Precision_ind = (tmp_TP >= min_pairs)
|
|
333
|
+
cont_stepwise_mat = cont_stepwise_mat.loc[:, Precision_ind]
|
|
334
|
+
tmp = cont_stepwise_mat.columns
|
|
335
|
+
y = np.array([float(col.split('_')[1]) if isinstance(col, str) and '_' in col else col for col in tmp])
|
|
336
|
+
x = cont_stepwise_mat.sum(axis=0)
|
|
337
|
+
mx, nx = cont_stepwise_mat.shape[0], cont_stepwise_mat.shape[1]
|
|
338
|
+
tmp = np.tile(x, (mx, 1))
|
|
339
|
+
x = cont_stepwise_mat.values / tmp
|
|
340
|
+
x_df = pd.DataFrame(x, index=cont_stepwise_anno, columns=cont_stepwise_mat.columns)
|
|
341
|
+
ind_for_mean = y >= (last_prec_value - min_precision_cutoff)
|
|
342
|
+
if sum(ind_for_mean) == 0:
|
|
343
|
+
log.info("No values above 'min.precision.cutoff'")
|
|
344
|
+
return False
|
|
345
|
+
if sum(ind_for_mean) == 1:
|
|
346
|
+
log.info("Only one value above 'min.precision.cutoff', unable to calculate meaningful contribution structure")
|
|
347
|
+
return False
|
|
348
|
+
# Select top complexes
|
|
349
|
+
a = x_df.loc[:, ind_for_mean].mean(axis=1).sort_values()[-num_complex_to_show:]
|
|
350
|
+
subset = x_df.loc[a.index, :]
|
|
351
|
+
# Use the RdYlBu colormap for the top 10 points
|
|
352
|
+
cmap = plt.get_cmap() # Get default from rcParams
|
|
353
|
+
colors = cmap(np.linspace(0, 1, num_complex_to_show))
|
|
354
|
+
colors = np.vstack(([0.5, 0.5, 0.5, 1.0], colors))
|
|
355
|
+
others = pd.DataFrame(1 - subset.sum(axis=0), columns=['others']).T
|
|
356
|
+
merged = pd.concat([others, subset], ignore_index=False)
|
|
357
|
+
x = merged.to_numpy()
|
|
358
|
+
x1 = np.zeros_like(x)
|
|
359
|
+
x2 = np.zeros_like(x)
|
|
360
|
+
for i in range(x.shape[0]):
|
|
361
|
+
if i == 0:
|
|
362
|
+
x2[i, :] = x[0, :]
|
|
363
|
+
elif i == 1:
|
|
364
|
+
x1[i, :] = x[0, :]
|
|
365
|
+
else:
|
|
366
|
+
x1[i, :] = x[:i, :].sum(axis=0)
|
|
367
|
+
if i > 0:
|
|
368
|
+
x2[i, :] = x[:i + 1, :].sum(axis=0)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# FORCE recalculation of y_lim - ignore any passed parameter
|
|
372
|
+
padding = 0.02 # Small padding to avoid clipping (adjust as needed, e.g., 0.05 for more space)
|
|
373
|
+
lower = max(0, min(y) - padding)
|
|
374
|
+
upper = last_prec_value + padding # Use the actual last precision value instead of 1.0
|
|
375
|
+
y_lim = (lower, upper)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
fig, ax = plt.subplots(2, 1, gridspec_kw={'height_ratios': [5, 1]})
|
|
379
|
+
ax[0].set_xlim(0, 1)
|
|
380
|
+
ax[0].set_ylim(*y_lim) # Use dynamic ylim
|
|
381
|
+
ax[0].set_xlabel(fig_labs[0])
|
|
382
|
+
ax[0].set_ylabel(fig_labs[1])
|
|
383
|
+
ax[0].set_title(fig_title if fig_title else f"{key} - Contribution of complexes")
|
|
384
|
+
for i in range(x.shape[0]):
|
|
385
|
+
ax[0].fill_betweenx(y, x1[i, :], x2[i, :], color=colors[i], edgecolor='white')
|
|
386
|
+
|
|
387
|
+
# Legend handling (keep custom settings)
|
|
388
|
+
legend_labels = [f'{label[:20]}.' for label in merged.index]
|
|
389
|
+
patches_list = [patches.Patch(color=colors[i], label=legend_labels[i]) for i in range(len(legend_labels))]
|
|
390
|
+
ax[1].axis('off')
|
|
391
|
+
ax[1].legend(handles=patches_list, loc='center', ncol=3, frameon=False, title="Complexes")
|
|
392
|
+
plt.tight_layout()
|
|
393
|
+
|
|
394
|
+
# Save handling (remove explicit dpi)
|
|
395
|
+
if plot_config["save_plot"]:
|
|
396
|
+
output_type = plot_config["output_type"]
|
|
397
|
+
output_folder = Path(config["output_folder"])
|
|
398
|
+
output_path = output_folder / f"complex_contributions_{key}.{output_type}"
|
|
399
|
+
fig.savefig(output_path, bbox_inches="tight", format=output_type)
|
|
400
|
+
|
|
401
|
+
if plot_config.get("show_plot", True):
|
|
402
|
+
plt.show()
|
|
403
|
+
|
|
404
|
+
plt.close(fig)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def plot_significant_complexes():
|
|
408
|
+
config = dload("config")
|
|
409
|
+
plot_config = config["plotting"]
|
|
410
|
+
pra_percomplex = dload("pra_percomplex")
|
|
411
|
+
|
|
412
|
+
# Define thresholds and prepare data
|
|
413
|
+
thresholds = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
414
|
+
datasets = list(pra_percomplex.keys())
|
|
415
|
+
num_datasets = len(datasets)
|
|
416
|
+
|
|
417
|
+
# Create a DataFrame to store results
|
|
418
|
+
df = pd.DataFrame(index=thresholds)
|
|
419
|
+
for key, complex_data in pra_percomplex.items():
|
|
420
|
+
df[key] = [complex_data.query(f'auc_score >= {t}').shape[0] for t in thresholds]
|
|
421
|
+
|
|
422
|
+
# Create figure
|
|
423
|
+
fig, ax = plt.subplots()
|
|
424
|
+
|
|
425
|
+
# Use colormap from rcParams
|
|
426
|
+
cmap = plt.get_cmap()
|
|
427
|
+
colors = [cmap(i / (num_datasets + 1)) for i in range(1, num_datasets + 1)]
|
|
428
|
+
|
|
429
|
+
# Plot bars
|
|
430
|
+
bar_width = 0.8 / num_datasets # Dynamic width based on dataset count
|
|
431
|
+
for i, dataset in enumerate(datasets):
|
|
432
|
+
x = np.arange(len(thresholds)) + i * bar_width
|
|
433
|
+
ax.bar(x, df[dataset], width=bar_width, color=colors[i], edgecolor='black', label=dataset)
|
|
434
|
+
|
|
435
|
+
# Customize x-axis labels
|
|
436
|
+
ax.set_xticks(np.arange(len(thresholds)) + (num_datasets - 1) * bar_width / 2)
|
|
437
|
+
ax.set_xticklabels(thresholds, rotation=0, ha='center')
|
|
438
|
+
|
|
439
|
+
# Set title and axis labels (handled by rcParams)
|
|
440
|
+
ax.set_title("Number of significant complexes above PR-AUC thresholds")
|
|
441
|
+
ax.set_xlabel("PR-AUC score thresholds")
|
|
442
|
+
ax.set_ylabel("Number of complexes")
|
|
443
|
+
|
|
444
|
+
# Add grid (already handled by rcParams, but ensured)
|
|
445
|
+
ax.grid(axis='y')
|
|
446
|
+
|
|
447
|
+
# Add legend
|
|
448
|
+
ax.legend(loc='upper right')
|
|
449
|
+
|
|
450
|
+
# Adjust layout
|
|
451
|
+
plt.tight_layout()
|
|
452
|
+
|
|
453
|
+
# Save figure if required
|
|
454
|
+
if plot_config["save_plot"]:
|
|
455
|
+
output_type = plot_config["output_type"]
|
|
456
|
+
output_folder = Path(config["output_folder"])
|
|
457
|
+
output_path = output_folder / f"number_of_significant_complexes.{output_type}"
|
|
458
|
+
plt.savefig(output_path, bbox_inches='tight', format=output_type)
|
|
459
|
+
|
|
460
|
+
if plot_config.get("show_plot", True):
|
|
461
|
+
plt.show()
|
|
462
|
+
|
|
463
|
+
plt.close(fig)
|
|
464
|
+
return df
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def plot_auc_scores():
|
|
469
|
+
config = dload("config")
|
|
470
|
+
plot_config = config["plotting"]
|
|
471
|
+
pra_dict = dload("pr_auc")
|
|
472
|
+
|
|
473
|
+
# Prepare data
|
|
474
|
+
datasets = list(pra_dict.keys())
|
|
475
|
+
auc_scores = list(pra_dict.values())
|
|
476
|
+
|
|
477
|
+
# Create figure and axis
|
|
478
|
+
fig, ax = plt.subplots()
|
|
479
|
+
|
|
480
|
+
# Use colormap from rcParams
|
|
481
|
+
cmap = plt.get_cmap()
|
|
482
|
+
num_datasets = len(datasets)
|
|
483
|
+
colors = [cmap(i / (num_datasets + 1)) for i in range(1, num_datasets + 1)]
|
|
484
|
+
|
|
485
|
+
# Plot bars
|
|
486
|
+
bars = ax.bar(datasets, auc_scores, color=colors, edgecolor="black")
|
|
487
|
+
|
|
488
|
+
# Set y-axis limits dynamically
|
|
489
|
+
ax.set_ylim(0, max(auc_scores) + 0.01)
|
|
490
|
+
|
|
491
|
+
# Set title and labels
|
|
492
|
+
ax.set_title("AUC scores for the datasets")
|
|
493
|
+
ax.set_ylabel("AUC score")
|
|
494
|
+
|
|
495
|
+
# Add grid (already handled by rcParams)
|
|
496
|
+
ax.grid(axis='y')
|
|
497
|
+
|
|
498
|
+
# Save the figure if required
|
|
499
|
+
if plot_config["save_plot"]:
|
|
500
|
+
output_type = plot_config["output_type"]
|
|
501
|
+
output_folder = Path(config["output_folder"])
|
|
502
|
+
output_folder.mkdir(parents=True, exist_ok=True)
|
|
503
|
+
output_path = output_folder / f"prauc_scores.{output_type}"
|
|
504
|
+
plt.savefig(output_path, bbox_inches='tight', format=output_type)
|
|
505
|
+
|
|
506
|
+
if plot_config.get("show_plot", True):
|
|
507
|
+
plt.show()
|
|
508
|
+
|
|
509
|
+
plt.close(fig)
|
|
510
|
+
return pra_dict
|