Oncodrive3D 1.0.4__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.
@@ -0,0 +1,2484 @@
1
+ import pandas as pd
2
+ import daiquiri
3
+ import logging
4
+ import numpy as np
5
+ from matplotlib import pyplot as plt
6
+ import seaborn as sns
7
+ import os
8
+ import json
9
+ import colorcet as cc
10
+ import warnings
11
+ from sklearn.preprocessing import StandardScaler
12
+ import statsmodels.api as sm
13
+ from statsmodels.tools.sm_exceptions import ConvergenceWarning
14
+ from adjustText import adjust_text
15
+ from matplotlib.axes._axes import Axes
16
+ from scripts.plotting.utils import get_broad_consequence, save_annotated_result
17
+ from scripts.plotting.utils import get_enriched_result, filter_o3d_result, subset_genes_and_ids, load_o3d_result
18
+
19
+ from scripts import __logger_name__
20
+
21
+ logger = daiquiri.getLogger(__logger_name__ + ".plotting.plot")
22
+
23
+ logging.getLogger('matplotlib.font_manager').setLevel(logging.WARNING)
24
+
25
+
26
+
27
+ # Summary plots
28
+ # =============
29
+
30
+ def get_summary_counts(gene_result, pos_result, seq_df):
31
+ """
32
+ Get dataframes including the counts required to generate the summary plots.
33
+ """
34
+
35
+ # Df with mut count
36
+ count_mut_gene_hit = gene_result[["Gene", "Clust_mut"]].rename(columns={"Clust_mut" : "Mut_in_gene"})
37
+ count_mut_gene_hit["C"] = "Mutations in clusters"
38
+ count_mut_gene_not = pd.DataFrame({"Gene" : count_mut_gene_hit["Gene"].values,
39
+ "Mut_in_gene" : gene_result.apply(lambda x: x["Mut_in_gene"] - x["Clust_mut"], axis=1)})
40
+ count_mut_gene_not["C"] = "Mutations not in clusters"
41
+ count_mut_gene = gene_result[["Gene", "Mut_in_gene"]].copy()
42
+ count_mut_gene["C"] = "Total mutations"
43
+
44
+ count_mut_gene_df = pd.concat((count_mut_gene_hit, count_mut_gene_not, count_mut_gene)).sort_values("Gene").rename(columns={"Mut_in_gene" : "Count"})
45
+ count_mut_gene_df = count_mut_gene_df.sort_values(["C", "Count"], ascending=False).reset_index(drop=True)
46
+
47
+ # Df with pos count
48
+ pos_result_not = pos_result[pos_result["C"] == 0]
49
+ if len(pos_result_not) > 0:
50
+ pos_result_not = pos_result_not.groupby("Gene").apply(len)
51
+ pos_result_not = pos_result_not.reset_index().rename(columns={0 : "Count"})
52
+ pos_result_not["C"] = "Residues not in clusters"
53
+ else:
54
+ pos_result_not = pd.DataFrame(columns=["Gene", "Count", "C"])
55
+ pos_result_hit = pos_result[pos_result["C"] == 1]
56
+ if len(pos_result_hit) > 0:
57
+ pos_result_hit = pos_result_hit.groupby("Gene").apply(len)
58
+ pos_result_hit = pos_result_hit.reset_index().rename(columns={0 : "Count"})
59
+ pos_result_hit["C"] = "Residues in clusters"
60
+ else:
61
+ pos_result_hit = pd.DataFrame(columns=["Gene", "Count", "C"])
62
+
63
+ pos_result_total = pd.DataFrame(seq_df.apply(lambda x: (x.Gene, len(x.Seq)), axis=1).to_list())
64
+ pos_result_total.columns = "Gene", "Count"
65
+ pos_result_total["C"] = "Protein length"
66
+
67
+ count_pos_df = pd.concat((pos_result_total, pos_result_hit, pos_result_not)).sort_values("Gene")
68
+ count_pos_df = count_pos_df.sort_values("C", ascending=False).reset_index(drop=True)
69
+
70
+ # Df with cluster count
71
+ cluster_df = pos_result.groupby("Gene").max("Clump").Clump.reset_index()
72
+ cluster_df["Clump"] = cluster_df["Clump"] + 1
73
+
74
+ return count_mut_gene_df, count_pos_df, cluster_df
75
+
76
+
77
+ def summary_plot(gene_result,
78
+ pos_result,
79
+ count_mut_gene_df,
80
+ count_pos_df,
81
+ cluster_df,
82
+ output_dir,
83
+ cohort,
84
+ plot_pars,
85
+ save_plot=True,
86
+ show_plot=False,
87
+ title=None):
88
+
89
+ # Init
90
+ h_ratios = plot_pars["summary_h_ratios"]
91
+ tracks = list(h_ratios.keys())
92
+
93
+ # Plot
94
+ fsize_x, fsize_y = plot_pars["summary_figsize"]
95
+ if len(gene_result) < 6:
96
+ fsize_x = 3
97
+ else:
98
+ fsize_x = fsize_x * len(gene_result)
99
+ fig, axes = plt.subplots(len(h_ratios), 1,
100
+ figsize=(fsize_x, fsize_y),
101
+ sharex=True,
102
+ gridspec_kw={'hspace': 0.1,
103
+ 'height_ratios': h_ratios.values()})
104
+
105
+ if "score" in tracks:
106
+ ax = tracks.index("score")
107
+ pos_result = pos_result.copy()
108
+ pos_result["C"] = pos_result.C.map({1 : "Volume in clusters", 0 : "Volume not in clusters"})
109
+ hue_order = ['Volume in clusters', 'Volume not in clusters']
110
+ sns.boxplot(x='Gene', y='Score_obs_sim', data=pos_result, order=gene_result.Gene, color=sns.color_palette("pastel")[7], showfliers=False, ax=axes[ax])
111
+ sns.stripplot(x='Gene', y='Score_obs_sim', data=pos_result, hue="C" ,jitter=True, size=6, alpha=plot_pars["summary_alpha"], order=gene_result.Gene.values,
112
+ palette=sns.color_palette("tab10", n_colors=2), hue_order=hue_order, ax=axes[ax])
113
+ axes[ax].set_ylabel('Clustering\nscore\n(obs/sim)', fontsize=12)
114
+ axes[ax].legend(fontsize=9.5, loc="upper right")
115
+ axes[ax].set_xlabel(None)
116
+
117
+ if "miss_count" in tracks:
118
+ ax = tracks.index("miss_count")
119
+ hue_order = ['Total mutations', 'Mutations in clusters']
120
+ custom_palette = [sns.color_palette("pastel")[7], sns.color_palette("pastel")[0]]
121
+ sns.barplot(x='Gene', y='Count', data=count_mut_gene_df[count_mut_gene_df["C"] != "Mutations not in clusters"],
122
+ order=gene_result.Gene, ax=axes[ax], hue="C", palette=custom_palette, hue_order=hue_order, ec="black", lw=0.5)
123
+ axes[ax].set_ylabel('Missense\nmut count', fontsize=12)
124
+ axes[ax].legend(fontsize=9.5, loc="upper right")
125
+ axes[ax].set_xlabel(None)
126
+
127
+ if "res_count" in tracks:
128
+ ax = tracks.index("res_count")
129
+ hue_order = ['Protein length', 'Residues in clusters']
130
+ sns.barplot(x='Gene', y='Count', data=count_pos_df[count_pos_df["C"] != "Residues not in clusters"], order=gene_result.Gene, hue="C", ax=axes[ax],
131
+ palette=custom_palette, hue_order=hue_order, ec="black", lw=0.5)
132
+ axes[ax].set_ylabel('Residues\ncount', fontsize=12)
133
+ axes[ax].legend(fontsize=9.5, loc="upper right")
134
+ axes[ax].set_xlabel(None)
135
+
136
+ if "res_clust_mut" in tracks:
137
+ ax = tracks.index("res_clust_mut")
138
+ count_mut_clusters = count_mut_gene_df[count_mut_gene_df["C"] == "Mutations in clusters"].drop(columns="C").rename(columns={"Count" : "Mut_count"})
139
+ count_res_clusters = count_pos_df[count_pos_df["C"] == "Residues in clusters"].drop(columns="C").rename(columns={"Count" : "Res_count"})
140
+ count_mut_res_clusters = count_mut_clusters.merge(count_res_clusters, on="Gene")
141
+ count_mut_res_clusters["Per_res_mut"] = np.round(count_mut_res_clusters["Mut_count"] / count_mut_res_clusters["Res_count"], 2)
142
+ sns.barplot(x='Gene', y='Per_res_mut', data=count_mut_res_clusters, order=gene_result.Gene, ax=axes[ax], color=sns.color_palette("pastel")[0], ec="black", lw=0.5)
143
+ axes[ax].set_ylabel('Per-residue\nmut\nin clusters', fontsize=12)
144
+ axes[ax].set_xlabel(None)
145
+
146
+ if "clusters" in tracks:
147
+ ax = tracks.index("clusters")
148
+ sns.barplot(x='Gene', y='Clump', data=cluster_df, order=gene_result.Gene, ax=axes[ax], color=sns.color_palette("pastel")[0], ec="black", lw=0.5)
149
+ axes[ax].set_ylabel('Clumps', fontsize=12)
150
+ axes[ax].set_xlabel(None)
151
+
152
+ # Details
153
+ if title:
154
+ fig.suptitle(f"{title} summary", fontsize=14)
155
+ else:
156
+ fig.suptitle(f"O3D analysis summary", fontsize=14)
157
+ xticks_labels = [ r'$\mathbf{*}$ ' + gene if gene_result.loc[gene_result["Gene"] == gene, "C_gene"].values[0] == 1 else gene for gene in gene_result.Gene]
158
+ axes[len(axes)-1].set_xticklabels(xticks_labels, rotation=45, rotation_mode="anchor", ha='right', fontsize=12)
159
+ plt.xticks(rotation=45, rotation_mode="anchor", ha='right', fontsize=12)
160
+ plt.subplots_adjust(top=0.94)
161
+
162
+ # Save
163
+ filename = f"{cohort}.summary_plot.png"
164
+ output_path = os.path.join(output_dir, filename)
165
+ if save_plot:
166
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
167
+ logger.debug(f"Saved {output_path}")
168
+ if show_plot:
169
+ plt.show()
170
+ plt.close()
171
+
172
+
173
+ # Gene plots
174
+ # ==========
175
+
176
+ def check_near_feat(uni_feat_gene, feat, dist_thr=0.05):
177
+ """
178
+ Check if two domains could be closer to each other
179
+ than allowed threshold (ratio of protein size).
180
+ """
181
+
182
+ near_feat = False
183
+ uni_feat_gene = uni_feat_gene.copy()
184
+
185
+ if feat == "domain" or feat == "pfam":
186
+ uni_feat_gene = uni_feat_gene[uni_feat_gene["Type"] == "DOMAIN"]
187
+ if feat == "pfam":
188
+ uni_feat_gene = uni_feat_gene[uni_feat_gene["Evidence"] == "Pfam"]
189
+ else:
190
+ uni_feat_gene = uni_feat_gene[uni_feat_gene["Evidence"] != "Pfam"]
191
+ uni_feat_gene = uni_feat_gene.drop_duplicates(subset='Description', keep='first')
192
+ elif feat == "motif":
193
+ uni_feat_gene = uni_feat_gene[uni_feat_gene["Type"] == "MOTIF"]
194
+ uni_feat_gene = uni_feat_gene.drop_duplicates(subset='Full_description', keep='first')
195
+
196
+ mid_pos = (uni_feat_gene.Begin + uni_feat_gene.End) / 2
197
+ mid_pos_norm = (mid_pos / mid_pos.max()).values
198
+
199
+ for i in range(len(mid_pos_norm)):
200
+ for j in range(i + 1, len(mid_pos_norm)):
201
+ diff = abs(mid_pos_norm[i] - mid_pos_norm[j])
202
+ if diff < dist_thr:
203
+ near_feat = True
204
+
205
+ return near_feat
206
+
207
+
208
+ def get_gene_arg(pos_result_gene, plot_pars, uni_feat_gene, maf_nonmiss=None):
209
+ """
210
+ Adjust the height ratio of tracks to include in the plot.
211
+ """
212
+
213
+ h_ratios = plot_pars["h_ratios"].copy()
214
+ plot_pars = plot_pars.copy()
215
+
216
+ track = "maf_nonmiss"
217
+ if track in h_ratios and not maf_nonmiss:
218
+ del h_ratios[track]
219
+
220
+ track = "maf_nonmiss_2"
221
+ if track in h_ratios and not maf_nonmiss:
222
+ del h_ratios[track]
223
+
224
+ track = "ddg"
225
+ if track in h_ratios and pos_result_gene["DDG"].isna().all():
226
+ del h_ratios[track]
227
+
228
+ track = "pae"
229
+ if track in h_ratios and np.isnan(pos_result_gene["PAE_vol"]).all():
230
+ del h_ratios[track]
231
+
232
+ track = "ptm"
233
+ if track in h_ratios:
234
+ if len(uni_feat_gene[uni_feat_gene["Type"] == "PTM"]) == 0:
235
+ del h_ratios[track]
236
+ else:
237
+ stracks = len(uni_feat_gene[uni_feat_gene["Type"] == "PTM"].Description.unique())
238
+ h_ratios[track] = h_ratios[track] * stracks
239
+
240
+ track = "site"
241
+ if track in h_ratios:
242
+ if len(uni_feat_gene[uni_feat_gene["Type"] == "SITE"]) == 0:
243
+ del h_ratios[track]
244
+ else:
245
+ stracks = len(uni_feat_gene[uni_feat_gene["Type"] == "SITE"].Description.unique())
246
+ h_ratios[track] = h_ratios[track] * stracks
247
+
248
+ track = "pfam"
249
+ if track in h_ratios:
250
+ if len(uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] == "pfam")]) == 1:
251
+ del h_ratios[track]
252
+ near_pfam = False
253
+ else:
254
+ near_pfam = check_near_feat(uni_feat_gene, feat=track, dist_thr=plot_pars["dist_thr"])
255
+ if near_pfam:
256
+ h_ratios[track] = h_ratios[track] * 2
257
+
258
+ track = "prosite"
259
+ if track in h_ratios:
260
+ if len(uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] != "Pfam")]) == 0:
261
+ del h_ratios[track]
262
+ near_prosite = False
263
+ else:
264
+ near_prosite = check_near_feat(uni_feat_gene, feat="domain", dist_thr=plot_pars["dist_thr"])
265
+ if near_prosite:
266
+ h_ratios[track] = h_ratios[track] * 2
267
+
268
+ track = "membrane"
269
+ if track in h_ratios:
270
+ if len(uni_feat_gene[uni_feat_gene["Type"] == "MEMBRANE"]) == 0:
271
+ del h_ratios[track]
272
+ else:
273
+ stracks = len(uni_feat_gene[uni_feat_gene["Type"] == "MEMBRANE"].Description.unique())
274
+ h_ratios[track] = h_ratios[track] * stracks
275
+
276
+ track = "motif"
277
+ if track in h_ratios:
278
+ if len(uni_feat_gene[uni_feat_gene["Type"] == "MOTIF"]) == 0:
279
+ del h_ratios[track]
280
+ near_motif = False
281
+ else:
282
+ near_motif = check_near_feat(uni_feat_gene, feat="motif", dist_thr=0.1)
283
+ if near_motif:
284
+ h_ratios[track] = h_ratios[track] * 1.8
285
+
286
+ h_ratios = {k:v/sum(h_ratios.values()) for k,v in h_ratios.items()}
287
+
288
+ return h_ratios, near_pfam, near_prosite, near_motif
289
+
290
+
291
+ def filter_non_processed_mut(maf, pos_result):
292
+ """
293
+ Get rid of mutations of the input file that were not processed.
294
+ """
295
+ # TO DO: In this way, I am not getting rid of the mismatches ones.. I should think about something else for these ones
296
+ len_maf = len(maf)
297
+ maf = maf[maf.apply(lambda x: f"{x.Gene}_{x.Pos}", axis=1).isin(pos_result.apply(lambda x: f"{x.Gene}_{x.Pos}", axis=1))]
298
+ logger.debug(f"Filtered out {len_maf - len(maf)} ({(len_maf - len(maf))/len_maf*100:.2f}%) mutations out of {len_maf} not processed during 3D-clustering analysis!")
299
+
300
+ return maf
301
+
302
+
303
+ def capitalize(string):
304
+ words = string.split("_")
305
+ words[0] = words[0].capitalize()
306
+
307
+ return ' '.join(words)
308
+
309
+
310
+ def get_nonmiss_mut(path_to_maf):
311
+ """
312
+ Get non missense mutations from MAF file.
313
+ """
314
+ try:
315
+ maf_nonmiss = pd.read_csv(path_to_maf, sep="\t", dtype={'Chromosome': str})
316
+ maf_nonmiss = maf_nonmiss[maf_nonmiss["Protein_position"] != "-"] ## TODO: Fix it for alternative MAF (see cancer)
317
+ maf_nonmiss = maf_nonmiss[~(maf_nonmiss['Consequence'].str.contains('Missense_Mutation')
318
+ | maf_nonmiss['Consequence'].str.contains('missense_variant'))]
319
+ maf_nonmiss = maf_nonmiss[["SYMBOL",
320
+ "Consequence",
321
+ "Protein_position"]].rename(
322
+ columns={"SYMBOL" : "Gene",
323
+ "Protein_position" : "Pos"}).reset_index(drop=True)
324
+
325
+ # Parse the consequence with multiple elements and get broader categories
326
+ maf_nonmiss["Consequence"] = get_broad_consequence(maf_nonmiss["Consequence"])
327
+
328
+ return maf_nonmiss
329
+
330
+ except Exception as e:
331
+ logger.warning("Can't parse non-missense mutation from MAF file: The track will not be included...")
332
+ logger.warning(f"{e}")
333
+
334
+
335
+ def avg_per_pos_ddg(pos_result_gene, ddg_prot, maf_gene):
336
+ """
337
+ Compute per-position average stability change upon mutations (DDG).
338
+ """
339
+
340
+ ddg_vec = np.repeat(0., len(pos_result_gene))
341
+ for pos, group in maf_gene.groupby('Pos'):
342
+ pos = str(pos)
343
+ obs_mut = group.Mut
344
+ if pos in ddg_prot:
345
+ ddg_pos = ddg_prot[pos]
346
+ ddg_pos = np.mean([ddg_pos[mut] for mut in obs_mut])
347
+ ddg_vec[int(pos)-1] = ddg_pos
348
+
349
+ return ddg_vec
350
+
351
+
352
+ def parse_pos_result_for_genes_plot(pos_result_gene, c_ext=True):
353
+ """
354
+ Get mut count and score divided by Oncodriv3D
355
+ result: significant, not significant, significant extended
356
+ (mutation in a non-significant residue that contribute to
357
+ mutations in the volume of a significant one/s).
358
+ """
359
+
360
+ pos_result_gene = pos_result_gene.copy()
361
+ pos_result_gene = pos_result_gene[["Pos", "Mut_in_res", "Mut_in_vol", "Score_obs_sim", "C", "C_ext", "pval", "Clump", "PAE_vol"]]
362
+ if not c_ext:
363
+ pos_result_gene["C"] = pos_result_gene.apply(
364
+ lambda x: 1 if (x["C"] == 1) & (x["C_ext"] == 0) else 2 if (x["C"] == 1) & (x["C_ext"] == 1) else 0, axis=1)
365
+ max_mut = np.max(pos_result_gene["Mut_in_res"].values)
366
+
367
+ return pos_result_gene, max_mut
368
+
369
+
370
+ def get_count_for_genes_plot(maf, maf_nonmiss, gene, non_missense_count=False):
371
+ """
372
+ Get missense and non-missense mutations count.
373
+ """
374
+
375
+ mut_count = maf.value_counts("Pos").reset_index()
376
+ mut_count = mut_count.rename(columns={0 : "Count"})
377
+ if non_missense_count:
378
+ maf_nonmiss_gene = maf_nonmiss[maf_nonmiss["Gene"] == gene]
379
+ mut_count_nonmiss = maf_nonmiss_gene.groupby("Consequence").value_counts("Pos").reset_index()
380
+ mut_count_nonmiss = mut_count_nonmiss.rename(columns={0 : "Count"})
381
+ # If there is more than one position affected, take the first one
382
+ ix_more_than_one_pos = mut_count_nonmiss.apply(lambda x: len(x["Pos"].split("-")), axis=1) > 1
383
+ mut_count_nonmiss.loc[ix_more_than_one_pos, "Pos"] = mut_count_nonmiss.loc[ix_more_than_one_pos].apply(lambda x: x["Pos"].split("-")[0], axis=1)
384
+ # Filter non-numerical Pos and get count
385
+ mut_count_nonmiss = mut_count_nonmiss[mut_count_nonmiss["Pos"].apply(lambda x: x.isdigit() or x.isnumeric())]
386
+ mut_count_nonmiss["Pos"] = mut_count_nonmiss["Pos"].astype(int)
387
+ else:
388
+ mut_count_nonmiss = None
389
+
390
+ return mut_count, mut_count_nonmiss
391
+
392
+
393
+ def get_score_for_genes_plot(pos_result_gene, mut_count, prob_vec):
394
+ """
395
+ Add any non-mutated position to the pos_result df, get
396
+ per-position score and normalized score.
397
+ """
398
+
399
+ pos_result_gene = pos_result_gene.copy()
400
+ score_vec = []
401
+ for pos in range(1, len(prob_vec)+1):
402
+
403
+ # Mut count
404
+ if pos in mut_count.Pos.values:
405
+ if pos not in pos_result_gene.Pos.values:
406
+ logger.error("Position in MAF not found in position-level O3D result: Check that MAF and O3D result are matching!")
407
+ score = pos_result_gene.loc[pos_result_gene["Pos"] == pos, "Score_obs_sim"].values[0]
408
+ else:
409
+ score = 0
410
+ row_gene = pd.DataFrame({'Pos': [pos], 'Mut_in_res': [0], 'Score_obs_sim': [np.nan], 'C': [np.nan]})
411
+ pos_result_gene = pd.concat([pos_result_gene, row_gene])
412
+
413
+ score_vec.append(score)
414
+
415
+ pos_result_gene = pos_result_gene.sort_values("Pos").reset_index(drop=True)
416
+
417
+ # Normalize score
418
+ if np.isnan(score_vec).any():
419
+ score_vec = pd.Series(score_vec).fillna(max(score_vec)).values
420
+ score_norm_vec = np.array(score_vec) / sum(score_vec)
421
+
422
+ return pos_result_gene, score_vec, score_norm_vec
423
+
424
+
425
+ def get_id_annotations(uni_id, pos_result_gene, maf_gene, annotations_dir, disorder, pdb_tool, uniprot_feat):
426
+ """
427
+ Get the annotations for a specific protein ID.
428
+ """
429
+
430
+ pos_result_gene = pos_result_gene.copy()
431
+ disorder_gene = disorder[disorder["Uniprot_ID"] == uni_id].reset_index(drop=True)
432
+ pdb_tool_gene = pdb_tool[pdb_tool["Uniprot_ID"] == uni_id].reset_index(drop=True)
433
+ uni_feat_gene = uniprot_feat[uniprot_feat["Uniprot_ID"] == uni_id].reset_index(drop=True)
434
+ ddg_path = os.path.join(annotations_dir, "stability_change", f"{uni_id}_ddg.json")
435
+ if os.path.isfile(ddg_path):
436
+ ddg = json.load(open(ddg_path))
437
+ ddg_vec = avg_per_pos_ddg(pos_result_gene, ddg, maf_gene)
438
+ pos_result_gene["DDG"] = ddg_vec
439
+ else:
440
+ pos_result_gene["DDG"] = np.nan
441
+ logger.debug(f"Stability change of {uni_id} not found. Path {ddg_path} doesn't exist: Skipping..")
442
+
443
+ # Avoid duplicates (Uniprot IDs mapping to the different gene names)
444
+ uni_feat_gene = uni_feat_gene.drop(columns=["Gene", "Ens_Transcr_ID", "Ens_Gene_ID"]).drop_duplicates()
445
+
446
+ return pos_result_gene, disorder_gene, pdb_tool_gene, uni_feat_gene
447
+
448
+
449
+ def get_site_pos(site_df):
450
+
451
+ positions = []
452
+ for begin, end in zip(site_df['Begin'], site_df['End']):
453
+ positions.extend(np.arange(begin, end + 1))
454
+
455
+ return np.array(positions)
456
+
457
+
458
+ def genes_plots(gene_result,
459
+ pos_result,
460
+ seq_df,
461
+ maf,
462
+ maf_nonmiss,
463
+ miss_prob_dict,
464
+ output_dir,
465
+ cohort,
466
+ annotations_dir,
467
+ disorder,
468
+ uniprot_feat,
469
+ pdb_tool,
470
+ plot_pars,
471
+ save_plot=True,
472
+ show_plot=False,
473
+ title=None,
474
+ c_ext=True):
475
+ """
476
+ Generate a diagnostic plot for each gene showing Oncodrive3D
477
+ results and annotated features.
478
+ """
479
+
480
+ annotated_result_lst = []
481
+ uni_feat_result_lst = []
482
+ for j, gene in enumerate(gene_result["Gene"].values):
483
+
484
+
485
+ # Load and parse
486
+ # ==============
487
+
488
+ # IDs
489
+ uni_id = seq_df[seq_df["Gene"] == gene].Uniprot_ID.values[0]
490
+ af_f = seq_df[seq_df["Gene"] == gene].F.values[0]
491
+ gene_len = len(seq_df[seq_df["Gene"] == gene].Seq.values[0])
492
+ maf_gene = maf[maf["Gene"] == gene]
493
+
494
+ # Parse
495
+ pos_result_gene = pos_result[pos_result["Gene"] == gene].sort_values("Pos").reset_index(drop=True)
496
+
497
+ if len(pos_result_gene) > 0:
498
+
499
+ pos_result_gene = pos_result_gene[["Pos", "Mut_in_res", "Mut_in_vol",
500
+ "Score_obs_sim", "C", "C_ext",
501
+ "pval", "Clump", "PAE_vol"]]
502
+ pos_result_gene, max_mut = parse_pos_result_for_genes_plot(pos_result_gene, c_ext=c_ext)
503
+
504
+ # Counts
505
+ mut_count, mut_count_nonmiss = get_count_for_genes_plot(maf_gene,
506
+ maf_nonmiss,
507
+ gene,
508
+ non_missense_count="nonmiss_count" in plot_pars["h_ratios"])
509
+
510
+ # Get prob vec
511
+ prob_vec = miss_prob_dict[f"{uni_id}-F{af_f}"] # TODO: If none, use uniform <-------------------------- TODO
512
+
513
+ # Get per-pos score and normalize score
514
+ pos_result_gene, score_vec, score_norm_vec = get_score_for_genes_plot(pos_result_gene,
515
+ mut_count,
516
+ prob_vec)
517
+
518
+ # Get annotations
519
+ pos_result_gene, disorder_gene, pdb_tool_gene, uni_feat_gene = get_id_annotations(uni_id,
520
+ pos_result_gene,
521
+ maf_gene,
522
+ annotations_dir,
523
+ disorder,
524
+ pdb_tool,
525
+ uniprot_feat)
526
+
527
+
528
+ # Generate plot
529
+ # =============
530
+
531
+ h_ratios, near_pfam, near_prosite, near_motif = get_gene_arg(pos_result_gene, plot_pars, uni_feat_gene, maf_nonmiss=maf_nonmiss)
532
+ annotations = list(h_ratios.keys())
533
+ fig, axes = plt.subplots(len(h_ratios), 1,
534
+ figsize=(24,12),
535
+ sharex=True,
536
+ gridspec_kw={'hspace': 0.1,
537
+ 'height_ratios': h_ratios.values()})
538
+
539
+
540
+ # Plot for Non-missense mut track
541
+ # -------------------------------
542
+ if "nonmiss_count" in annotations:
543
+ ax = annotations.index("nonmiss_count")
544
+ if len(mut_count_nonmiss.Consequence.unique()) > 6:
545
+ ncol = 3
546
+ else:
547
+ ncol = 2
548
+ i = 0
549
+ axes[ax].vlines(mut_count_nonmiss["Pos"], ymin=0, ymax=mut_count_nonmiss["Count"],
550
+ color="gray", lw=0.7, zorder=0, alpha=0.5) # To cover the overlapping needle top part
551
+ axes[ax].scatter(mut_count_nonmiss["Pos"], mut_count_nonmiss["Count"], color='white', zorder=4, lw=plot_pars["s_lw"])
552
+ for cnsq in mut_count_nonmiss.Consequence.unique():
553
+ count_cnsq = mut_count_nonmiss[mut_count_nonmiss["Consequence"] == cnsq]
554
+ if cnsq == "synonymous_variant":
555
+ order = 1
556
+ else:
557
+ order = 2
558
+ if cnsq in plot_pars["color_cnsq"]:
559
+ color = plot_pars["color_cnsq"][cnsq]
560
+ else:
561
+ color=sns.color_palette("tab10")[i]
562
+ i+=1
563
+ axes[ax].scatter(count_cnsq.Pos.values, count_cnsq.Count.values, label=capitalize(cnsq),
564
+ color=color, zorder=order, alpha=0.7, lw=plot_pars["s_lw"], ec="black") # ec="black",
565
+ axes[ax].legend(fontsize=11.5, ncol=ncol, framealpha=0.75)
566
+ axes[ax].set_ylabel('Non\nmissense\nmutations', fontsize=13.5, rotation=0, va='center')
567
+ axes[ax].set_ylim(-0.5, mut_count_nonmiss["Count"].max()+0.5)
568
+ axes[ax].set_ylim(0, max(mut_count_nonmiss["Count"])*1.1)
569
+
570
+
571
+ # Plot for Missense Mut_in_res track
572
+ # ----------------------------------
573
+ if "miss_count" in annotations:
574
+ ax = annotations.index("miss_count")
575
+
576
+ axes[ax].vlines(mut_count["Pos"], ymin=0, ymax=mut_count["Count"], color="gray", lw=0.7, zorder=1, alpha=0.5)
577
+
578
+ mut_pos = pos_result_gene[pos_result_gene["Mut_in_res"] > 0].Pos.values
579
+ mut_res_pos = pos_result_gene[pos_result_gene["Mut_in_res"] > 0].Mut_in_res.values
580
+ # mut_vol_pos = pos_result_gene[pos_result_gene["Mut_in_res"] > 0].Mut_in_vol.values
581
+
582
+ axes[ax].scatter(mut_pos, mut_res_pos, color='white', zorder=3, lw=plot_pars["s_lw"], ec="white") # To cover the overlapping needle top part
583
+ axes[ax].scatter(mut_pos, mut_res_pos, color='gray', zorder=4, alpha=0.7, lw=plot_pars["s_lw"], ec="black", s=60)
584
+
585
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, max_mut, where=(pos_result_gene['C'] == 1),
586
+ color='skyblue', alpha=0.3, label='Position in cluster', zorder=0, lw=2)
587
+ # axes[ax].fill_between(pos_result_gene['Pos'], 0, max_mut, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
588
+ # color='#ffd8b1', alpha=0.6, label='Mutated not *', zorder=0)
589
+ axes[ax].legend(fontsize=11.5, ncol=2, framealpha=0.75)
590
+ axes[ax].set_ylabel('Missense\nmutations', fontsize=13.5, rotation=0, va='center')
591
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
592
+ axes[ax].set_ylim(0-(max(mut_res_pos)*0.04), max(mut_res_pos)*1.1)
593
+
594
+
595
+ legend = axes[ax].legend(fontsize=11.5, ncol=1, framealpha=0.75, bbox_to_anchor=(0.97, 1.5), borderaxespad=0.)
596
+ legend.set_title("Global legend")
597
+ legend.get_title().set_fontsize(12)
598
+
599
+ # Plot for Miss prob track
600
+ # ----------------------------------
601
+ if "miss_prob" in annotations:
602
+ ax = annotations.index("miss_prob")
603
+
604
+ max_value = np.max(prob_vec)
605
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, max_value, where=(pos_result_gene['C'] == 1),
606
+ color='skyblue', alpha=0.3, label='Position in cluster', zorder=0, lw=2)
607
+
608
+ # axes[ax].hlines(0, xmin=0, xmax=gene_len, color="gray", lw=0.6, zorder=1)
609
+ axes[ax].plot(range(1, len(prob_vec)+1), prob_vec, zorder=3, color="C2", lw=1)
610
+ axes[ax].set_ylabel('Missense\nmut prob', fontsize=13.5, rotation=0, va='center')
611
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
612
+
613
+ # Plot for Score track
614
+ # ----------------------------------
615
+ if "score" in annotations:
616
+ ax = annotations.index("score")
617
+
618
+ max_value = np.max(score_vec)
619
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, max_value, where=(pos_result_gene['C'] == 1),
620
+ color='skyblue', alpha=0.3, label='Position in cluster', zorder=0)
621
+
622
+ # axes[ax].hlines(0, xmin=0, xmax=gene_len, color="gray", lw=0.7, zorder=1)
623
+ axes[ax].plot(range(1, len(score_vec)+1), score_vec, zorder=2, color="C2", lw=1)
624
+
625
+ # handles, labels = axes[ax].get_legend_handles_labels()
626
+ # axes[ax].legend(fontsize=11.5, framealpha=0.75, ncol=2)
627
+ axes[ax].set_ylabel('Clustering\nscore\n(obs/sim)', fontsize=13.5, rotation=0, va='center')
628
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
629
+
630
+
631
+ # Plot annotations
632
+ # ================
633
+
634
+ # Plot PAE
635
+ # ----------------------------------
636
+ if "pae" in annotations:
637
+ ax = annotations.index("pae")
638
+
639
+ max_value = np.max(pos_result_gene["PAE_vol"])
640
+ # axes[ax+3].fill_between(pos_result_gene['Pos'], 0, max_value, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
641
+ # color='#ffd8b1', alpha=0.6)
642
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, max_value, where=(pos_result_gene['C'] == 1),
643
+ color='white', lw=2)
644
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, max_value, where=(pos_result_gene['C'] == 1),
645
+ color='skyblue', alpha=0.3, lw=2)
646
+ axes[ax].fill_between(pos_result_gene["Pos"], 0, pos_result_gene["PAE_vol"].fillna(0),
647
+ zorder=2, color="white")
648
+ axes[ax].fill_between(pos_result_gene["Pos"], 0, pos_result_gene["PAE_vol"].fillna(0),
649
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
650
+ axes[ax].plot(pos_result_gene['Pos'], pos_result_gene["PAE_vol"].fillna(0),
651
+ label="Confidence", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
652
+ axes[ax].set_ylabel('Predicted\naligned error\n(Å)', fontsize=13.5, rotation=0, va='center')
653
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
654
+
655
+ # Plot disorder
656
+ # -------------
657
+ if "disorder" in annotations:
658
+ ax = annotations.index("disorder")
659
+
660
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, 100, where=(pos_result_gene['C'] == 1),
661
+ color='white', lw=2)
662
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, 100, where=(pos_result_gene['C'] == 1),
663
+ color='skyblue', alpha=0.4, label='Mutated *', lw=2)
664
+
665
+ axes[ax].fill_between(disorder_gene["Pos"], 0, disorder_gene["Confidence"].fillna(0),
666
+ zorder=2, color="white")
667
+ axes[ax].fill_between(disorder_gene["Pos"], 0, disorder_gene["Confidence"].fillna(0),
668
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
669
+
670
+
671
+ axes[ax].plot(disorder_gene["Pos"], disorder_gene["Confidence"],
672
+ label="Confidence", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
673
+ axes[ax].set_ylabel('pLDDT\n(disorder)', fontsize=13.5, rotation=0, va='center')
674
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
675
+ axes[ax].set_ylim(-10, 110)
676
+
677
+ # Plot pACC
678
+ # ---------
679
+ if "pacc" in annotations:
680
+ ax = annotations.index("pacc")
681
+
682
+ # axes[ax+5].fill_between(pos_result_gene['Pos'], 0, 100, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
683
+ # color='#ffd8b1', alpha=0.6)
684
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, 100, where=(pos_result_gene['C'] == 1),
685
+ color='white', lw=2)
686
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, 100, where=(pos_result_gene['C'] == 1),
687
+ color='skyblue', alpha=0.4, lw=2)
688
+ axes[ax].fill_between(pdb_tool_gene["Pos"], 0, pdb_tool_gene["pACC"].fillna(0),
689
+ zorder=2, color="white")
690
+ axes[ax].fill_between(pdb_tool_gene["Pos"], 0, pdb_tool_gene["pACC"].fillna(0),
691
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
692
+ axes[ax].plot(pdb_tool_gene['Pos'], pdb_tool_gene["pACC"].fillna(0),
693
+ label="pACC", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
694
+ axes[ax].set_ylabel('Solvent\naccessibility', fontsize=13.5, rotation=0, va='center')
695
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
696
+ axes[ax].set_ylim(-10, 110)
697
+
698
+ # Plot stability change
699
+ # ---------------------
700
+ if "ddg" in annotations:
701
+ ax = annotations.index("ddg")
702
+
703
+ max_value, min_value = pos_result_gene["DDG"].max(), pos_result_gene["DDG"].min()
704
+ # axes[ax+6].fill_between(pos_result_gene['Pos'], min_value, max_value, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
705
+ # color='#ffd8b1', alpha=0.6)
706
+ if sum(pos_result_gene['C'] == 1) > 0:
707
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
708
+ color='white', lw=2)
709
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
710
+ color='skyblue', alpha=0.4, lw=2)
711
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, pos_result_gene["DDG"], zorder=1,
712
+ color="white")
713
+ axes[ax].fill_between(pos_result_gene['Pos'], 0, pos_result_gene["DDG"], zorder=1,
714
+ color=sns.color_palette("pastel")[4], alpha=0.6)
715
+ axes[ax].plot(pos_result_gene['Pos'], pos_result_gene["DDG"],
716
+ label="Stability change", zorder=2, color=sns.color_palette("tab10")[4], lw=0.5)
717
+ axes[ax].set_ylabel('ΔΔG (kcal/mol)', fontsize=13.5, rotation=0, va='center')
718
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
719
+
720
+ # PTM
721
+ # --------------
722
+ if "ptm" in annotations:
723
+ ax = annotations.index("ptm")
724
+
725
+ ptm_gene = uni_feat_gene[uni_feat_gene["Type"] == "PTM"]
726
+ ptm_names = ptm_gene["Description"].unique()
727
+ sb_width = 0.5
728
+ max_value = (len(ptm_names) * sb_width) - 0.2
729
+ min_value = - 0.3
730
+
731
+ # axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
732
+ # color='#ffd8b1', alpha=0.6, label='Mutated not *')
733
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
734
+ color='white', lw=2)
735
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
736
+ color='skyblue', alpha=0.4, label='Mutated *', lw=2)
737
+
738
+ for n, name in enumerate(ptm_names):
739
+ c = sns.color_palette("tab10")[n]
740
+ ptm = ptm_gene[ptm_gene["Description"] == name]
741
+ ptm_pos = ptm.Begin.values
742
+ axes[ax].scatter(ptm_pos, np.repeat(n*sb_width, len(ptm_pos)), label=name, alpha=0.7, color=c) #label=name
743
+ axes[ax].hlines(y=n*sb_width, xmin=0, xmax=gene_len, linewidth=1, color='lightgray', alpha=0.7, zorder=0)
744
+
745
+ axes[ax].set_ylim(min_value, max_value)
746
+ y_ticks_positions = sb_width * np.arange(len(ptm_names))
747
+ axes[ax].set_yticks(y_ticks_positions)
748
+ axes[ax].set_yticklabels(ptm_names)
749
+ axes[ax].set_ylabel(' PTM ', fontsize=13.5, rotation=0, va='center')
750
+
751
+ # SITES
752
+ # --------------
753
+ if "site" in annotations:
754
+ ax = annotations.index("site")
755
+
756
+ site_gene = uni_feat_gene[uni_feat_gene["Type"] == "SITE"]
757
+ site_names = site_gene["Description"].unique()
758
+ sb_width = 0.5
759
+ max_value = (len(site_names) * sb_width) - 0.2
760
+ min_value = - 0.3
761
+
762
+ # axes[ax+8].fill_between(pos_result_gene['Pos'], min_value, max_value, where=((pos_result_gene["C"] == 0) | (pos_result_gene["C"] == 2)),
763
+ # color='#ffd8b1', alpha=0.6, label='Mutated not *')
764
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
765
+ color='white', lw=2)
766
+ axes[ax].fill_between(pos_result_gene['Pos'], min_value, max_value, where=(pos_result_gene['C'] == 1),
767
+ color='skyblue', alpha=0.4, label='Mutated *', lw=2)
768
+
769
+ for n, name in enumerate(site_names):
770
+ c = sns.color_palette("tab10")[n]
771
+ site_df = site_gene[site_gene["Description"] == name]
772
+ site_pos = get_site_pos(site_df)
773
+ axes[ax].scatter(site_pos, np.repeat(n*sb_width, len(site_pos)), label=name, alpha=0.7, color=c)
774
+ axes[ax].hlines(y=n*sb_width, xmin=0, xmax=gene_len, linewidth=1, color='lightgray', alpha=0.7, zorder=0)
775
+
776
+ axes[ax].set_ylim(min_value, max_value)
777
+ y_ticks_positions = sb_width * np.arange(len(site_names))
778
+ axes[ax].set_yticks(y_ticks_positions)
779
+ axes[ax].set_yticklabels(site_names)
780
+ axes[ax].set_ylabel('Site ', fontsize=13.5, rotation=0, va='center')
781
+
782
+ # Clusters label
783
+ # --------------
784
+ if "clusters" in annotations:
785
+ ax = annotations.index("clusters")
786
+
787
+ clusters_label = pos_result_gene.Clump.dropna().unique()
788
+ palette = sns.color_palette(cc.glasbey, n_colors=len(clusters_label))
789
+ for i, cluster in enumerate(clusters_label):
790
+ axes[ax].fill_between(pos_result_gene['Pos'], -0.5, 0.46,
791
+ where=((pos_result_gene['Clump'] == cluster) & (pos_result_gene['C'] == 1)),
792
+ color=palette[i], lw=0.4) # alpha=0.6
793
+ axes[ax].set_ylabel('Clumps', fontsize=13.5, rotation=0, va='center')
794
+ axes[ax].set_yticks([])
795
+ axes[ax
796
+ ].set_yticklabels([], fontsize=12)
797
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
798
+
799
+ # Secondary structure
800
+ # -------------------
801
+ if "sse" in annotations:
802
+ ax = annotations.index("sse")
803
+
804
+ for i, sse in enumerate(['Helix', 'Ladder', 'Coil']):
805
+ c = 0+i
806
+ ya, yb = c-plot_pars["sse_fill_width"], c+plot_pars["sse_fill_width"]
807
+ axes[ax].fill_between(pdb_tool_gene["Pos"].values, ya, yb, where=(pdb_tool_gene["SSE"] == sse),
808
+ color=sns.color_palette("tab10")[7+i], label=sse)
809
+ axes[ax].set_yticks([0, 1, 2])
810
+ axes[ax].set_yticklabels(['Helix', 'Ladder', 'Coil'], fontsize=10)
811
+ axes[ax].set_ylabel('SSE', fontsize=13.5, rotation=0, va='center')
812
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
813
+
814
+ # Pfam
815
+ # ----
816
+ if "pfam" in annotations:
817
+ ax = annotations.index("pfam")
818
+
819
+ pfam_gene = uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] == "Pfam")]
820
+ pfam_gene = pfam_gene.sort_values("Begin").reset_index(drop=True)
821
+ pfam_color_dict = {}
822
+
823
+ for n, name in enumerate(pfam_gene["Description"].unique()):
824
+ pfam_color_dict[name] = f"C{n}"
825
+
826
+ n = 0
827
+ added_pfam = []
828
+ for i, row in pfam_gene.iterrows():
829
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
830
+ continue
831
+
832
+ name = row["Description"]
833
+ start = int(row["Begin"])
834
+ end = int(row["End"])
835
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=pfam_color_dict[name])
836
+ if name not in added_pfam:
837
+ if near_pfam:
838
+ n += 1
839
+ if n == 1:
840
+ y = 0.28
841
+ elif n == 2:
842
+ y = 0
843
+ elif n == 3:
844
+ y = -0.295
845
+ n = 0
846
+ else:
847
+ y = -0.04
848
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
849
+ added_pfam.append(name)
850
+ axes[ax].set_yticks([])
851
+ axes[ax].set_yticklabels([], fontsize=12)
852
+ axes[ax].set_ylabel('Pfam', fontsize=13.5, rotation=0, va='center')
853
+ axes[ax].set_ylim(-0.5, 0.5)
854
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
855
+
856
+ # Prosite
857
+ # -------
858
+ if "prosite" in annotations:
859
+ ax = annotations.index("prosite")
860
+
861
+ prosite_gene = uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] != "Pfam")]
862
+
863
+ prosite_gene = prosite_gene.sort_values("Begin").reset_index(drop=True)
864
+ prosite_color_dict = {}
865
+
866
+ for n, name in enumerate(prosite_gene["Description"].unique()):
867
+ prosite_color_dict[name] = f"C{n}"
868
+
869
+ n = 0
870
+ added_prosite = []
871
+ for i, row in prosite_gene.iterrows():
872
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
873
+ continue
874
+
875
+ name = row["Description"]
876
+ start = int(row["Begin"])
877
+ end = int(row["End"])
878
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=prosite_color_dict[name])
879
+ if name not in added_prosite:
880
+ if near_prosite:
881
+ n += 1
882
+ if n == 1:
883
+ y = 0.28
884
+ elif n == 2:
885
+ y = 0
886
+ elif n == 3:
887
+ y = -0.295
888
+ n = 0
889
+ else:
890
+ y = -0.04
891
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
892
+ added_prosite.append(name)
893
+ axes[ax].set_yticks([])
894
+ axes[ax].set_yticklabels([], fontsize=12)
895
+ axes[ax].set_ylabel('Prosite', fontsize=13.5, rotation=0, va='center')
896
+ axes[ax].set_ylim(-0.5, 0.5)
897
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
898
+
899
+ # Membrane
900
+ # --------
901
+ if "membrane" in annotations:
902
+ ax = annotations.index("membrane")
903
+
904
+ membrane_gene = uni_feat_gene[(uni_feat_gene["Type"] == "MEMBRANE")]
905
+ membrane_color_dict = {}
906
+
907
+ for n, name in enumerate(membrane_gene["Description"].unique()):
908
+ membrane_color_dict[name] = f"C{n}"
909
+
910
+ n = 0
911
+ added_membrane = []
912
+ for i, row in membrane_gene.iterrows():
913
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
914
+ continue
915
+
916
+ name = row["Description"]
917
+ start = int(row["Begin"])
918
+ end = int(row["End"])
919
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=membrane_color_dict[name])
920
+ if name not in added_membrane:
921
+ y = -0.04
922
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
923
+ added_membrane.append(name)
924
+ axes[ax].set_yticks([])
925
+ axes[ax].set_yticklabels([], fontsize=12)
926
+ axes[ax].set_ylabel('Membrane', fontsize=13.5, rotation=0, va='center')
927
+ axes[ax].set_ylim(-0.5, 0.5)
928
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
929
+
930
+ # Motifs
931
+ # ------
932
+ if "motif" in annotations:
933
+ ax = annotations.index("motif")
934
+
935
+ motif_gene = uni_feat_gene[(uni_feat_gene["Type"] == "MOTIF")]
936
+
937
+ motif_gene = motif_gene.sort_values("Begin").reset_index(drop=True)
938
+ motif_color_dict = {}
939
+
940
+ for n, name in enumerate(motif_gene["Full_description"].unique()):
941
+ motif_color_dict[name] = f"C{n}"
942
+
943
+ n = 0
944
+ added_motif = []
945
+ for i, row in motif_gene.iterrows():
946
+ if pd.Series([row["Full_description"], row["Begin"], row["End"]]).isnull().any():
947
+ continue
948
+
949
+ name = row["Full_description"]
950
+ start = int(row["Begin"])
951
+ end = int(row["End"])
952
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=motif_color_dict[name])
953
+ if name not in added_motif:
954
+ if near_motif:
955
+ n += 1
956
+ if n == 1:
957
+ y = 0.28
958
+ elif n == 2:
959
+ y = 0
960
+ elif n == 3:
961
+ y = -0.295
962
+ n = 0
963
+ else:
964
+ y = -0.04
965
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
966
+ added_motif.append(name)
967
+ axes[ax].set_yticks([])
968
+ axes[ax].set_yticklabels([], fontsize=12)
969
+ axes[ax].set_ylabel('Motif', fontsize=13.5, rotation=0, va='center')
970
+ axes[ax].set_ylim(-0.5, 0.5)
971
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
972
+
973
+ axes[len(axes)-1].set_xlabel(None)
974
+
975
+ # Save
976
+ # ====
977
+ if title:
978
+ fig.suptitle(f'{title}\n{gene} - {uni_id}', fontsize=16)
979
+ else:
980
+ fig.suptitle(f'{gene} - {uni_id}', fontsize=16)
981
+ filename = f"{cohort}.genes_plot_{j+1}.{gene}_{uni_id}.png"
982
+ output_path = os.path.join(output_dir, filename)
983
+ htop = 0.947
984
+ if title:
985
+ htop -= 0.018
986
+ plt.subplots_adjust(top=htop)
987
+
988
+ if save_plot:
989
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
990
+ logger.debug(f"Saved {output_path}")
991
+ if show_plot:
992
+ plt.show()
993
+ plt.close()
994
+
995
+ # Store annotated result
996
+ pos_result_gene = get_enriched_result(pos_result_gene,
997
+ disorder_gene,
998
+ pdb_tool_gene,
999
+ seq_df)
1000
+ annotated_result_lst.append(pos_result_gene)
1001
+ uni_feat_result_lst.append(uni_feat_gene)
1002
+
1003
+ pos_result_annotated = pd.concat(annotated_result_lst)
1004
+ feat_processed = pd.concat(uni_feat_result_lst)
1005
+
1006
+ return pos_result_annotated, feat_processed
1007
+
1008
+
1009
+ # Comparative plots
1010
+ # =================
1011
+
1012
+ def comparative_plots(shared_genes,
1013
+ pos_result_1,
1014
+ maf_1,
1015
+ maf_nonmiss_1,
1016
+ miss_prob_dict_1,
1017
+ cohort_1,
1018
+ pos_result_2,
1019
+ maf_2,
1020
+ maf_nonmiss_2,
1021
+ miss_prob_dict_2,
1022
+ cohort_2,
1023
+ seq_df,
1024
+ output_dir,
1025
+ annotations_dir,
1026
+ disorder,
1027
+ uniprot_feat,
1028
+ pdb_tool,
1029
+ plot_pars,
1030
+ save_plot=True,
1031
+ show_plot=False):
1032
+ """
1033
+ Generate plot to compare each gene that are processed in both 3D-clustering analysis.
1034
+ """
1035
+
1036
+ warnings.filterwarnings("ignore", category=UserWarning)
1037
+
1038
+ for j, gene in enumerate(shared_genes):
1039
+
1040
+ logger.debug(f"Generating comparative plots for {len(shared_genes)} genes..")
1041
+
1042
+ # Load and parse
1043
+ # ==============
1044
+
1045
+ uni_id = seq_df[seq_df["Gene"] == gene].Uniprot_ID.values[0]
1046
+ af_f = seq_df[seq_df["Gene"] == gene].F.values[0]
1047
+ gene_len = len(seq_df[seq_df["Gene"] == gene].Seq.values[0])
1048
+ maf_gene_1 = maf_1[maf_1["Gene"] == gene]
1049
+ maf_gene_2 = maf_2[maf_2["Gene"] == gene]
1050
+
1051
+ # Parse
1052
+ pos_result_gene_1 = pos_result_1[pos_result_1["Gene"] == gene].sort_values("Pos").reset_index(drop=True)
1053
+ pos_result_gene_2 = pos_result_2[pos_result_2["Gene"] == gene].sort_values("Pos").reset_index(drop=True)
1054
+
1055
+ if len(pos_result_gene_1) > 0 and len(pos_result_gene_2) > 0:
1056
+ pos_result_gene_1 = pos_result_gene_1[["Pos", "Mut_in_res", "Mut_in_vol",
1057
+ "Score_obs_sim", "C", "C_ext",
1058
+ "pval", "Clump", "PAE_vol"]]
1059
+ pos_result_gene_2 = pos_result_gene_2[["Pos", "Mut_in_res", "Mut_in_vol",
1060
+ "Score_obs_sim", "C", "C_ext",
1061
+ "pval", "Clump", "PAE_vol"]]
1062
+ pos_result_gene_1, max_mut_1 = parse_pos_result_for_genes_plot(pos_result_gene_1)
1063
+ pos_result_gene_2, max_mut_2 = parse_pos_result_for_genes_plot(pos_result_gene_2)
1064
+
1065
+ # Counts
1066
+ mut_count_1, mut_count_nonmiss_1 = get_count_for_genes_plot(maf_gene_1,
1067
+ maf_nonmiss_1,
1068
+ gene,
1069
+ non_missense_count="nonmiss_count" in plot_pars["h_ratios"])
1070
+ mut_count_2, mut_count_nonmiss_2 = get_count_for_genes_plot(maf_gene_2,
1071
+ maf_nonmiss_2,
1072
+ gene,
1073
+ non_missense_count="nonmiss_count" in plot_pars["h_ratios"])
1074
+
1075
+ # Get prob vec
1076
+ prob_vec_1 = np.array(miss_prob_dict_1[f"{uni_id}-F{af_f}"])
1077
+ prob_vec_2 = np.array(miss_prob_dict_2[f"{uni_id}-F{af_f}"])
1078
+
1079
+ # Get per-pos score and normalize score
1080
+ pos_result_gene_1, score_vec_1, score_norm_vec_1 = get_score_for_genes_plot(pos_result_gene_1,
1081
+ mut_count_1,
1082
+ prob_vec_1)
1083
+ pos_result_gene_2, score_vec_2, score_norm_vec_2 = get_score_for_genes_plot(pos_result_gene_2,
1084
+ mut_count_2,
1085
+ prob_vec_2)
1086
+
1087
+ # Get annotations
1088
+ pos_result_gene_1, disorder_gene, pdb_tool_gene, uni_feat_gene = get_id_annotations(uni_id,
1089
+ pos_result_gene_1,
1090
+ maf_gene_1,
1091
+ annotations_dir,
1092
+ disorder,
1093
+ pdb_tool,
1094
+ uniprot_feat)
1095
+ pos_result_gene_2, _, _, _ = get_id_annotations(uni_id,
1096
+ pos_result_gene_2,
1097
+ maf_gene_2,
1098
+ annotations_dir,
1099
+ disorder,
1100
+ pdb_tool,
1101
+ uniprot_feat)
1102
+
1103
+ # Pos result for background filling
1104
+ pos_result_gene_shared = pos_result_gene_1.copy()
1105
+ pos_result_gene_shared["C"] = np.nan
1106
+ pos_result_gene_shared["C_A"] = pos_result_gene_1["C"]
1107
+ pos_result_gene_shared["C_B"] = pos_result_gene_2["C"]
1108
+ pos_result_gene_shared["C"] = pos_result_gene_shared.apply(lambda x:
1109
+ "A" if x.C_A == 1 and x.C_B != 1 else
1110
+ "B" if x.C_A != 1 and x.C_B == 1 else
1111
+ "AB" if x.C_A == 1 and x.C_B == 1 else np.nan, axis=1)
1112
+
1113
+ # Generate plot
1114
+ # =============
1115
+
1116
+ if not maf_nonmiss_1 or not maf_nonmiss_2:
1117
+ maf_nonmiss = None
1118
+ else:
1119
+ maf_nonmiss = maf_nonmiss_1
1120
+ h_ratios, near_pfam, near_prosite, near_motif = get_gene_arg(pd.concat((pos_result_gene_1, pos_result_gene_2)),
1121
+ plot_pars,
1122
+ uni_feat_gene,
1123
+ maf_nonmiss)
1124
+ annotations = list(h_ratios.keys())
1125
+
1126
+ fig, axes = plt.subplots(len(h_ratios), 1,
1127
+ figsize=plot_pars["figsize"],
1128
+ sharex=True,
1129
+ gridspec_kw={'hspace': 0.1,
1130
+ 'height_ratios': h_ratios.values()})
1131
+
1132
+
1133
+ # Plot for Non-missense mut track ## TO DO: Enable not mirror for non-missense
1134
+ # -------------------------------
1135
+ if "nonmiss_count" in annotations:
1136
+ ax = annotations.index("nonmiss_count")
1137
+
1138
+ if len(mut_count_nonmiss_1.Consequence.unique()) > 6:
1139
+ ncol = 3
1140
+ else:
1141
+ ncol = 2
1142
+ i = 0
1143
+ axes[ax].vlines(mut_count_nonmiss_1["Pos"], ymin=0, ymax=mut_count_nonmiss_1["Count"],
1144
+ color="gray", lw=0.7, zorder=0, alpha=0.5) # To cover the overlapping needle top part
1145
+ axes[ax].scatter(mut_count_nonmiss_1["Pos"], mut_count_nonmiss_1["Count"], color='white', zorder=4, lw=plot_pars["s_lw"])
1146
+ for cnsq in mut_count_nonmiss_1.Consequence.unique():
1147
+ count_cnsq = mut_count_nonmiss_1[mut_count_nonmiss_1["Consequence"] == cnsq]
1148
+ if cnsq == "synonymous_variant":
1149
+ order = 1
1150
+ else:
1151
+ order = 2
1152
+ if cnsq in plot_pars["color_cnsq"]:
1153
+ color = plot_pars["color_cnsq"][cnsq]
1154
+ else:
1155
+ color=sns.color_palette("tab10")[i]
1156
+ i+=1
1157
+ axes[ax].scatter(count_cnsq.Pos.values, count_cnsq.Count.values, label=capitalize(cnsq),
1158
+ color=color, zorder=order, alpha=0.7, lw=plot_pars["s_lw"], ec="black") # ec="black",
1159
+ axes[ax].legend(fontsize=11.5, ncol=ncol, framealpha=0.75)
1160
+ axes[ax].set_ylabel('Non\nmissense\nmutations', fontsize=13.5, rotation=0, va='center')
1161
+ ymargin = max(max(mut_count_nonmiss_1["Count"]), max(mut_count_nonmiss_2["Count"])) * 0.1
1162
+ axes[ax].set_ylim(-(max(mut_count_nonmiss_1["Count"])-ymargin), max(mut_count_nonmiss_1["Count"])+ymargin)
1163
+
1164
+
1165
+ # Plot for Missense mut track
1166
+ # ---------------------------
1167
+ mut_pos_1 = pos_result_gene_1[pos_result_gene_1["Mut_in_res"] > 0].Pos.values
1168
+ mut_res_pos_1 = pos_result_gene_1[pos_result_gene_1["Mut_in_res"] > 0].Mut_in_res.values
1169
+ mut_pos_2 = pos_result_gene_2[pos_result_gene_2["Mut_in_res"] > 0].Pos.values
1170
+ mut_res_pos_2 = pos_result_gene_2[pos_result_gene_2["Mut_in_res"] > 0].Mut_in_res.values
1171
+
1172
+ if plot_pars["count_mirror"]:
1173
+ if "miss_count" in annotations:
1174
+ ax = annotations.index("miss_count")
1175
+
1176
+ axes[ax].hlines(0, xmin=0, xmax=gene_len, color="gray", lw=0.6, zorder=1)
1177
+ axes[ax].vlines(mut_count_1["Pos"], ymin=0, ymax=mut_count_1["Count"], color="gray", lw=0.7, zorder=1, alpha=0.5) # A
1178
+ axes[ax].vlines(mut_count_2["Pos"], ymin=-mut_count_2["Count"], ymax=0, color="gray", lw=0.7, zorder=1, alpha=0.5) # B
1179
+
1180
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, 0, where=(pos_result_gene_1['C'] == "NA"),
1181
+ color=sns.color_palette("pastel")[2], alpha=0.4, label='Position in cluster A', zorder=0, lw=2) # Just for the legend
1182
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, 0, where=(pos_result_gene_1['C'] == "NA"),
1183
+ color=sns.color_palette("pastel")[3], alpha=0.4, label='Position in cluster B', zorder=0, lw=2) # Just for the legend
1184
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], -max_mut_2, max_mut_1,
1185
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1186
+ color='skyblue', alpha=0.4, label='Position in cluster A or B', zorder=0, lw=2)
1187
+
1188
+ axes[ax].scatter(mut_pos_1, mut_res_pos_1, color='white', zorder=3, lw=plot_pars["s_lw"], ec="white") # A
1189
+ axes[ax].scatter(mut_pos_2, -mut_res_pos_2, color='white', zorder=3, lw=plot_pars["s_lw"], ec="white") # B
1190
+
1191
+ axes[ax].scatter(mut_pos_1, mut_res_pos_1, color="C2", zorder=4, alpha=0.6, # A
1192
+ lw=plot_pars["s_lw"], ec="black", s=60, label='Cohort A')
1193
+ axes[ax].scatter(mut_pos_2, -mut_res_pos_2, color="tomato", zorder=4, alpha=0.6, # B
1194
+ lw=plot_pars["s_lw"], ec="black", s=60, label='Cohort B')
1195
+
1196
+
1197
+ legend = axes[ax].legend(fontsize=11.5, ncol=2, framealpha=0.75, bbox_to_anchor=(0.95, 2.3),
1198
+ borderaxespad=0., loc='upper right')
1199
+ legend.set_title("Global legend")
1200
+ legend.get_title().set_fontsize(12)
1201
+
1202
+ axes[ax].set_ylabel('Missense\nmutations', fontsize=13.5, rotation=0, va='center')
1203
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1204
+ ymargin = max(max(mut_res_pos_2), max(mut_res_pos_1)) * 0.1
1205
+ axes[ax].set_ylim(-max(mut_res_pos_2)-ymargin, max(mut_res_pos_1)+ymargin)
1206
+
1207
+ tick_labels = [f'{abs(label):.4g}' for label in axes[ax].get_yticks()]
1208
+ axes[ax].set_yticklabels(tick_labels)
1209
+
1210
+ else:
1211
+ if "miss_count" in annotations and "miss_count_2" in annotations:
1212
+
1213
+ # A
1214
+ ax = annotations.index("miss_count")
1215
+ axes[ax].vlines(mut_count_1["Pos"], ymin=0, ymax=mut_count_1["Count"], color="gray", lw=0.7, zorder=1, alpha=0.5)
1216
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, max(mut_res_pos_1), where=(pos_result_gene_1['C'] == 1),
1217
+ color=sns.color_palette("pastel")[2], alpha=0.4, label='Position in cluster A', zorder=0, lw=2)
1218
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, 0, where=(pos_result_gene_1['C'] == "NA"),
1219
+ color=sns.color_palette("pastel")[3], alpha=0.4, label='Position in cluster B', zorder=0, lw=2) # Just for the legend
1220
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, 0, where=(pos_result_gene_1['C'] == "NA"),
1221
+ color="skyblue", alpha=0.4, label='Position in cluster A or B', zorder=0, lw=2) # Just for the legend
1222
+
1223
+ axes[ax].scatter(mut_pos_1, mut_res_pos_1, color='white', zorder=3, lw=plot_pars["s_lw"], ec="white")
1224
+ axes[ax].scatter(mut_pos_1, mut_res_pos_1, color="C2", zorder=4, alpha=0.6,
1225
+ lw=plot_pars["s_lw"], ec="black", s=60, label='Cohort A')
1226
+ axes[ax].scatter(-20, -20, color="tomato", zorder=4, alpha=0.6, # Just for the legend
1227
+ lw=plot_pars["s_lw"], ec="black", s=60, label='Cohort B')
1228
+
1229
+ legend = axes[ax].legend(fontsize=11.5, ncol=2, framealpha=0.75,
1230
+ bbox_to_anchor=(0.95, 2.45), borderaxespad=0., loc='upper right')
1231
+ legend.set_title("Global legend")
1232
+ legend.get_title().set_fontsize(12)
1233
+
1234
+ axes[ax].set_ylabel('Missense\nmutations A', fontsize=13.5, rotation=0, va='center')
1235
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1236
+ ymargin = max(mut_res_pos_1) * 0.1
1237
+ axes[ax].set_ylim(0-ymargin, max(mut_res_pos_1)+ymargin)
1238
+
1239
+ # B
1240
+ ax = annotations.index("miss_count_2")
1241
+ axes[ax].vlines(mut_count_2["Pos"], ymin=0, ymax=mut_count_2["Count"], color="gray", lw=0.7, zorder=1, alpha=0.5)
1242
+ axes[ax].fill_between(pos_result_gene_2['Pos'], 0, max(mut_res_pos_2), where=(pos_result_gene_2['C'] == 1),
1243
+ color=sns.color_palette("pastel")[3], alpha=0.4, label='Position in cluster B', zorder=0, lw=2)
1244
+ axes[ax].scatter(mut_pos_2, mut_res_pos_2, color='white', zorder=3, lw=plot_pars["s_lw"], ec="white")
1245
+ axes[ax].scatter(mut_pos_2, mut_res_pos_2, color="tomato", zorder=4, alpha=0.6,
1246
+ lw=plot_pars["s_lw"], ec="black", s=60, label='Cohort B')
1247
+ axes[ax].set_ylabel('Missense\nmutations B', fontsize=13.5, rotation=0, va='center')
1248
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1249
+ ymargin = max(mut_res_pos_2) * 0.1
1250
+ axes[ax].set_ylim(0-ymargin, max(mut_res_pos_2)+ymargin)
1251
+
1252
+
1253
+ # Plot for Miss prob track
1254
+ # ------------------------
1255
+ if "miss_prob" in annotations:
1256
+ ax = annotations.index("miss_prob")
1257
+
1258
+ if plot_pars["prob_mirror"]:
1259
+ max_value = max(prob_vec_1)
1260
+ min_value = -max(prob_vec_2)
1261
+ prob_vec_2 = -np.array(prob_vec_2)
1262
+ else:
1263
+ max_value = max(max(prob_vec_2), max(prob_vec_1))
1264
+ min_value = 0
1265
+
1266
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], min_value, max_value,
1267
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1268
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1269
+
1270
+ axes[ax].hlines(0, xmin=0, xmax=gene_len, color="gray", lw=0.6, zorder=1)
1271
+ axes[ax].plot(range(1, len(prob_vec_1)+1), prob_vec_1, label="Cohort A", zorder=3, color="C2", lw=1)
1272
+ axes[ax].plot(range(1, len(prob_vec_2)+1), prob_vec_2, label="Cohort B", zorder=3,
1273
+ color="tomato", lw=1)
1274
+
1275
+ axes[ax].set_ylabel('Missense\nmut prob', fontsize=13.5, rotation=0, va='center')
1276
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1277
+ if plot_pars["prob_mirror"]:
1278
+ tick_labels = [f'{abs(label):.4g}' for label in axes[ax].get_yticks()]
1279
+ axes[ax].set_yticklabels(tick_labels)
1280
+
1281
+
1282
+ # Plot for Score track
1283
+ # --------------------
1284
+ if plot_pars["score_mirror"]:
1285
+
1286
+ if "score" in annotations:
1287
+ ax = annotations.index("score")
1288
+
1289
+ max_value = np.max(score_vec_1)
1290
+ min_value = -np.max(score_vec_2)
1291
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], min_value, max_value,
1292
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1293
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1294
+ axes[ax].hlines(0, xmin=0, xmax=gene_len, color="gray", lw=0.7, zorder=1)
1295
+ axes[ax].plot(range(1, len(prob_vec_1)+1), score_vec_1, label="Cohort A", zorder=2, color="C2", lw=1)
1296
+ axes[ax].plot(range(1, len(prob_vec_2)+1), -np.array(score_vec_2), label="Cohort B", zorder=2, color="tomato", lw=1)
1297
+
1298
+ axes[ax].set_ylabel('Clustering\nscore\n(obs/sim)', fontsize=13.5, rotation=0, va='center')
1299
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1300
+
1301
+ tick_labels = [f'{abs(label):.4g}' for label in axes[ax].get_yticks()]
1302
+ axes[ax].set_yticklabels(tick_labels)
1303
+
1304
+ else:
1305
+ if "score" in annotations and "score_2" in annotations:
1306
+
1307
+ # A
1308
+ ax = annotations.index("score")
1309
+
1310
+ max_value = np.max(score_vec_1)
1311
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, max_value, where=(pos_result_gene_1['C'] == 1),
1312
+ color=sns.color_palette("pastel")[2], alpha=0.4, label='Position in cluster A', zorder=0, lw=2)
1313
+ axes[ax].plot(range(1, len(prob_vec_1)+1), score_vec_1, label="Cohort A", zorder=2, color="C2", lw=1)
1314
+ axes[ax].set_ylabel('Clustering\nscore A\n(obs/sim)', fontsize=13.5, rotation=0, va='center')
1315
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1316
+
1317
+ # B
1318
+ ax = annotations.index("score_2")
1319
+
1320
+ max_value = np.max(score_vec_2)
1321
+ axes[ax].fill_between(pos_result_gene_2['Pos'], 0, max_value, where=(pos_result_gene_2['C'] == 1),
1322
+ color=sns.color_palette("pastel")[3], alpha=0.4, label='Position in cluster B', zorder=0, lw=2)
1323
+ axes[ax].plot(range(1, len(prob_vec_2)+1), np.array(score_vec_2), label="Cohort B", zorder=2, color="tomato", lw=1)
1324
+ axes[ax].set_ylabel('Clustering\nscore B\n(obs/sim)', fontsize=13.5, rotation=0, va='center')
1325
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1326
+
1327
+
1328
+ # Clusters label A
1329
+ # ----------------
1330
+
1331
+ # A
1332
+ if "clusters" in annotations:
1333
+ ax = annotations.index("clusters")
1334
+
1335
+ clusters_label = pos_result_gene_1.Clump.dropna().unique()
1336
+ clusters_label_2 = pos_result_gene_2.Clump.dropna().unique()
1337
+ n_colors = max(len(clusters_label), len(clusters_label_2))
1338
+ palette = sns.color_palette(cc.glasbey, n_colors=n_colors)
1339
+ for i, cluster in enumerate(clusters_label):
1340
+ axes[ax].fill_between(pos_result_gene_1['Pos'], -0.5, 0.46,
1341
+ where=((pos_result_gene_1['Clump'] == cluster) & (pos_result_gene_1['C'] == 1)),
1342
+ color=palette[i], lw=0.4) # alpha=0.6
1343
+ axes[ax].set_ylabel('Clusters A ', fontsize=13.5, rotation=0, va='center')
1344
+ axes[ax].set_yticks([])
1345
+ axes[ax].yaxis.set_label_coords(-0.034, 0.5)
1346
+
1347
+ # B
1348
+ if "clusters_2" in annotations:
1349
+ ax = annotations.index("clusters_2")
1350
+
1351
+ clusters_label_2 = pos_result_gene_2.Clump.dropna().unique()
1352
+ for i, cluster in enumerate(clusters_label_2):
1353
+ axes[ax].fill_between(pos_result_gene_2['Pos'], -0.5, 0.46,
1354
+ where=((pos_result_gene_2['Clump'] == cluster) & (pos_result_gene_2['C'] == 1)),
1355
+ color=palette[i], lw=0.4) # alpha=0.6
1356
+ axes[ax].set_ylabel('Clusters B ', fontsize=13.5, rotation=0, va='center')
1357
+ axes[ax].set_yticks([])
1358
+ axes[ax].yaxis.set_label_coords(-0.034, 0.5)
1359
+
1360
+
1361
+ # Plot annotations
1362
+ # ================
1363
+
1364
+ # Plot PAE
1365
+ # --------
1366
+ if "pae" in annotations:
1367
+ ax = annotations.index("pae")
1368
+
1369
+ max_value = np.max(pos_result_gene_1["PAE_vol"])
1370
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], 0, max_value,
1371
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1372
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1373
+ axes[ax].fill_between(pos_result_gene_1["Pos"], 0, pos_result_gene_1["PAE_vol"].fillna(0),
1374
+ zorder=2, color="white")
1375
+ axes[ax].fill_between(pos_result_gene_1["Pos"], 0, pos_result_gene_1["PAE_vol"].fillna(0),
1376
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
1377
+ axes[ax].plot(pos_result_gene_1['Pos'], pos_result_gene_1["PAE_vol"].fillna(0),
1378
+ label="Confidence", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
1379
+ axes[ax].set_ylabel('Predicted\naligned\nerror\n(Å)', fontsize=13.5, rotation=0, va='center')
1380
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1381
+
1382
+
1383
+ # Plot disorder
1384
+ # -------------
1385
+ if "disorder" in annotations:
1386
+ ax = annotations.index("disorder")
1387
+
1388
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], 0, 100,
1389
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1390
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1391
+
1392
+ # ## Comment out to use AF color palette
1393
+
1394
+ # af_colors = ["#1F6AD7",
1395
+ # "#65CBF3",
1396
+ # "#FFDC48",
1397
+ # "#FB7C44"]
1398
+
1399
+ # disorder_x, disorder_y = interpolate_x_y(disorder_gene["Pos"], disorder_gene["Confidence"])
1400
+ # condition_1 = disorder_y > 90
1401
+ # condition_2 = disorder_y <= 90
1402
+ # condition_3 = disorder_y <= 70
1403
+ # condition_4 = disorder_y <= 50
1404
+ # conditions = [condition_1, condition_2, condition_3, condition_4]
1405
+ # for color, condition in zip(af_colors, conditions):
1406
+ # axes[ax].fill_between(disorder_x, 0, disorder_y, where=(condition),
1407
+ # zorder=2, color="white")
1408
+ # axes[ax].fill_between(disorder_x, 0, disorder_y, where=(condition),
1409
+ # zorder=3, facecolor=color, alpha=0.8)
1410
+
1411
+ axes[ax].fill_between(disorder_gene["Pos"], 0, disorder_gene["Confidence"].fillna(0),
1412
+ zorder=2, color="white")
1413
+ axes[ax].fill_between(disorder_gene["Pos"], 0, disorder_gene["Confidence"].fillna(0),
1414
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
1415
+
1416
+
1417
+ axes[ax].plot(disorder_gene["Pos"], disorder_gene["Confidence"],
1418
+ label="Confidence", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
1419
+ axes[ax].set_ylabel('pLDDT\n(disorder)', fontsize=13.5, rotation=0, va='center')
1420
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1421
+ axes[ax].set_ylim(-10, 110)
1422
+
1423
+
1424
+ # Plot pACC
1425
+ # ---------
1426
+ if "pacc" in annotations:
1427
+ ax = annotations.index("pacc")
1428
+
1429
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], 0, 100,
1430
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1431
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1432
+ axes[ax].fill_between(pdb_tool_gene["Pos"], 0, pdb_tool_gene["pACC"].fillna(0),
1433
+ zorder=2, color="white")
1434
+ axes[ax].fill_between(pdb_tool_gene["Pos"], 0, pdb_tool_gene["pACC"].fillna(0),
1435
+ zorder=2, color=sns.color_palette("pastel")[4], alpha=0.6)
1436
+ axes[ax].plot(pdb_tool_gene['Pos'], pdb_tool_gene["pACC"].fillna(0),
1437
+ label="pACC", zorder=3, color=sns.color_palette("tab10")[4], lw=0.5)
1438
+ axes[ax].set_ylabel('Solvent\naccessibility', fontsize=13.5, rotation=0, va='center')
1439
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1440
+ axes[ax].set_ylim(-10, 110)
1441
+
1442
+
1443
+ # Plot stability change A
1444
+ # -----------------------
1445
+ if "ddg" in annotations:
1446
+ ax = annotations.index("ddg")
1447
+
1448
+ max_value, min_value = pos_result_gene_1["DDG"].max(), pos_result_gene_1["DDG"].min()
1449
+
1450
+ axes[ax].fill_between(pos_result_gene_1['Pos'], min_value, max_value, where=(pos_result_gene_1['C'] == 1),
1451
+ color=sns.color_palette("pastel")[2], alpha=0.4, label='Position in cluster A', zorder=0, lw=2)
1452
+
1453
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, pos_result_gene_1["DDG"], zorder=1,
1454
+ color="white")
1455
+ axes[ax].fill_between(pos_result_gene_1['Pos'], 0, pos_result_gene_1["DDG"], zorder=1,
1456
+ color=sns.color_palette("pastel")[4], alpha=0.6)
1457
+ axes[ax].plot(pos_result_gene_1['Pos'], pos_result_gene_1["DDG"],
1458
+ label="Stability change", zorder=2, color=sns.color_palette("tab10")[4], lw=0.5)
1459
+ axes[ax].set_ylabel('ΔΔG A\n(kcal/mol)', fontsize=13.5, rotation=0, va='center')
1460
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1461
+
1462
+
1463
+ # Plot stability change B
1464
+ # -----------------------
1465
+ if "ddg_2" in annotations:
1466
+ ax = annotations.index("ddg_2")
1467
+
1468
+ max_value, min_value = pos_result_gene_2["DDG"].max(), pos_result_gene_1["DDG"].min()
1469
+
1470
+ axes[ax].fill_between(pos_result_gene_2['Pos'], min_value, max_value, where=(pos_result_gene_2['C'] == 1),
1471
+ color=sns.color_palette("pastel")[3], alpha=0.4, label='Position in cluster B', zorder=0, lw=2)
1472
+
1473
+ axes[ax].fill_between(pos_result_gene_2['Pos'], 0, pos_result_gene_2["DDG"], zorder=1,
1474
+ color="white")
1475
+ axes[ax].fill_between(pos_result_gene_2['Pos'], 0, pos_result_gene_2["DDG"], zorder=1,
1476
+ color=sns.color_palette("pastel")[4], alpha=0.6)
1477
+ axes[ax].plot(pos_result_gene_2['Pos'], pos_result_gene_2["DDG"],
1478
+ label="Stability change", zorder=2, color=sns.color_palette("tab10")[4], lw=0.5)
1479
+ axes[ax].set_ylabel('ΔΔG B\n(kcal/mol)', fontsize=13.5, rotation=0, va='center')
1480
+ axes[ax].yaxis.set_label_coords(-0.06, 0.5)
1481
+
1482
+
1483
+ # PTM
1484
+ # ---
1485
+ if "ptm" in annotations:
1486
+ ax = annotations.index("ptm")
1487
+
1488
+ ptm_gene = uni_feat_gene[uni_feat_gene["Type"] == "PTM"]
1489
+ ptm_names = ptm_gene["Description"].unique()
1490
+ sb_width = 0.5
1491
+ max_value = (len(ptm_names) * sb_width) - 0.2
1492
+ min_value = - 0.3
1493
+
1494
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], min_value, max_value,
1495
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1496
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1497
+
1498
+ for n, name in enumerate(ptm_names):
1499
+ c = sns.color_palette("tab10")[n]
1500
+ ptm = ptm_gene[ptm_gene["Description"] == name]
1501
+ ptm_pos = ptm.Begin.values
1502
+ axes[ax].scatter(ptm_pos, np.repeat(n*sb_width, len(ptm_pos)), label=name, alpha=0.7, color=c) #label=name
1503
+ axes[ax].hlines(y=n*sb_width, xmin=0, xmax=gene_len, linewidth=1, color='lightgray', alpha=0.7, zorder=0)
1504
+
1505
+ axes[ax].set_ylim(min_value, max_value)
1506
+ y_ticks_positions = sb_width * np.arange(len(ptm_names))
1507
+ axes[ax].set_yticks(y_ticks_positions)
1508
+ axes[ax].set_yticklabels(ptm_names)
1509
+ axes[ax].set_ylabel(' PTM ', fontsize=13.5, rotation=0, va='center')
1510
+
1511
+
1512
+ # SITES
1513
+ # --------------
1514
+ if "site" in annotations:
1515
+ ax = annotations.index("site")
1516
+
1517
+ site_gene = uni_feat_gene[uni_feat_gene["Type"] == "SITE"]
1518
+ site_names = site_gene["Description"].unique()
1519
+ sb_width = 0.5
1520
+ max_value = (len(site_names) * sb_width) - 0.2
1521
+ min_value = - 0.3
1522
+
1523
+ axes[ax].fill_between(pos_result_gene_shared['Pos'], min_value, max_value,
1524
+ where=(pos_result_gene_shared['C'] == "A") | (pos_result_gene_shared['C'] == "B") | (pos_result_gene_shared['C'] == "AB"),
1525
+ color='skyblue', alpha=0.4, label='Position in cluster', zorder=0, lw=2)
1526
+
1527
+ for n, name in enumerate(site_names):
1528
+ c = sns.color_palette("tab10")[n]
1529
+ site = site_gene[site_gene["Description"] == name]
1530
+ site_pos = site.Begin.values
1531
+ axes[ax].scatter(site_pos, np.repeat(n*sb_width, len(site_pos)), label=name, alpha=0.7, color=c) #label=name
1532
+ axes[ax].hlines(y=n*sb_width, xmin=0, xmax=gene_len, linewidth=1, color='lightgray', alpha=0.7, zorder=0)
1533
+
1534
+ axes[ax].set_ylim(min_value, max_value)
1535
+ y_ticks_positions = sb_width * np.arange(len(site_names))
1536
+ axes[ax].set_yticks(y_ticks_positions)
1537
+ axes[ax].set_yticklabels(site_names)
1538
+ axes[ax].set_ylabel('Site ', fontsize=13.5, rotation=0, va='center')
1539
+
1540
+
1541
+ # Secondary structure
1542
+ # -------------------
1543
+ if "sse" in annotations:
1544
+ ax = annotations.index("sse")
1545
+
1546
+ for i, sse in enumerate(['Helix', 'Ladder', 'Coil']):
1547
+ c = 0+i
1548
+ ya, yb = c-plot_pars["sse_fill_width"], c+plot_pars["sse_fill_width"]
1549
+ axes[ax].fill_between(pdb_tool_gene["Pos"].values, ya, yb, where=(pdb_tool_gene["SSE"] == sse),
1550
+ color=sns.color_palette("tab10")[7+i], label=sse)
1551
+ axes[ax].set_yticks([0, 1, 2])
1552
+ axes[ax].set_yticklabels(['Helix', 'Ladder', 'Coil'], fontsize=10)
1553
+ axes[ax].set_ylabel('SSE ', fontsize=13.5, rotation=0, va='center')
1554
+ axes[ax].yaxis.set_label_coords(-0.051, 0.5)
1555
+
1556
+
1557
+ # Pfam
1558
+ # ----
1559
+ if "pfam" in annotations:
1560
+ ax = annotations.index("pfam")
1561
+
1562
+ pfam_gene = uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] == "Pfam")]
1563
+ pfam_gene = pfam_gene.sort_values("Begin").reset_index(drop=True)
1564
+ pfam_color_dict = {}
1565
+
1566
+ for n, name in enumerate(pfam_gene["Description"].unique()):
1567
+ pfam_color_dict[name] = f"C{n}"
1568
+
1569
+ n = 0
1570
+ added_pfam = []
1571
+ for i, row in pfam_gene.iterrows():
1572
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
1573
+ continue
1574
+
1575
+ name = row["Description"]
1576
+ start = int(row["Begin"])
1577
+ end = int(row["End"])
1578
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=pfam_color_dict[name])
1579
+ if name not in added_pfam:
1580
+ if near_pfam:
1581
+ n += 1
1582
+ if n == 1:
1583
+ y = 0.28
1584
+ elif n == 2:
1585
+ y = 0
1586
+ elif n == 3:
1587
+ y = -0.295
1588
+ n = 0
1589
+ else:
1590
+ y = -0.04
1591
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
1592
+ added_pfam.append(name)
1593
+ axes[ax].set_yticks([])
1594
+ axes[ax].set_yticklabels([], fontsize=12)
1595
+ axes[ax].set_ylabel('Pfam ', fontsize=13.5, rotation=0, va='center')
1596
+ axes[ax].set_ylim(-0.5, 0.5)
1597
+ axes[ax].yaxis.set_label_coords(-0.051, 0.5)
1598
+
1599
+
1600
+ # Prosite
1601
+ # -------
1602
+ if "prosite" in annotations:
1603
+ ax = annotations.index("prosite")
1604
+
1605
+ prosite_gene = uni_feat_gene[(uni_feat_gene["Type"] == "DOMAIN") & (uni_feat_gene["Evidence"] != "Pfam")]
1606
+ prosite_gene = prosite_gene.sort_values("Begin").reset_index(drop=True)
1607
+ prosite_color_dict = {}
1608
+
1609
+ for n, name in enumerate(prosite_gene["Description"].unique()):
1610
+ prosite_color_dict[name] = f"C{n}"
1611
+
1612
+ n = 0
1613
+ added_prosite = []
1614
+ for i, row in prosite_gene.iterrows():
1615
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
1616
+ continue
1617
+
1618
+ name = row["Description"]
1619
+ start = int(row["Begin"])
1620
+ end = int(row["End"])
1621
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=prosite_color_dict[name])
1622
+ if name not in added_prosite:
1623
+ if near_prosite:
1624
+ n += 1
1625
+ if n == 1:
1626
+ y = 0.28
1627
+ elif n == 2:
1628
+ y = 0
1629
+ elif n == 3:
1630
+ y = -0.295
1631
+ n = 0
1632
+ else:
1633
+ y = -0.04
1634
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
1635
+ added_prosite.append(name)
1636
+ axes[ax].set_yticks([])
1637
+ axes[ax].set_yticklabels([], fontsize=12)
1638
+ axes[ax].set_ylabel('Prosite ', fontsize=13.5, rotation=0, va='center')
1639
+ axes[ax].set_ylim(-0.5, 0.5)
1640
+ axes[ax].yaxis.set_label_coords(-0.051, 0.5)
1641
+
1642
+
1643
+ # Membrane
1644
+ # --------
1645
+ if "membrane" in annotations:
1646
+ ax = annotations.index("membrane")
1647
+
1648
+ membrane_gene = uni_feat_gene[(uni_feat_gene["Type"] == "MEMBRANE")]
1649
+ membrane_gene = membrane_gene.sort_values("Begin").reset_index(drop=True)
1650
+ membrane_color_dict = {}
1651
+
1652
+ for n, name in enumerate(membrane_gene["Description"].unique()):
1653
+ membrane_color_dict[name] = f"C{n}"
1654
+
1655
+ n = 0
1656
+ added_membrane = []
1657
+ for i, row in membrane_gene.iterrows():
1658
+ if pd.Series([row["Description"], row["Begin"], row["End"]]).isnull().any():
1659
+ continue
1660
+
1661
+ name = row["Description"]
1662
+ start = int(row["Begin"])
1663
+ end = int(row["End"])
1664
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=membrane_color_dict[name])
1665
+ if name not in added_membrane:
1666
+ y = -0.04
1667
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
1668
+ added_membrane.append(name)
1669
+ axes[ax].set_yticks([])
1670
+ axes[ax].set_yticklabels([], fontsize=12)
1671
+ axes[ax].set_ylabel('Membrane ', fontsize=13.5, rotation=0, va='center')
1672
+ axes[ax].set_ylim(-0.5, 0.5)
1673
+ axes[ax].yaxis.set_label_coords(-0.051, 0.5)
1674
+
1675
+
1676
+ # Motifs
1677
+ # ------
1678
+ if "motif" in annotations:
1679
+ ax = annotations.index("motif")
1680
+
1681
+ motif_gene = uni_feat_gene[(uni_feat_gene["Type"] == "MOTIF")]
1682
+ motif_gene = motif_gene.sort_values("Begin").reset_index(drop=True)
1683
+ motif_color_dict = {}
1684
+
1685
+ for n, name in enumerate(motif_gene["Full_description"].unique()):
1686
+ motif_color_dict[name] = f"C{n}"
1687
+
1688
+ n = 0
1689
+ added_motif = []
1690
+ for i, row in motif_gene.iterrows():
1691
+ if pd.Series([row["Full_description"], row["Begin"], row["End"]]).isnull().any():
1692
+ continue
1693
+
1694
+ name = row["Full_description"]
1695
+ start = int(row["Begin"])
1696
+ end = int(row["End"])
1697
+ axes[ax].fill_between(range(start, end+1), -0.45, 0.45, alpha=0.5, color=motif_color_dict[name])
1698
+ if name not in added_motif:
1699
+ if near_motif:
1700
+ n += 1
1701
+ if n == 1:
1702
+ y = 0.28
1703
+ elif n == 2:
1704
+ y = 0
1705
+ elif n == 3:
1706
+ y = -0.295
1707
+ n = 0
1708
+ else:
1709
+ y = -0.04
1710
+ axes[ax].text(((start + end) / 2)+0.5, y, name, ha='center', va='center', fontsize=10, color="black")
1711
+ added_motif.append(name)
1712
+ axes[ax].set_yticks([])
1713
+ axes[ax].set_yticklabels([], fontsize=12)
1714
+ axes[ax].set_ylabel('Motif ', fontsize=13.5, rotation=0, va='center')
1715
+ axes[ax].set_ylim(-0.5, 0.5)
1716
+ axes[ax].yaxis.set_label_coords(-0.051, 0.5)
1717
+
1718
+
1719
+ # Save
1720
+ # ====
1721
+
1722
+ fig.suptitle(f'{cohort_1} (A) - {cohort_2} (B)\n\n{gene} ({uni_id})', fontsize=16)
1723
+ if save_plot:
1724
+ filename = f"{cohort_1}.{cohort_2}.comp_plot_{j+1}.{gene}_{uni_id}.png"
1725
+ output_path = os.path.join(output_dir, filename)
1726
+ plt.subplots_adjust(top=0.9)
1727
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
1728
+ logger.debug(f"Saved {output_path}")
1729
+ if show_plot:
1730
+ plt.show()
1731
+ plt.close()
1732
+
1733
+ else:
1734
+ logger.warning("Nothing to plot!")
1735
+
1736
+
1737
+
1738
+ # Associations plots
1739
+ # ==================
1740
+
1741
+ def expand_uniprot_feat_rows(df):
1742
+ """
1743
+ Convert Uniprot features df from ranges of positions to individual positions.
1744
+ """
1745
+
1746
+ expanded_list = []
1747
+ for _, row in df.iterrows():
1748
+ begin, end = int(row['Begin']), int(row['End'])
1749
+ expanded_data = [{'Uniprot_ID': row['Uniprot_ID'], 'Type': row['Type'], 'Pos': pos, 'Description': row['Description']}
1750
+ for pos in range(begin, end + 1)]
1751
+ expanded_list.extend(expanded_data)
1752
+ expanded_df = pd.DataFrame(expanded_list)
1753
+
1754
+ return expanded_df
1755
+
1756
+
1757
+ def get_dummy(df, pos, annot):
1758
+
1759
+ return int(annot in df[df["Pos"] == pos].Type.values)
1760
+
1761
+
1762
+ def get_dummies_annot(df, col):
1763
+
1764
+ pos = pd.Series(df.Pos.unique(), name="Pos")
1765
+ lst_results = [pos]
1766
+ for annot in df[col].unique():
1767
+ series = pd.Series(df.Pos.unique()).apply(lambda x: get_dummy(df, pos=x, annot=annot))
1768
+ series.name = annot.capitalize()
1769
+ lst_results.append(series)
1770
+
1771
+ return pd.concat(lst_results, axis=1)
1772
+
1773
+
1774
+ def get_uni_feat_for_odds(uni_id, uni_feat_df):
1775
+ """
1776
+ Preprocess Uniprot features df for logistic regression.
1777
+ """
1778
+
1779
+ uni_feat_df = uni_feat_df.copy()
1780
+ uni_feat_df = uni_feat_df[uni_feat_df["Uniprot_ID"] == uni_id]
1781
+ uni_feat_df = get_dummies_annot(uni_feat_df, col="Type")
1782
+ uni_feat_df.insert(0, "Uniprot_ID", uni_id)
1783
+
1784
+ return uni_feat_df
1785
+
1786
+
1787
+ def uni_log_reg(df, labels):
1788
+ """
1789
+ Univariate logistic regression analysis.
1790
+ """
1791
+
1792
+ df = df.copy()
1793
+ results = {}
1794
+
1795
+ df = df.drop(columns=[col for col in df.columns if df[col].nunique() == 1])
1796
+ columns = df.columns
1797
+
1798
+ # Keep tracks of NA in ddG before standardizing
1799
+ if "ΔΔG" in columns:
1800
+ ddg_ix = ~df["ΔΔG"].isna().values
1801
+ df = df.fillna(0)
1802
+
1803
+ scaler = StandardScaler()
1804
+ df = scaler.fit_transform(df)
1805
+
1806
+ for i, col in enumerate(columns):
1807
+
1808
+ # Drop NA only in since it is the only annotation that depends on mutations
1809
+ if col == "ΔΔG":
1810
+ X_col = df[ddg_ix, i]
1811
+ y_col = labels[ddg_ix]
1812
+ if y_col.nunique() < 2:
1813
+ results[col] = {'p_value': np.nan, 'log_odds': np.nan, 'std_err': np.nan}
1814
+ continue
1815
+ else:
1816
+ X_col = df[:, i]
1817
+ y_col = labels
1818
+
1819
+ with warnings.catch_warnings():
1820
+ warnings.simplefilter('ignore', ConvergenceWarning)
1821
+ X = sm.add_constant(X_col)
1822
+ model = sm.Logit(y_col, X)
1823
+
1824
+ try:
1825
+ result = model.fit(disp=0)
1826
+ p_value = result.pvalues[1]
1827
+ coeff = result.params[1]
1828
+ std_err = result.bse[1]
1829
+
1830
+ except np.linalg.LinAlgError as e:
1831
+ logger.debug("Logistic regression singular matrix: Skipping..")
1832
+ logger.debug(e)
1833
+ p_value = np.nan
1834
+ coeff = np.nan
1835
+ std_err = np.nan
1836
+
1837
+ except sm.tools.sm_exceptions.PerfectSeparationError as e:
1838
+ logger.debug("Logistic regression perfect separation: Skipping..")
1839
+ logger.debug(e)
1840
+ p_value = np.nan
1841
+ coeff = np.nan
1842
+ std_err = np.nan
1843
+
1844
+ results[col] = {'p_value': p_value, 'log_odds': coeff, 'std_err': std_err}
1845
+
1846
+ return pd.DataFrame(results)
1847
+
1848
+
1849
+ def uni_log_reg_all_genes(df_annotated, uni_feat_df):
1850
+ """
1851
+ Univariate logistic regression analysis of all genes.
1852
+ """
1853
+
1854
+ results_lst = []
1855
+ df_gene_lst = []
1856
+
1857
+ for uni_id in df_annotated.Uniprot_ID.unique():
1858
+
1859
+ # Process each gene individually
1860
+ uni_feat_df_gene = get_uni_feat_for_odds(uni_id=uni_id, uni_feat_df=uni_feat_df)
1861
+
1862
+ df_gene = df_annotated[df_annotated["Uniprot_ID"] == uni_id].reset_index(drop=True)
1863
+ gene = df_gene.Gene.unique()[0]
1864
+
1865
+ df_gene = df_gene.merge(uni_feat_df_gene, on=["Uniprot_ID", "Pos"], how="left")
1866
+ target_cols = df_gene.drop(columns=["Pos", "C", "Uniprot_ID", "Gene"]).columns.values
1867
+
1868
+ y_data = df_gene["C"]
1869
+ X_data = df_gene[target_cols]
1870
+ y_data = y_data.fillna(0)
1871
+
1872
+ if y_data.nunique() > 1:
1873
+ results_gene = uni_log_reg(X_data, y_data)
1874
+ results_gene["Gene"] = gene
1875
+ results_gene["Uniprot_ID"] = uni_id
1876
+
1877
+ else:
1878
+ results_gene = pd.DataFrame(np.nan, index=["p_value", "log_odds", "std_err"], columns=target_cols)
1879
+ results_gene["Gene"] = gene
1880
+ results_gene["Uniprot_ID"] = uni_id
1881
+
1882
+ df_gene_lst.append(df_gene)
1883
+ results_lst.append(results_gene)
1884
+
1885
+ uni_log_result = pd.concat(results_lst)
1886
+ df_genes = pd.concat(df_gene_lst)
1887
+
1888
+ return uni_log_result, df_genes
1889
+
1890
+
1891
+ def rename_columns(df):
1892
+ """
1893
+ Simply rename columns after dummy transformation.
1894
+ """
1895
+
1896
+ new_columns = {}
1897
+ for column in df.columns:
1898
+ for prefix in ["SSE", "TYPE"]:
1899
+ if column.startswith(f'{prefix}_'):
1900
+ new_column = column.replace(f'{prefix}_', '').capitalize()
1901
+ new_columns[column] = new_column
1902
+
1903
+ return df.rename(columns=new_columns)
1904
+
1905
+
1906
+ def volcano_plot(logreg_results,
1907
+ fsize=(10, 6),
1908
+ expand_text_xy=(3, 2),
1909
+ text_fontsize=10,
1910
+ top_n=15,
1911
+ top_by_gene=False,
1912
+ gene_text=False,
1913
+ save_plot=True,
1914
+ show_plot=False,
1915
+ output_dir=None,
1916
+ cohort="o3d_run"):
1917
+ """
1918
+ Volcano plot all genes.
1919
+ """
1920
+
1921
+ genes = logreg_results[~logreg_results.drop(columns=["Gene", "Uniprot_ID"]).isna().all(axis=1)].Gene.unique()
1922
+ with warnings.catch_warnings():
1923
+ warnings.simplefilter("ignore", category=DeprecationWarning)
1924
+ cmap = plt.cm.get_cmap('tab20', len(genes))
1925
+ lgray_rgb = 0.7803921568627451, 0.7803921568627451, 0.7803921568627451, 1.0
1926
+ used_colors = []
1927
+
1928
+ all_gene_results = []
1929
+ plt.figure(figsize=fsize)
1930
+
1931
+ for i, gene in enumerate(genes):
1932
+ gene_results = logreg_results[logreg_results["Gene"] == gene].drop(columns=["Gene", "Uniprot_ID"]).dropna(axis=1)
1933
+ gene_logodds = gene_results.loc["log_odds", :]
1934
+ gene_pvals = gene_results.loc["p_value", :]
1935
+ gene_logpvals = -np.log10(gene_pvals)
1936
+
1937
+ # Volcano plot
1938
+ significant_mask = gene_pvals < 0.01
1939
+ non_significant_mask = ~significant_mask
1940
+ marker = '*' if cmap(i) in used_colors else 'o'
1941
+ used_colors.append(cmap(i))
1942
+ plt.scatter(gene_logodds[non_significant_mask], gene_logpvals[non_significant_mask], zorder=1, color='lightgray', alpha=0.7, marker=marker)
1943
+ plt.scatter(gene_logodds[significant_mask], gene_logpvals[significant_mask], zorder=2, label=gene,
1944
+ color="black" if cmap(i) == lgray_rgb else cmap(i), alpha=0.7, marker=marker)
1945
+
1946
+ # Append results for annotation
1947
+ gene_data = pd.DataFrame({
1948
+ 'Gene': gene,
1949
+ 'Log_odds': gene_logodds.values,
1950
+ 'Pval': gene_pvals.values,
1951
+ 'Log_pval': gene_logpvals.values,
1952
+ 'Feature': gene_logodds.index})
1953
+ all_gene_results.append(gene_data)
1954
+
1955
+ # Annotated top significant n points
1956
+ all_gene_results = pd.concat(all_gene_results)
1957
+ if top_by_gene:
1958
+ top_significant_points = all_gene_results[all_gene_results["Pval"] < 0.01].groupby("Gene").apply(lambda x: x.nsmallest(top_n, 'Pval')).reset_index(drop=True)
1959
+ else:
1960
+ top_significant_points = all_gene_results[all_gene_results["Pval"] < 0.01].nsmallest(top_n, 'Pval')
1961
+
1962
+ annotations = []
1963
+ for _, row in top_significant_points.iterrows():
1964
+ if gene_text:
1965
+ text = f"{row['Gene']}-{row['Feature']}"
1966
+ else:
1967
+ text = row['Feature']
1968
+ annotations.append(plt.text(row['Log_odds'], row['Log_pval'], text, ha='center', va='center', fontsize=text_fontsize, color='black'))
1969
+ adjust_text(annotations, expand=expand_text_xy, # expand text bounding boxes by 1.2 fold in x direction and 2 fold in y direction
1970
+ arrowprops=dict(arrowstyle='->', color='gray'), lw=0.5)
1971
+
1972
+ plt.xlabel('Log odds', fontsize=12)
1973
+ plt.ylabel('-log10(p-value)', fontsize=12)
1974
+ plt.axhline(y=-np.log10(0.01), color='lightgrey', linestyle='--', zorder=0)
1975
+ plt.axvline(x=0, color='lightgrey', linestyle='--', zorder=0)
1976
+ plt.legend(ncol=1 if len(genes) < 20 else 2)
1977
+ plt.suptitle(f"{cohort} - Residues' cluster status and annotations associations", y=0.93)
1978
+
1979
+ if save_plot and output_dir:
1980
+ filename = f"{cohort}.volcano_plot.png"
1981
+ output_path = os.path.join(output_dir, filename)
1982
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
1983
+ logger.debug(f"Saved {output_path}")
1984
+ if show_plot:
1985
+ plt.show()
1986
+ plt.close()
1987
+
1988
+
1989
+ def volcano_plot_each_gene(logreg_results,
1990
+ fsize=(3.2, 3),
1991
+ expand_text_xy=(3, 2),
1992
+ text_fontsize=10,
1993
+ top_n=5,
1994
+ ncols=5,
1995
+ all_significant=True,
1996
+ save_plot=True,
1997
+ show_plot=False,
1998
+ output_dir=None,
1999
+ cohort="o3d_run"):
2000
+ """
2001
+ Volcano plot of individual genes.
2002
+ """
2003
+
2004
+ genes = logreg_results[~logreg_results.drop(columns=["Gene", "Uniprot_ID"]).isna().all(axis=1)].Gene.unique()
2005
+ num_genes = len(genes)
2006
+ with warnings.catch_warnings():
2007
+ warnings.simplefilter("ignore", category=DeprecationWarning)
2008
+ cmap = plt.cm.get_cmap('tab20', num_genes)
2009
+ lgray_rgb = 0.7803921568627451, 0.7803921568627451, 0.7803921568627451, 1.0
2010
+
2011
+ all_gene_results = []
2012
+
2013
+ # Figsize
2014
+ if num_genes <= 5:
2015
+ ncols = num_genes
2016
+ elif num_genes <= 10:
2017
+ ncols = int(np.ceil(num_genes / 2))
2018
+ elif num_genes <= 20:
2019
+ ncols = 5
2020
+ else:
2021
+ ncols = 6
2022
+ nrows = int(np.ceil(num_genes / ncols))
2023
+
2024
+ fsize_x, fsize_y = fsize
2025
+ fsize_x = fsize_x * ncols
2026
+ fsize_y = fsize_y * nrows
2027
+
2028
+ fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(fsize_x, fsize_y), constrained_layout=True)
2029
+ # Ensure that the axes is always a 1D subscriptable array
2030
+ if not isinstance(axes, Axes):
2031
+ axes = axes.flatten()
2032
+ axes = np.atleast_1d(axes)
2033
+
2034
+ for i, ax in enumerate(axes):
2035
+
2036
+ if i < len(genes):
2037
+ gene = genes[i]
2038
+ gene_results = logreg_results[logreg_results["Gene"] == gene].drop(columns=["Gene", "Uniprot_ID"]).dropna(axis=1)
2039
+ gene_logodds = gene_results.loc["log_odds", :]
2040
+ gene_pvals = gene_results.loc["p_value", :]
2041
+ gene_logpvals = -np.log10(gene_pvals)
2042
+
2043
+ # Volcano plot
2044
+ significant_mask = gene_pvals < 0.01
2045
+ non_significant_mask = ~significant_mask
2046
+ ax.scatter(gene_logodds[non_significant_mask], gene_logpvals[non_significant_mask], zorder=1, color='lightgray', alpha=0.7)
2047
+ ax.scatter(gene_logodds[significant_mask], gene_logpvals[significant_mask], zorder=2,
2048
+ color="black" if cmap(i) == lgray_rgb else cmap(i), alpha=0.7)
2049
+
2050
+ # Annotated top significant n points
2051
+ gene_data = pd.DataFrame({
2052
+ 'Gene': gene,
2053
+ 'Log_odds': gene_logodds.values,
2054
+ 'Pval': gene_pvals.values,
2055
+ 'Log_pval': gene_logpvals.values,
2056
+ 'Feature': gene_logodds.index})
2057
+ all_gene_results.append(gene_data)
2058
+
2059
+ if all_significant:
2060
+ top_significant_points = gene_data[gene_data["Pval"] < 0.01]
2061
+ else:
2062
+ top_significant_points = gene_data.nsmallest(top_n, 'Pval')
2063
+ annotations = []
2064
+ for _, row in top_significant_points.iterrows():
2065
+ annotations.append(ax.text(row['Log_odds'], row['Log_pval'], row['Feature'], ha='center', va='center', fontsize=text_fontsize, color='black'))
2066
+ adjust_text(annotations, expand=expand_text_xy,
2067
+ arrowprops=dict(arrowstyle='->', color='gray'), lw=0.5, ax=ax)
2068
+
2069
+ ax.set_xlabel(None)
2070
+ ax.set_ylabel(None)
2071
+ ax.set_title(gene)
2072
+ ax.axhline(y=-np.log10(0.01), color='lightgrey', linestyle='--', zorder=0)
2073
+ ax.axvline(x=0, color='lightgrey', linestyle='--', zorder=0)
2074
+ else:
2075
+ ax.remove()
2076
+
2077
+ fig.supxlabel('Log odds')
2078
+ fig.supylabel('-log10(p-value)')
2079
+ plt.suptitle(f"{cohort} - Residues' cluster status and annotations associations")
2080
+
2081
+ if save_plot and output_dir:
2082
+ filename = f"{cohort}.volcano_plot_gene.png"
2083
+ output_path = os.path.join(output_dir, filename)
2084
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
2085
+ logger.debug(f"Saved {output_path}")
2086
+ if show_plot:
2087
+ plt.show()
2088
+ plt.close()
2089
+
2090
+
2091
+ def log_odds_plot(logreg_results,
2092
+ fsize=(1.7,3.6),
2093
+ save_plot=True,
2094
+ show_plot=False,
2095
+ output_dir=None,
2096
+ cohort="o3d_run"):
2097
+ """
2098
+ Log odds plot.
2099
+ """
2100
+
2101
+ genes = logreg_results[~logreg_results.drop(columns=["Gene", "Uniprot_ID"]).isna().all(axis=1)].Gene.unique()
2102
+ num_genes = len(genes)
2103
+ with warnings.catch_warnings():
2104
+ warnings.simplefilter("ignore", category=DeprecationWarning)
2105
+ cmap = plt.cm.get_cmap('tab20', num_genes)
2106
+ lgray_rgb = 0.7803921568627451, 0.7803921568627451, 0.7803921568627451, 1.0
2107
+
2108
+ # Figsize
2109
+ if num_genes > 10:
2110
+ nrows = 2
2111
+ else:
2112
+ nrows = 1
2113
+
2114
+ ncols = int(np.ceil(num_genes / nrows))
2115
+ fsize_x, fsize_y = fsize
2116
+ fsize_x = fsize_x * ncols
2117
+ fsize_y = fsize_y * nrows
2118
+
2119
+ fig, axes = plt.subplots(nrows, ncols,
2120
+ figsize=(fsize_x, fsize_y),
2121
+ sharey=True,
2122
+ gridspec_kw={'hspace': 0.1*nrows})
2123
+
2124
+ # Ensure that the axes is always a 1D subscriptable array
2125
+ if not isinstance(axes, Axes):
2126
+ axes = axes.flatten()
2127
+ axes = np.atleast_1d(axes)
2128
+
2129
+ for i, ax in enumerate(axes):
2130
+
2131
+ if i < len(genes):
2132
+ gene = genes[i]
2133
+ gene_results = logreg_results[logreg_results["Gene"] == gene].drop(columns=["Gene", "Uniprot_ID"])
2134
+ gene_logodds = gene_results.loc["log_odds", :]
2135
+ gene_pvals = gene_results.loc["p_value", :]
2136
+ gene_stderr = gene_results.loc["std_err", :]
2137
+
2138
+ # Get 95% confidence interval
2139
+ z = 1.96
2140
+ lower_ci = np.array(gene_logodds) - z * np.array(gene_stderr)
2141
+ upper_ci = np.array(gene_logodds) + z * np.array(gene_stderr)
2142
+
2143
+ # Calculate error bars
2144
+ lower_error = np.array(gene_logodds) - lower_ci
2145
+ upper_error = upper_ci - np.array(gene_logodds)
2146
+ error = [lower_error, upper_error]
2147
+
2148
+ # Plot
2149
+ significant_mask = gene_pvals < 0.01
2150
+ non_significant_mask = ~significant_mask
2151
+
2152
+ ax.errorbar(gene_logodds, gene_logodds.index.values, yerr=None, xerr=error, fmt='o', capsize=5, capthick=1, markersize=5)
2153
+ ax.errorbar(gene_logodds[non_significant_mask], gene_logodds.index.values[non_significant_mask], yerr=None,
2154
+ xerr=[err[non_significant_mask] for err in error], fmt='o', capsize=5, capthick=1, markersize=5, color='lightgray')
2155
+ ax.errorbar(gene_logodds[significant_mask], gene_logodds.index.values[significant_mask], yerr=None,
2156
+ xerr=[err[significant_mask] for err in error], fmt='o', capsize=5, capthick=1, markersize=5,
2157
+ color="black" if cmap(i) == lgray_rgb else cmap(i))
2158
+ ax.axvline(x=0, color='lightgrey', linestyle='--', zorder=0, lw=1)
2159
+ ax.set_xlim(gene_logodds.min()-1.5, gene_logodds.max()+1.5)
2160
+ ax.set_xlabel(f"\n\n{gene}", fontsize=12, rotation=0, va='center')
2161
+ ax.xaxis.set_label_coords(0.5, 1.11)
2162
+ else:
2163
+ ax.remove()
2164
+
2165
+ plt.suptitle(f"{cohort} - Residues' cluster status and annotations associations", y=1)
2166
+ if nrows == 1:
2167
+ fig.supxlabel('Log odds', y=-0.015)
2168
+ plt.subplots_adjust(top=0.868)
2169
+ else:
2170
+ fig.supxlabel('Log odds', y=0.037)
2171
+ plt.subplots_adjust(top=0.925)
2172
+
2173
+
2174
+ if save_plot and output_dir:
2175
+ filename = f"{cohort}.logodds_plot.png"
2176
+ output_path = os.path.join(output_dir, filename)
2177
+ plt.savefig(output_path, dpi=300, bbox_inches='tight')
2178
+ logger.debug(f"Saved {output_path}")
2179
+ if show_plot:
2180
+ plt.show()
2181
+ plt.close()
2182
+
2183
+
2184
+ def associations_plots(df_annotated,
2185
+ uni_feat_processed,
2186
+ output_dir,
2187
+ plot_pars,
2188
+ miss_prob_dict,
2189
+ cohort="o3d_run"):
2190
+ """
2191
+ Generate volcano plots and log odds plot to look for associations
2192
+ between cluster status and annotated features.
2193
+ """
2194
+
2195
+ # Prepare data
2196
+ df_annotated = df_annotated.copy()
2197
+ df_annotated["Miss_prob"] = df_annotated.apply(lambda x: (miss_prob_dict[f"{x.Uniprot_ID}-F{x.F}"][x.Pos-1]), axis=1)
2198
+ df_annotated = df_annotated[df_annotated["Miss_prob"] > 0]
2199
+ cols_drop = "Mut_in_res", "Mut_in_vol", "Score_obs_sim", "C_ext", "pval", "Clump", "Res", "F", "Ens_Gene_ID", "Ens_Transcr_ID", "PAE_vol", "Miss_prob"
2200
+ df_annotated = df_annotated.drop(columns=[col for col in cols_drop if col in df_annotated.columns])
2201
+ sse_dummies = pd.get_dummies(df_annotated['SSE'], prefix='SSE')
2202
+ df_annotated = pd.concat((df_annotated.drop(columns="SSE"), sse_dummies), axis=1)
2203
+ df_annotated = df_annotated.rename(columns={"pLDDT_res" : "pLDDT", "PAE_vol" : "PAE", "DDG" : "ΔΔG"})
2204
+
2205
+ # Add Uniprot features
2206
+ uni_feat_processed = uni_feat_processed[uni_feat_processed["Type"] != "REGION"].reset_index(drop=True)
2207
+ uni_feat_processed_expanded = expand_uniprot_feat_rows(uni_feat_processed)
2208
+
2209
+ # Perform univariate log reg
2210
+ logreg_results, df_annotated_uni_feat = uni_log_reg_all_genes(df_annotated, uni_feat_processed_expanded)
2211
+ logreg_results = rename_columns(logreg_results)
2212
+
2213
+ # Plots
2214
+ genes = logreg_results[~logreg_results.drop(columns=["Gene", "Uniprot_ID"]).isna().all(axis=1)].Gene.unique()
2215
+ if len(genes) > 0:
2216
+ output_dir_associations_plots = os.path.join(output_dir, f"{cohort}.associations_plots")
2217
+ logger.info(f"Generating associations plots in {output_dir_associations_plots}")
2218
+ os.makedirs(output_dir_associations_plots, exist_ok=True)
2219
+ log_odds_plot(logreg_results,
2220
+ output_dir=output_dir_associations_plots,
2221
+ cohort=cohort,
2222
+ fsize=(plot_pars["log_odds_fsize_x"], plot_pars["log_odds_fsize_y"]))
2223
+ volcano_plot(logreg_results,
2224
+ top_n=plot_pars["volcano_top_n"],
2225
+ output_dir=output_dir_associations_plots,
2226
+ cohort=cohort,
2227
+ fsize=(plot_pars["volcano_fsize_x"], plot_pars["volcano_fsize_y"]))
2228
+ volcano_plot_each_gene(logreg_results,
2229
+ output_dir=output_dir_associations_plots,
2230
+ cohort=cohort,
2231
+ fsize=(plot_pars["volcano_subplots_fsize_x"], plot_pars["volcano_subplots_fsize_y"]))
2232
+ else:
2233
+ logger.debug("There aren't any relationship to plot: Skipping associations plots..")
2234
+
2235
+ uni_feat_cols = ["Uniprot_ID", "Pos", "Domain", "Ptm", "Membrane", "Motif", "Site"]
2236
+ df_annotated_uni_feat = df_annotated_uni_feat[[col for col in uni_feat_cols if col in df_annotated_uni_feat.columns]]
2237
+
2238
+ return df_annotated_uni_feat
2239
+
2240
+
2241
+ # PLOT WRAPPER
2242
+ # ============
2243
+
2244
+ def generate_plots(gene_result_path,
2245
+ pos_result_path,
2246
+ maf_path,
2247
+ miss_prob_path,
2248
+ seq_df_path,
2249
+ cohort,
2250
+ datasets_dir,
2251
+ annotations_dir,
2252
+ output_dir,
2253
+ plot_pars,
2254
+ maf_path_for_nonmiss=None,
2255
+ c_genes_only=True,
2256
+ n_genes=30,
2257
+ lst_genes=None,
2258
+ save_plot=True,
2259
+ show_plot=False,
2260
+ save_csv=True,
2261
+ include_all_pos=False,
2262
+ c_ext=True,
2263
+ title=None,
2264
+ plot_associations=True):
2265
+
2266
+ # Load data tracks
2267
+ # ================
2268
+
2269
+ # Load data
2270
+ logger.debug("Loading data")
2271
+
2272
+ gene_result = pd.read_csv(gene_result_path)
2273
+ pos_result = pd.read_csv(pos_result_path)
2274
+ maf = pd.read_csv(maf_path, sep="\t")
2275
+ miss_prob_dict = json.load(open(miss_prob_path))
2276
+ seq_df = pd.read_csv(seq_df_path, sep="\t")
2277
+ uniprot_feat = pd.read_csv(os.path.join(annotations_dir, "uniprot_feat.tsv"), sep="\t")
2278
+ pdb_tool = pd.read_csv(os.path.join(annotations_dir, "pdb_tool_df.tsv"), sep="\t")
2279
+ disorder = pd.read_csv(os.path.join(datasets_dir, "confidence.tsv"), sep="\t", low_memory=False)
2280
+
2281
+ # Clean up MOTIF description TO DO: it should be moved in the build-annotations step
2282
+ uniprot_feat.loc[(uniprot_feat["Type"] == "MOTIF") & (
2283
+ uniprot_feat["Description"] == "Zinc finger"), "Full_description"] = "Zinc finger"
2284
+ uniprot_feat.loc[(uniprot_feat["Type"] == "MOTIF") & (uniprot_feat["Full_description"].str.contains('WIN', case=False)), "Full_description"] = "WIN"
2285
+ uniprot_feat.loc[uniprot_feat["Type"] == "MOTIF", "Full_description"] = uniprot_feat.loc[uniprot_feat["Type"] == "MOTIF", "Full_description"].apply(
2286
+ lambda x: x.split(";")[0] if len(x.split(";")) > 1 else x)
2287
+
2288
+ # Filter Oncodrive3D result
2289
+ logger.debug("Filtering result")
2290
+ maf = filter_non_processed_mut(maf, pos_result)
2291
+ gene_result, pos_result, genes, uni_ids = filter_o3d_result(gene_result,
2292
+ pos_result,
2293
+ n_genes,
2294
+ lst_genes)
2295
+
2296
+ if len(gene_result) > 0:
2297
+
2298
+ # Subset dfs by selected genes and IDs
2299
+ logger.debug("Subset genes")
2300
+ seq_df, disorder, pdb_tool, uniprot_feat = subset_genes_and_ids(genes,
2301
+ uni_ids,
2302
+ seq_df,
2303
+ disorder,
2304
+ pdb_tool,
2305
+ uniprot_feat)
2306
+
2307
+ # Summary plot
2308
+ os.makedirs(output_dir, exist_ok=True)
2309
+ logger.info(f"Generating summary plot in {output_dir}")
2310
+ count_mut_gene_df, count_pos_df, cluster_df = get_summary_counts(gene_result, pos_result, seq_df)
2311
+ summary_plot(gene_result,
2312
+ pos_result,
2313
+ count_mut_gene_df,
2314
+ count_pos_df,
2315
+ cluster_df,
2316
+ output_dir,
2317
+ cohort,
2318
+ plot_pars,
2319
+ save_plot=save_plot,
2320
+ show_plot=show_plot,
2321
+ title=title)
2322
+
2323
+ # Individual gene plots
2324
+ if "nonmiss_count" in plot_pars["h_ratios"]:
2325
+ maf_nonmiss = get_nonmiss_mut(maf_path_for_nonmiss)
2326
+ else:
2327
+ maf_nonmiss = None
2328
+ output_dir_genes_plots = os.path.join(output_dir, f"{cohort}.genes_plots")
2329
+ os.makedirs(output_dir_genes_plots, exist_ok=True)
2330
+ logger.info(f"Generating genes plots in {output_dir_genes_plots}")
2331
+
2332
+ if c_genes_only:
2333
+ n_genes = len(gene_result[gene_result["C_gene"] == 1])
2334
+ gene_result, pos_result, genes, uni_ids = filter_o3d_result(gene_result,
2335
+ pos_result,
2336
+ n_genes,
2337
+ lst_genes)
2338
+
2339
+ if c_genes_only == False or (c_genes_only and n_genes > 1):
2340
+
2341
+ pos_result_annotated, uni_feat_processed = genes_plots(gene_result,
2342
+ pos_result,
2343
+ seq_df,
2344
+ maf,
2345
+ maf_nonmiss,
2346
+ miss_prob_dict,
2347
+ output_dir_genes_plots,
2348
+ cohort,
2349
+ annotations_dir,
2350
+ disorder,
2351
+ uniprot_feat,
2352
+ pdb_tool,
2353
+ plot_pars,
2354
+ save_plot=save_plot,
2355
+ show_plot=show_plot,
2356
+ c_ext=c_ext,
2357
+ title=title)
2358
+
2359
+ # Associations plots
2360
+ if plot_associations and len(pos_result_annotated) > 0:
2361
+ pos_result_annotated_uni_feat = associations_plots(pos_result_annotated,
2362
+ uni_feat_processed,
2363
+ output_dir,
2364
+ plot_pars,
2365
+ miss_prob_dict,
2366
+ cohort)
2367
+ pos_result_annotated = pos_result_annotated.merge(
2368
+ pos_result_annotated_uni_feat.fillna(0), how="left", on=["Uniprot_ID", "Pos"])
2369
+
2370
+
2371
+ # Save annotations
2372
+ if save_csv and pos_result_annotated is not None:
2373
+ logger.info(f"Saving annotated Oncodrive3D result in {output_dir}")
2374
+ save_annotated_result(pos_result,
2375
+ pos_result_annotated,
2376
+ uni_feat_processed,
2377
+ output_dir,
2378
+ cohort,
2379
+ include_all_pos)
2380
+
2381
+ logger.info("Plotting completed!")
2382
+ else:
2383
+ logger.warning("There aren't any significant genes to plot!")
2384
+ else:
2385
+ logger.warning("There aren't any genes to plot!")
2386
+
2387
+
2388
+ def generate_comparative_plots(o3d_result_dir_1,
2389
+ cohort_1,
2390
+ o3d_result_dir_2,
2391
+ cohort_2,
2392
+ datasets_dir,
2393
+ annotations_dir,
2394
+ output_dir,
2395
+ plot_pars,
2396
+ maf_path_nonmiss_1=None,
2397
+ maf_path_nonmiss_2=None,
2398
+ n_genes=30,
2399
+ lst_genes=None):
2400
+
2401
+ # Load data tracks
2402
+ # ================
2403
+
2404
+ # Load data
2405
+ logger.debug("Loading data")
2406
+ gene_result_1, pos_result_1, maf_1, miss_prob_dict_1 = load_o3d_result(o3d_result_dir_1, cohort_1)
2407
+ gene_result_2, pos_result_2, maf_2, miss_prob_dict_2 = load_o3d_result(o3d_result_dir_2, cohort_2)
2408
+
2409
+ seq_df_path = os.path.join(datasets_dir, "seq_for_mut_prob.tsv")
2410
+ seq_df = pd.read_csv(seq_df_path, sep="\t")
2411
+ uniprot_feat = pd.read_csv(os.path.join(annotations_dir, "uniprot_feat.tsv"), sep="\t")
2412
+ pdb_tool = pd.read_csv(os.path.join(annotations_dir, "pdb_tool_df.tsv"), sep="\t")
2413
+ disorder = pd.read_csv(os.path.join(datasets_dir, "confidence.tsv"), sep="\t", low_memory=False)
2414
+
2415
+ # Clean up MOTIF description TO DO: it should be moved in the build-annotations step
2416
+ uniprot_feat.loc[(uniprot_feat["Type"] == "MOTIF") & (
2417
+ uniprot_feat["Description"] == "Zinc finger"), "Full_description"] = "Zinc finger"
2418
+ uniprot_feat.loc[(uniprot_feat["Type"] == "MOTIF") & (uniprot_feat["Full_description"].str.contains('WIN', case=False)), "Full_description"] = "WIN"
2419
+ uniprot_feat.loc[uniprot_feat["Type"] == "MOTIF", "Full_description"] = uniprot_feat.loc[uniprot_feat["Type"] == "MOTIF", "Full_description"].apply(
2420
+ lambda x: x.split(";")[0] if len(x.split(";")) > 1 else x)
2421
+
2422
+ # Filter Oncodrive3D result
2423
+ logger.debug("Filtering result")
2424
+ maf_1 = filter_non_processed_mut(maf_1, pos_result_1)
2425
+ maf_2 = filter_non_processed_mut(maf_2, pos_result_2)
2426
+ gene_result_1, pos_result_1, genes_1, uni_ids_1 = filter_o3d_result(gene_result_1,
2427
+ pos_result_1,
2428
+ n_genes,
2429
+ lst_genes)
2430
+ gene_result_2, pos_result_2, genes_2, uni_ids_2 = filter_o3d_result(gene_result_2,
2431
+ pos_result_2,
2432
+ n_genes,
2433
+ lst_genes)
2434
+
2435
+ # Get shared genes and Uniprot IDs
2436
+ shared_genes = [gene for gene in genes_1 if gene in genes_2]
2437
+ shared_uni_ids = [uid for uid in uni_ids_1 if uid in uni_ids_2]
2438
+
2439
+ if len(shared_genes) > 0:
2440
+
2441
+ # Subset dfs by selected genes and IDs
2442
+ logger.debug("Subset genes")
2443
+ seq_df, disorder, pdb_tool, uniprot_feat = subset_genes_and_ids(shared_genes,
2444
+ shared_uni_ids,
2445
+ seq_df,
2446
+ disorder,
2447
+ pdb_tool,
2448
+ uniprot_feat)
2449
+
2450
+ # Comparative plots
2451
+ if "nonmiss_count" in plot_pars["h_ratios"]:
2452
+ maf_nonmiss_1 = get_nonmiss_mut(maf_path_nonmiss_1)
2453
+ maf_nonmiss_2 = get_nonmiss_mut(maf_path_nonmiss_2)
2454
+ else:
2455
+ maf_nonmiss_1 = None
2456
+ maf_nonmiss_2 = None
2457
+ output_dir = os.path.join(output_dir, f"{cohort_1}.{cohort_2}.comparative_plots")
2458
+ os.makedirs(output_dir, exist_ok=True)
2459
+
2460
+ logger.info(f"Generating comparative plots in {output_dir}")
2461
+ comparative_plots(shared_genes,
2462
+ pos_result_1,
2463
+ maf_1,
2464
+ maf_nonmiss_1,
2465
+ miss_prob_dict_1,
2466
+ cohort_1,
2467
+ pos_result_2,
2468
+ maf_2,
2469
+ maf_nonmiss_2,
2470
+ miss_prob_dict_2,
2471
+ cohort_2,
2472
+ seq_df,
2473
+ output_dir,
2474
+ annotations_dir,
2475
+ disorder,
2476
+ uniprot_feat,
2477
+ pdb_tool,
2478
+ plot_pars,
2479
+ save_plot=True,
2480
+ show_plot=False)
2481
+ logger.info("Plotting completed!")
2482
+
2483
+ else:
2484
+ logger.warning("There aren't any genes to plot!")