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.
- oncodrive3d-1.0.4.dist-info/METADATA +333 -0
- oncodrive3d-1.0.4.dist-info/RECORD +36 -0
- oncodrive3d-1.0.4.dist-info/WHEEL +4 -0
- oncodrive3d-1.0.4.dist-info/entry_points.txt +5 -0
- oncodrive3d-1.0.4.dist-info/licenses/LICENSE +15 -0
- scripts/__init__.py +2 -0
- scripts/clustering_3d.code-workspace +7 -0
- scripts/datasets/__init__.py +0 -0
- scripts/datasets/af_merge.py +344 -0
- scripts/datasets/build_datasets.py +125 -0
- scripts/datasets/get_pae.py +78 -0
- scripts/datasets/get_structures.py +107 -0
- scripts/datasets/model_confidence.py +97 -0
- scripts/datasets/parse_pae.py +64 -0
- scripts/datasets/prob_contact_maps.py +258 -0
- scripts/datasets/seq_for_mut_prob.py +900 -0
- scripts/datasets/utils.py +394 -0
- scripts/globals.py +169 -0
- scripts/main.py +650 -0
- scripts/plotting/__init__.py +0 -0
- scripts/plotting/build_annotations.py +102 -0
- scripts/plotting/chimerax_plot.py +251 -0
- scripts/plotting/pdb_tool.py +149 -0
- scripts/plotting/pfam.py +94 -0
- scripts/plotting/plot.py +2484 -0
- scripts/plotting/stability_change.py +184 -0
- scripts/plotting/uniprot_feat.py +276 -0
- scripts/plotting/utils.py +594 -0
- scripts/run/__init__.py +0 -0
- scripts/run/clustering.py +749 -0
- scripts/run/communities.py +53 -0
- scripts/run/miss_mut_prob.py +206 -0
- scripts/run/mutability.py +289 -0
- scripts/run/pvalues.py +91 -0
- scripts/run/score_and_simulations.py +155 -0
- scripts/run/utils.py +461 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains functions to perform the 3D clustering of missense mutations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import multiprocessing
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import daiquiri
|
|
9
|
+
import networkx.algorithms.community as nx_comm
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from scripts import __logger_name__
|
|
14
|
+
|
|
15
|
+
from scripts.run.communities import get_community_index_nx, get_network
|
|
16
|
+
from scripts.run.pvalues import get_final_gene_result
|
|
17
|
+
from scripts.run.miss_mut_prob import get_miss_mut_prob_dict, mut_rate_vec_to_dict, get_unif_gene_miss_prob
|
|
18
|
+
from scripts.run.score_and_simulations import get_anomaly_score, get_sim_anomaly_score, recompute_inf_score
|
|
19
|
+
from scripts.run.utils import add_info, get_gene_entry, add_nan_clust_cols, parse_maf_input, sort_cols, empty_result_pos
|
|
20
|
+
from scripts.run.mutability import init_mutabilities_module
|
|
21
|
+
|
|
22
|
+
logger = daiquiri.getLogger(__logger_name__ + ".run.clustering")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def process_mapping_issue(issue_ix,
|
|
26
|
+
mut_gene_df,
|
|
27
|
+
result_gene_df,
|
|
28
|
+
gene, uniprot_id,
|
|
29
|
+
af_f,
|
|
30
|
+
transcript_status,
|
|
31
|
+
thr,
|
|
32
|
+
issue_type):
|
|
33
|
+
"""
|
|
34
|
+
Check if there are mutations not in the structure (mut pos exceed lenght of
|
|
35
|
+
the structure protein sequence) or mutations with mismatches between WT AA
|
|
36
|
+
between mut and structure protein sequence. If the ratio of mutations do
|
|
37
|
+
not exceed threshold, filter out the specific mutations, else filter out
|
|
38
|
+
all mutations of that gene.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if issue_type == "Mut_not_in_structure":
|
|
42
|
+
logger_txt="mut not in the structure"
|
|
43
|
+
df_col = "Ratio_not_in_structure"
|
|
44
|
+
elif issue_type == "WT_mismatch":
|
|
45
|
+
logger_txt="mut with ref-structure WT AA mismatch"
|
|
46
|
+
df_col = "Ratio_WT_mismatch"
|
|
47
|
+
else:
|
|
48
|
+
logger.warning(f"'{issue_type}' is not a valid issue type, please select 'Mut_not_in_structure' or 'Ratio_not_in_structure': Skipping processing mapping issue..")
|
|
49
|
+
filter_gene = False
|
|
50
|
+
|
|
51
|
+
return filter_gene, result_gene_df, mut_gene_df
|
|
52
|
+
|
|
53
|
+
ratio_issue = sum(issue_ix) / len(mut_gene_df)
|
|
54
|
+
logger_out = f"Detected {sum(issue_ix)} ({ratio_issue*100:.1f}%) {logger_txt} of {gene} ({uniprot_id}-F{af_f}, transcript status = {transcript_status}): "
|
|
55
|
+
result_gene_df[df_col] = np.round(ratio_issue, 3)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Do not filter neither gene neither mut
|
|
59
|
+
if issue_type == "WT_mismatch" and thr == 1:
|
|
60
|
+
logger.warning(logger_out + "Filtering of mismatching mutations disabled ('thr_mapping_issue' = 1)..")
|
|
61
|
+
mut_gene_df.loc[issue_ix, "WT_mismatch"] = 1
|
|
62
|
+
filter_gene = False
|
|
63
|
+
return filter_gene, result_gene_df, mut_gene_df
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
# Filter gene
|
|
67
|
+
if ratio_issue >= thr:
|
|
68
|
+
result_gene_df["Status"] = issue_type
|
|
69
|
+
|
|
70
|
+
if transcript_status == "Match":
|
|
71
|
+
logger.warning(logger_out + "Filtering the gene")
|
|
72
|
+
else:
|
|
73
|
+
logger.debug(logger_out + "Filtering the gene..")
|
|
74
|
+
filter_gene = True
|
|
75
|
+
return filter_gene, result_gene_df, None
|
|
76
|
+
|
|
77
|
+
# Filter mut
|
|
78
|
+
else:
|
|
79
|
+
if transcript_status == "Match":
|
|
80
|
+
logger.warning(logger_out + "Filtering the mutations..")
|
|
81
|
+
else:
|
|
82
|
+
logger.debug(logger_out + "Filtering the mutations..")
|
|
83
|
+
mut_gene_df = mut_gene_df[~issue_ix]
|
|
84
|
+
filter_gene = False
|
|
85
|
+
return filter_gene, result_gene_df, mut_gene_df
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def clustering_3d(gene,
|
|
90
|
+
uniprot_id,
|
|
91
|
+
mut_gene_df,
|
|
92
|
+
cmap_path,
|
|
93
|
+
miss_prob_dict,
|
|
94
|
+
seq_gene,
|
|
95
|
+
af_f,
|
|
96
|
+
alpha=0.01,
|
|
97
|
+
num_iteration=10000,
|
|
98
|
+
cmap_prob_thr=0.5,
|
|
99
|
+
seed=None,
|
|
100
|
+
pae_path=None,
|
|
101
|
+
thr_mapping_issue=0.1,
|
|
102
|
+
sample_info=False):
|
|
103
|
+
"""
|
|
104
|
+
Compute local density of missense mutations for a sphere of 10A around
|
|
105
|
+
each amino acid position of the selected gene product. It performed a
|
|
106
|
+
rank-based comparison between observed density and simulated ones in
|
|
107
|
+
absense of positive selection (cohort mut profile). Get an experimental
|
|
108
|
+
per-residue p-val for the local enrichment and a global p-val for the gene,
|
|
109
|
+
which correspond to the minimum p-val across its positions.
|
|
110
|
+
|
|
111
|
+
Parameters: ## CHANGE/UPDATE
|
|
112
|
+
-----------
|
|
113
|
+
gene : str
|
|
114
|
+
mut_gene_df : pandas dataframe
|
|
115
|
+
It must include the mutated positions of the gene.
|
|
116
|
+
|
|
117
|
+
gene_to_uniprot_dict : dict
|
|
118
|
+
Uniprot_ID as key and corresponding genes as values
|
|
119
|
+
|
|
120
|
+
neighbours_df : pandas df
|
|
121
|
+
miss_prob_dict : dict
|
|
122
|
+
Uniprot_ID as keys and per residue prob of missense mut as values
|
|
123
|
+
|
|
124
|
+
af_structures_path : str
|
|
125
|
+
num_iteration : int
|
|
126
|
+
v : bolean
|
|
127
|
+
plot_contact_map : bolean
|
|
128
|
+
|
|
129
|
+
Returns: ## CHANGE/UPDATE
|
|
130
|
+
----------
|
|
131
|
+
evaluation_df : pandas df (per-position evaluation)
|
|
132
|
+
test_result : pandas df (per-gene summary evaluation)
|
|
133
|
+
status_df : pandas df (per-gene processing status)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
## Initialize
|
|
137
|
+
|
|
138
|
+
mut_count = len(mut_gene_df)
|
|
139
|
+
transcript_id_input = mut_gene_df.Transcript_ID.iloc[0]
|
|
140
|
+
transcript_id_o3d = mut_gene_df.O3D_transcript_ID.iloc[0]
|
|
141
|
+
transcript_status = mut_gene_df.Transcript_status.iloc[0]
|
|
142
|
+
result_gene_df = pd.DataFrame({"Gene" : gene,
|
|
143
|
+
"Uniprot_ID" : uniprot_id,
|
|
144
|
+
"F" : af_f,
|
|
145
|
+
"Mut_in_gene" : mut_count,
|
|
146
|
+
"Ratio_not_in_structure" : 0,
|
|
147
|
+
"Ratio_WT_mismatch" : 0,
|
|
148
|
+
"Mut_zero_mut_prob" : 0,
|
|
149
|
+
"Pos_zero_mut_prob" : np.nan,
|
|
150
|
+
"Transcript_ID" : transcript_id_input,
|
|
151
|
+
"O3D_transcript_ID" : transcript_id_o3d,
|
|
152
|
+
"Transcript_status" : transcript_status,
|
|
153
|
+
"Status" : np.nan},
|
|
154
|
+
index=[1])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Check if there is a mutation that is not in the structure
|
|
158
|
+
if max(mut_gene_df.Pos) > len(seq_gene):
|
|
159
|
+
not_in_structure_ix = mut_gene_df.Pos > len(seq_gene)
|
|
160
|
+
filter_gene, result_gene_df, mut_gene_df = process_mapping_issue(not_in_structure_ix,
|
|
161
|
+
mut_gene_df,
|
|
162
|
+
result_gene_df,
|
|
163
|
+
gene,
|
|
164
|
+
uniprot_id,
|
|
165
|
+
af_f,
|
|
166
|
+
transcript_status,
|
|
167
|
+
thr_mapping_issue,
|
|
168
|
+
issue_type="Mut_not_in_structure")
|
|
169
|
+
if filter_gene:
|
|
170
|
+
return None, result_gene_df
|
|
171
|
+
|
|
172
|
+
# Check for mismatch between WT reference and WT structure
|
|
173
|
+
wt_mismatch_ix = mut_gene_df.apply(lambda x: bool(seq_gene[x.Pos-1] != x.WT), axis=1)
|
|
174
|
+
if sum(wt_mismatch_ix) > 0:
|
|
175
|
+
filter_gene, result_gene_df, mut_gene_df = process_mapping_issue(wt_mismatch_ix,
|
|
176
|
+
mut_gene_df,
|
|
177
|
+
result_gene_df,
|
|
178
|
+
gene,
|
|
179
|
+
uniprot_id,
|
|
180
|
+
af_f,
|
|
181
|
+
transcript_status,
|
|
182
|
+
thr_mapping_issue,
|
|
183
|
+
issue_type="WT_mismatch")
|
|
184
|
+
if filter_gene:
|
|
185
|
+
return None, result_gene_df
|
|
186
|
+
|
|
187
|
+
# Load cmap
|
|
188
|
+
cmap_complete_path = f"{cmap_path}/{uniprot_id}-F{af_f}.npy"
|
|
189
|
+
if os.path.isfile(cmap_complete_path):
|
|
190
|
+
cmap = np.load(cmap_complete_path)
|
|
191
|
+
cmap = cmap > cmap_prob_thr
|
|
192
|
+
cmap = cmap.astype(int)
|
|
193
|
+
else:
|
|
194
|
+
result_gene_df["Status"] = "Cmap_not_found"
|
|
195
|
+
return None, result_gene_df
|
|
196
|
+
|
|
197
|
+
# Load PAE
|
|
198
|
+
pae_complete_path = f"{pae_path}/{uniprot_id}-F{af_f}-predicted_aligned_error.npy"
|
|
199
|
+
if os.path.isfile(pae_complete_path):
|
|
200
|
+
pae = np.load(pae_complete_path)
|
|
201
|
+
else:
|
|
202
|
+
pae = None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## Get expected local myssense mutation density
|
|
206
|
+
|
|
207
|
+
# Probability that each residue can be hit by a missense mut
|
|
208
|
+
if miss_prob_dict is not None:
|
|
209
|
+
gene_miss_prob = np.array(miss_prob_dict[f"{uniprot_id}-F{af_f}"])
|
|
210
|
+
else:
|
|
211
|
+
gene_miss_prob = get_unif_gene_miss_prob(size=len(cmap))
|
|
212
|
+
|
|
213
|
+
# Filter out genes whose missense prob vec include any NA
|
|
214
|
+
if np.any(np.isnan(gene_miss_prob)):
|
|
215
|
+
result_gene_df["Status"] = "NA_miss_prob"
|
|
216
|
+
return None, result_gene_df
|
|
217
|
+
|
|
218
|
+
# Filter out genes with a mutation in a residue having zero prob to mutate
|
|
219
|
+
pos_vec = np.unique(mut_gene_df["Pos"].values)
|
|
220
|
+
pos_prob_vec = np.array(gene_miss_prob)[pos_vec-1]
|
|
221
|
+
if (pos_prob_vec == 0).any():
|
|
222
|
+
result_gene_df["Status"] = "Mut_with_zero_prob"
|
|
223
|
+
pos_prob_vec = np.array(gene_miss_prob)[pos_vec-1]
|
|
224
|
+
pos_zero_prob = list(pos_vec[pos_prob_vec == 0])
|
|
225
|
+
mut_zero_prob_ix = mut_gene_df["Pos"].isin(pos_zero_prob)
|
|
226
|
+
mut_zero_prob_count = sum(mut_zero_prob_ix)
|
|
227
|
+
result_gene_df["Mut_zero_mut_prob"] = mut_zero_prob_count
|
|
228
|
+
result_gene_df["Pos_zero_mut_prob"] = str(pos_zero_prob)
|
|
229
|
+
ratio_zero_prob = mut_zero_prob_count / len(mut_gene_df)
|
|
230
|
+
logger_out = f"Detected {mut_zero_prob_count} ({ratio_zero_prob*100:.1f}%) mut in {len(pos_zero_prob)} pos {pos_zero_prob} with zero mut prob in {gene} ({uniprot_id}-F{af_f}, transcript status = {transcript_status}): "
|
|
231
|
+
result_gene_df["Ratio_mut_zero_prob"] = ratio_zero_prob
|
|
232
|
+
|
|
233
|
+
if ratio_zero_prob > thr_mapping_issue:
|
|
234
|
+
result_gene_df["Status"] = "Mut_with_zero_prob"
|
|
235
|
+
if transcript_status == "Match":
|
|
236
|
+
logger.warning(logger_out + "Filtering the gene..")
|
|
237
|
+
else:
|
|
238
|
+
logger.debug(logger_out + "Filtering the gene..")
|
|
239
|
+
return None, result_gene_df
|
|
240
|
+
else:
|
|
241
|
+
if transcript_status == "Match":
|
|
242
|
+
logger.warning(logger_out + "Filtering the mutations..")
|
|
243
|
+
else:
|
|
244
|
+
logger.debug(logger_out + "Filtering the mutations..")
|
|
245
|
+
mut_gene_df = mut_gene_df[~mut_zero_prob_ix]
|
|
246
|
+
|
|
247
|
+
# Probability that the volume of each residue can be hit by a missense mut
|
|
248
|
+
vol_missense_mut_prob = np.dot(cmap, gene_miss_prob)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
## Get observed and ranked simulated scores (loglik+_LFC)
|
|
252
|
+
|
|
253
|
+
# Get the observed mut count and densities
|
|
254
|
+
count = mut_gene_df.Pos.value_counts()
|
|
255
|
+
mut_count_v = np.zeros(len(cmap))
|
|
256
|
+
mut_count_v[count.index - 1] = count.values
|
|
257
|
+
mut_count_m = mut_count_v.reshape((1, -1))
|
|
258
|
+
density_m = np.einsum('ij,jk->ki', cmap, mut_count_m.T, optimize=True)
|
|
259
|
+
mutated_pos = np.sort(count.index)
|
|
260
|
+
|
|
261
|
+
# Do not process if there isn't any density larger than 1
|
|
262
|
+
if max(density_m[0][mutated_pos-1]) <= 1:
|
|
263
|
+
result_gene_df["Status"] = "No_density"
|
|
264
|
+
return None, result_gene_df
|
|
265
|
+
|
|
266
|
+
# Inialize result df
|
|
267
|
+
result_pos_df = pd.DataFrame({"Pos" : mutated_pos, "Mut_in_vol" : density_m[0, mutated_pos-1].astype(int)})
|
|
268
|
+
|
|
269
|
+
# Get the ranked simulated score
|
|
270
|
+
sim_anomaly = get_sim_anomaly_score(len(mut_gene_df),
|
|
271
|
+
cmap,
|
|
272
|
+
gene_miss_prob,
|
|
273
|
+
vol_missense_mut_prob,
|
|
274
|
+
num_iteration=num_iteration,
|
|
275
|
+
seed=seed)
|
|
276
|
+
|
|
277
|
+
# Get ranked observed score (loglik+_LFC)
|
|
278
|
+
no_mut_pos = len(result_pos_df)
|
|
279
|
+
sim_anomaly = sim_anomaly.iloc[:no_mut_pos,:].reset_index()
|
|
280
|
+
|
|
281
|
+
result_pos_df["Score"] = get_anomaly_score(result_pos_df["Mut_in_vol"],
|
|
282
|
+
len(mut_gene_df),
|
|
283
|
+
vol_missense_mut_prob[result_pos_df["Pos"]-1])
|
|
284
|
+
if np.isinf(result_pos_df.Score).any():
|
|
285
|
+
logger.debug(f"Detected inf observed score in gene {gene} ({uniprot_id}-F{af_f}): Recomputing with higher precision..")
|
|
286
|
+
result_pos_df = recompute_inf_score(result_pos_df, len(mut_gene_df), vol_missense_mut_prob[result_pos_df["Pos"]-1])
|
|
287
|
+
|
|
288
|
+
mut_in_res = count.rename("Mut_in_res").reset_index().rename(columns={"index" : "Pos"})
|
|
289
|
+
result_pos_df = mut_in_res.merge(result_pos_df, on = "Pos", how = "outer")
|
|
290
|
+
result_pos_df = result_pos_df.sort_values("Score", ascending=False).reset_index(drop=True)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
## Compute p-val and assign hits
|
|
294
|
+
|
|
295
|
+
# Add to the simulated score of each iteration its standard deviation
|
|
296
|
+
# (makes the method more conservative, eg., avoid borderline cases)
|
|
297
|
+
sim_anomaly.iloc[:,1:] = sim_anomaly.apply(lambda x: x[1:] + x[1:].std(), axis=1)
|
|
298
|
+
|
|
299
|
+
# Ratio observed and simulated anomaly scores
|
|
300
|
+
# (used to break the tie in p-values gene sorting)
|
|
301
|
+
result_pos_df["Score_obs_sim"] = sim_anomaly.apply(lambda x: result_pos_df["Score"].values[int(x["index"])] / np.mean(x[1:]), axis=1)
|
|
302
|
+
|
|
303
|
+
# Empirical p-val
|
|
304
|
+
result_pos_df["pval"] = sim_anomaly.apply(lambda x: sum(x[1:] >= result_pos_df["Score"].values[int(x["index"])]) / len(x[1:]), axis=1)
|
|
305
|
+
|
|
306
|
+
# Assign hits
|
|
307
|
+
result_pos_df["C"] = [int(i) for i in result_pos_df["pval"] < alpha]
|
|
308
|
+
|
|
309
|
+
# Select extended significant hits
|
|
310
|
+
pos_hits = result_pos_df[result_pos_df["C"] == 1].Pos
|
|
311
|
+
neigh_pos_hits = list(set([pos for p in pos_hits.values for pos in list(np.where(cmap[p - 1])[0] + 1)]))
|
|
312
|
+
pos_hits_extended = [pos for pos in result_pos_df.Pos if pos in neigh_pos_hits]
|
|
313
|
+
result_pos_df["C_ext"] = result_pos_df.apply(lambda x: 1 if (x["C"] == 0) & (x["Pos"] in pos_hits_extended)
|
|
314
|
+
else 0 if (x["C"] == 1) else np.nan, axis=1)
|
|
315
|
+
result_pos_df["C"] = result_pos_df.apply(lambda x: 1 if (x["C"] == 1) | (x["C_ext"] == 1) else 0, axis=1)
|
|
316
|
+
pos_hits = result_pos_df[result_pos_df["C"] == 1].Pos
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
## Communities detection
|
|
320
|
+
if len(pos_hits) > 0:
|
|
321
|
+
if len(pos_hits) > 1:
|
|
322
|
+
# Build network and perform detection
|
|
323
|
+
G = get_network(pos_hits, mut_count_v, cmap)
|
|
324
|
+
communities = nx_comm.label_propagation_communities(G)
|
|
325
|
+
clumps = get_community_index_nx(pos_hits, communities)
|
|
326
|
+
|
|
327
|
+
else:
|
|
328
|
+
# Assign cluster 0 to the only pos hit
|
|
329
|
+
clumps = 0
|
|
330
|
+
meta_clusters = pd.DataFrame({"Pos" : pos_hits, "Clump" : clumps})
|
|
331
|
+
result_pos_df = result_pos_df.merge(meta_clusters, how = "left", on = "Pos")
|
|
332
|
+
else:
|
|
333
|
+
result_pos_df["Clump"] = np.nan
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
## Output
|
|
337
|
+
if len(pos_hits) > 0:
|
|
338
|
+
clustered_mut = sum([pos in np.unique(np.concatenate([np.where(cmap[pos-1])[0]+1 for pos in pos_hits.values]))
|
|
339
|
+
for pos in mut_gene_df.Pos])
|
|
340
|
+
else:
|
|
341
|
+
clustered_mut = 0
|
|
342
|
+
result_pos_df["Rank"] = result_pos_df.index
|
|
343
|
+
result_pos_df.insert(0, "Gene", gene)
|
|
344
|
+
result_pos_df.insert(1, "Uniprot_ID", uniprot_id)
|
|
345
|
+
result_pos_df.insert(2, "F", af_f)
|
|
346
|
+
result_pos_df.insert(4, "Mut_in_gene", len(mut_gene_df))
|
|
347
|
+
result_pos_df = add_info(mut_gene_df, result_pos_df, cmap, pae, sample_info)
|
|
348
|
+
result_gene_df["Clust_res"] = len(pos_hits)
|
|
349
|
+
result_gene_df["Clust_mut"] = clustered_mut
|
|
350
|
+
result_gene_df["Status"] = "Processed"
|
|
351
|
+
|
|
352
|
+
return result_pos_df, result_gene_df
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def clustering_3d_mp(genes,
|
|
356
|
+
data,
|
|
357
|
+
cmap_path,
|
|
358
|
+
miss_prob_dict,
|
|
359
|
+
seq_df,
|
|
360
|
+
plddt_df,
|
|
361
|
+
num_process,
|
|
362
|
+
alpha=0.01,
|
|
363
|
+
num_iteration=10000,
|
|
364
|
+
cmap_prob_thr=0.5,
|
|
365
|
+
seed=None,
|
|
366
|
+
pae_path=None,
|
|
367
|
+
thr_mapping_issue=0.1,
|
|
368
|
+
sample_info=False):
|
|
369
|
+
"""
|
|
370
|
+
Run the 3D-clustering algorithm in parallel on multiple genes.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
result_gene_lst = []
|
|
374
|
+
result_pos_lst = []
|
|
375
|
+
|
|
376
|
+
for n, gene in enumerate(genes):
|
|
377
|
+
|
|
378
|
+
mut_gene_df = data[data["Gene"] == gene]
|
|
379
|
+
seq_df_gene = seq_df[seq_df["Gene"] == gene]
|
|
380
|
+
uniprot_id = seq_df_gene['Uniprot_ID'].values[0]
|
|
381
|
+
seq = seq_df_gene['Seq'].values[0]
|
|
382
|
+
af_f = seq_df_gene['F'].values[0]
|
|
383
|
+
|
|
384
|
+
# Add confidence to mut_gene_df
|
|
385
|
+
plddt_df_gene_df = plddt_df[plddt_df["Uniprot_ID"] == uniprot_id].drop(columns=["Uniprot_ID"])
|
|
386
|
+
mut_gene_df = mut_gene_df.merge(plddt_df_gene_df, on = ["Pos"], how = "left")
|
|
387
|
+
|
|
388
|
+
pos_result, result_gene = clustering_3d(gene,
|
|
389
|
+
uniprot_id,
|
|
390
|
+
mut_gene_df,
|
|
391
|
+
cmap_path,
|
|
392
|
+
miss_prob_dict,
|
|
393
|
+
seq_gene=seq,
|
|
394
|
+
af_f=af_f,
|
|
395
|
+
alpha=alpha,
|
|
396
|
+
num_iteration=num_iteration,
|
|
397
|
+
cmap_prob_thr=cmap_prob_thr,
|
|
398
|
+
seed=seed,
|
|
399
|
+
pae_path=pae_path,
|
|
400
|
+
thr_mapping_issue=thr_mapping_issue,
|
|
401
|
+
sample_info=sample_info)
|
|
402
|
+
result_gene_lst.append(result_gene)
|
|
403
|
+
if pos_result is not None:
|
|
404
|
+
result_pos_lst.append(pos_result)
|
|
405
|
+
|
|
406
|
+
# Monitor processing
|
|
407
|
+
if n == 0:
|
|
408
|
+
logger.debug(f"Process [{num_process+1}] starting..")
|
|
409
|
+
elif n % 10 == 0:
|
|
410
|
+
logger.debug(f"Process [{num_process+1}] completed [{n+1}/{len(genes)}] structures..")
|
|
411
|
+
elif n+1 == len(genes):
|
|
412
|
+
logger.debug(f"Process [{num_process+1}] completed!")
|
|
413
|
+
|
|
414
|
+
return result_gene_lst, result_pos_lst
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def clustering_3d_mp_wrapper(genes,
|
|
418
|
+
data,
|
|
419
|
+
cmap_path,
|
|
420
|
+
miss_prob_dict,
|
|
421
|
+
seq_df,
|
|
422
|
+
plddt_df,
|
|
423
|
+
num_cores,
|
|
424
|
+
alpha=0.01,
|
|
425
|
+
num_iteration=10000,
|
|
426
|
+
cmap_prob_thr=0.5,
|
|
427
|
+
seed=None,
|
|
428
|
+
pae_path=None,
|
|
429
|
+
thr_mapping_issue=0.1,
|
|
430
|
+
sample_info=False):
|
|
431
|
+
"""
|
|
432
|
+
Wrapper function to run the 3D-clustering algorithm in parallel on multiple genes.
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
# Split the genes into chunks for each process
|
|
436
|
+
chunk_size = int(len(genes) / num_cores) + 1
|
|
437
|
+
chunks = [genes[i : i + chunk_size] for i in range(0, len(genes), chunk_size)]
|
|
438
|
+
# num_cores = min(num_cores, len(chunks))
|
|
439
|
+
|
|
440
|
+
# Create a pool of processes and run clustering in parallel
|
|
441
|
+
with multiprocessing.Pool(processes = num_cores) as pool:
|
|
442
|
+
|
|
443
|
+
logger.debug(f'Starting [{len(chunks)}] processes..')
|
|
444
|
+
results = pool.starmap(clustering_3d_mp, [(chunk,
|
|
445
|
+
data[data["Gene"].isin(chunk)],
|
|
446
|
+
cmap_path,
|
|
447
|
+
miss_prob_dict,
|
|
448
|
+
seq_df[seq_df["Gene"].isin(chunk)],
|
|
449
|
+
plddt_df[plddt_df["Uniprot_ID"].isin(seq_df.loc[seq_df["Gene"].isin(chunk), "Uniprot_ID"])],
|
|
450
|
+
n_process,
|
|
451
|
+
alpha,
|
|
452
|
+
num_iteration,
|
|
453
|
+
cmap_prob_thr,
|
|
454
|
+
seed,
|
|
455
|
+
pae_path,
|
|
456
|
+
thr_mapping_issue,
|
|
457
|
+
sample_info)
|
|
458
|
+
for n_process, chunk in enumerate(chunks)])
|
|
459
|
+
|
|
460
|
+
# Parse output
|
|
461
|
+
result_pos_lst = [pd.concat(r[1]) for r in results if len(r[1]) > 0]
|
|
462
|
+
if len(result_pos_lst) > 0:
|
|
463
|
+
result_pos = pd.concat(result_pos_lst)
|
|
464
|
+
else:
|
|
465
|
+
result_pos = None
|
|
466
|
+
result_gene = pd.concat([pd.concat(r[0]) for r in results])
|
|
467
|
+
|
|
468
|
+
return result_pos, result_gene
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def run_clustering(input_path,
|
|
472
|
+
mut_profile_path,
|
|
473
|
+
mutability_config_path,
|
|
474
|
+
output_dir,
|
|
475
|
+
cmap_path,
|
|
476
|
+
seq_df_path,
|
|
477
|
+
plddt_path,
|
|
478
|
+
pae_path,
|
|
479
|
+
n_iterations,
|
|
480
|
+
alpha,
|
|
481
|
+
cmap_prob_thr,
|
|
482
|
+
cores,
|
|
483
|
+
seed,
|
|
484
|
+
verbose,
|
|
485
|
+
cancer_type,
|
|
486
|
+
cohort,
|
|
487
|
+
no_fragments,
|
|
488
|
+
only_processed,
|
|
489
|
+
thr_mapping_issue,
|
|
490
|
+
o3d_transcripts,
|
|
491
|
+
use_input_symbols,
|
|
492
|
+
mane,
|
|
493
|
+
sample_info):
|
|
494
|
+
"""
|
|
495
|
+
Main function to lunch the 3D clustering analysis.
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
# Load
|
|
499
|
+
# ====
|
|
500
|
+
|
|
501
|
+
seq_df = pd.read_csv(seq_df_path, sep="\t")
|
|
502
|
+
data, seq_df = parse_maf_input(input_path,
|
|
503
|
+
seq_df,
|
|
504
|
+
use_o3d_transcripts=o3d_transcripts,
|
|
505
|
+
use_input_symbols=use_input_symbols,
|
|
506
|
+
mane=mane)
|
|
507
|
+
|
|
508
|
+
if len(data) > 0:
|
|
509
|
+
|
|
510
|
+
# Run
|
|
511
|
+
# ===
|
|
512
|
+
|
|
513
|
+
# Get genes with enough mut
|
|
514
|
+
result_np_gene_lst = []
|
|
515
|
+
genes = data.groupby("Gene").apply(len)
|
|
516
|
+
genes_mut = genes[genes >= 2]
|
|
517
|
+
genes_no_mut = genes[genes < 2].index
|
|
518
|
+
|
|
519
|
+
if len(genes_no_mut) > 0:
|
|
520
|
+
logger.debug(f"Detected [{len(genes_no_mut)}] genes without enough mutations: Skipping..")
|
|
521
|
+
result_gene = pd.DataFrame({"Gene" : genes_no_mut,
|
|
522
|
+
"Uniprot_ID" : np.nan,
|
|
523
|
+
"F" : np.nan,
|
|
524
|
+
"Mut_in_gene" : 1,
|
|
525
|
+
"Ratio_not_in_structure" : np.nan,
|
|
526
|
+
"Ratio_WT_mismatch" : np.nan,
|
|
527
|
+
"Mut_zero_mut_prob" : np.nan,
|
|
528
|
+
"Pos_zero_mut_prob" : np.nan,
|
|
529
|
+
"Transcript_ID" : get_gene_entry(data, genes_no_mut, "Transcript_ID"),
|
|
530
|
+
"O3D_transcript_ID" : get_gene_entry(data, genes_no_mut, "O3D_transcript_ID"),
|
|
531
|
+
"Transcript_status" : get_gene_entry(data, genes_no_mut, "Transcript_status"),
|
|
532
|
+
"Status" : "No_mut"})
|
|
533
|
+
result_np_gene_lst.append(result_gene)
|
|
534
|
+
|
|
535
|
+
# Seq df for metadata info
|
|
536
|
+
metadata_cols = [col for col in ["Gene", "HGNC_ID", "Ens_Gene_ID", "Ens_Transcr_ID", "Refseq_prot", "Uniprot_ID", "F"] if col in seq_df.columns]
|
|
537
|
+
metadata_mapping_cols = [col for col in ["Seq", "Chr", "Reverse_strand", "Exons_coord", "Seq_dna", "Tri_context", "Reference_info"] if col in seq_df.columns]
|
|
538
|
+
seq_df_all = seq_df[seq_df["Gene"].isin(genes.index)].copy()
|
|
539
|
+
|
|
540
|
+
# Get genes with corresponding Uniprot-ID mapping
|
|
541
|
+
gene_to_uniprot_dict = {gene : uni_id for gene, uni_id in seq_df[["Gene", "Uniprot_ID"]].drop_duplicates().values}
|
|
542
|
+
genes_to_process = [gene for gene in genes_mut.index if gene in gene_to_uniprot_dict.keys()]
|
|
543
|
+
seq_df = seq_df[seq_df["Gene"].isin(genes_to_process)].reset_index(drop=True)
|
|
544
|
+
genes_no_mapping = genes[[gene in genes_mut.index and gene not in gene_to_uniprot_dict.keys() for gene in genes.index]]
|
|
545
|
+
if len(genes_no_mapping) > 0:
|
|
546
|
+
logger.debug(f"Detected [{len(genes_no_mapping)}] genes without IDs mapping: Skipping..")
|
|
547
|
+
result_gene = pd.DataFrame({"Gene" : genes_no_mapping.index,
|
|
548
|
+
"Uniprot_ID" : np.nan,
|
|
549
|
+
"F" : np.nan,
|
|
550
|
+
"Mut_in_gene" : genes_no_mapping.values,
|
|
551
|
+
"Ratio_not_in_structure" : np.nan,
|
|
552
|
+
"Ratio_WT_mismatch" : np.nan,
|
|
553
|
+
"Mut_zero_mut_prob" : np.nan,
|
|
554
|
+
"Pos_zero_mut_prob" : np.nan,
|
|
555
|
+
"Transcript_ID" : get_gene_entry(data, genes_no_mapping.index, "Transcript_ID"),
|
|
556
|
+
"O3D_transcript_ID" : get_gene_entry(data, genes_no_mapping.index, "O3D_transcript_ID"),
|
|
557
|
+
"Transcript_status" : get_gene_entry(data, genes_no_mapping.index, "Transcript_status"),
|
|
558
|
+
"Status" : "No_ID_mapping"})
|
|
559
|
+
result_np_gene_lst.append(result_gene)
|
|
560
|
+
|
|
561
|
+
# Filter on fragmented (AF-F) genes
|
|
562
|
+
if no_fragments:
|
|
563
|
+
# Return the fragmented genes as non processed output
|
|
564
|
+
genes_frag = seq_df[seq_df.F.str.extract(r'(\d+)', expand=False).astype(int) > 1]
|
|
565
|
+
genes_frag = genes_frag.Gene.reset_index(drop=True).values
|
|
566
|
+
genes_frag_mut = genes_mut[[gene in genes_frag for gene in genes_mut.index]]
|
|
567
|
+
genes_frag = genes_frag_mut.index.values
|
|
568
|
+
if len(genes_frag) > 0:
|
|
569
|
+
logger.debug(f"Detected [{len(genes_frag)}] fragmented genes with disabled fragments processing: Skipping..")
|
|
570
|
+
result_gene = pd.DataFrame({"Gene" : genes_frag,
|
|
571
|
+
"Uniprot_ID" : np.nan,
|
|
572
|
+
"F" : np.nan,
|
|
573
|
+
"Mut_in_gene" : genes_frag_mut.values,
|
|
574
|
+
"Ratio_not_in_structure" : np.nan,
|
|
575
|
+
"Ratio_WT_mismatch" : np.nan,
|
|
576
|
+
"Mut_zero_mut_prob" : np.nan,
|
|
577
|
+
"Pos_zero_mut_prob" : np.nan,
|
|
578
|
+
"Transcript_ID" : get_gene_entry(data, genes_frag, "Transcript_ID"),
|
|
579
|
+
"O3D_transcript_ID" : get_gene_entry(data, genes_frag, "O3D_transcript_ID"),
|
|
580
|
+
"Transcript_status" : get_gene_entry(data, genes_frag, "Transcript_status"),
|
|
581
|
+
"Status" : "Fragmented"})
|
|
582
|
+
result_np_gene_lst.append(result_gene)
|
|
583
|
+
# Filter out from genes to process and seq df
|
|
584
|
+
genes_to_process = [gene for gene in genes_to_process if gene not in genes_frag]
|
|
585
|
+
seq_df = seq_df[seq_df["Gene"].isin(genes_to_process)].reset_index(drop=True)
|
|
586
|
+
|
|
587
|
+
# Filter on start-loss mutations
|
|
588
|
+
start_mut_ix = data["Pos"] == 1
|
|
589
|
+
start_mut = sum(start_mut_ix)
|
|
590
|
+
if start_mut > 0:
|
|
591
|
+
genes_start_mut = list(data[start_mut_ix].Gene.unique())
|
|
592
|
+
data = data[~start_mut_ix]
|
|
593
|
+
logger.warning(f"Detected {start_mut} start-loss mutations in {len(genes_start_mut)} genes {genes_start_mut}: Filtering mutations..")
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
# Missense mut prob
|
|
597
|
+
# =================
|
|
598
|
+
|
|
599
|
+
# Using mutabilities if provided
|
|
600
|
+
if mutability_config_path is not None:
|
|
601
|
+
logger.info("Computing missense mut probabilities using mutabilities..")
|
|
602
|
+
mutab_config = json.load(open(mutability_config_path, encoding="utf-8"))
|
|
603
|
+
logger.debug("Init mutabilities module..")
|
|
604
|
+
init_mutabilities_module(mutab_config)
|
|
605
|
+
seq_df = seq_df[seq_df["Reference_info"] == 1]
|
|
606
|
+
seq_df['Exons_coord'] = seq_df['Exons_coord'].apply(eval)
|
|
607
|
+
genes_to_process = [gene for gene in genes_to_process if gene in seq_df["Gene"].unique()]
|
|
608
|
+
genes_not_mutability = [gene for gene in genes_to_process if gene not in seq_df["Gene"].unique()]
|
|
609
|
+
logger.debug("Computing probabilities..")
|
|
610
|
+
miss_prob_dict = get_miss_mut_prob_dict(mut_rate_dict=None, seq_df=seq_df,
|
|
611
|
+
mutability=True, mutability_config=mutab_config)
|
|
612
|
+
|
|
613
|
+
if len(genes_not_mutability) > 0:
|
|
614
|
+
logger.debug(f"Detected [{len(genes_not_mutability)}] genes without mutability information: Skipping..")
|
|
615
|
+
result_gene = pd.DataFrame({"Gene" : genes_not_mutability,
|
|
616
|
+
"Uniprot_ID" : np.nan,
|
|
617
|
+
"F" : np.nan,
|
|
618
|
+
"Mut_in_gene" : np.nan,
|
|
619
|
+
"Ratio_not_in_structure" : np.nan,
|
|
620
|
+
"Ratio_WT_mismatch" : np.nan,
|
|
621
|
+
"Mut_zero_mut_prob" : np.nan,
|
|
622
|
+
"Pos_zero_mut_prob" : np.nan,
|
|
623
|
+
"Transcript_ID" : get_gene_entry(data, genes_not_mutability, "Transcript_ID"),
|
|
624
|
+
"O3D_transcript_ID" : get_gene_entry(data, genes_not_mutability, "O3D_transcript_ID"),
|
|
625
|
+
"Transcript_status" : get_gene_entry(data, genes_not_mutability, "Transcript_status"),
|
|
626
|
+
"Status" : "No_mutability"})
|
|
627
|
+
result_np_gene_lst.append(result_gene)
|
|
628
|
+
|
|
629
|
+
# Using mutational profiles
|
|
630
|
+
elif mut_profile_path is not None:
|
|
631
|
+
# Compute dict from mut profile of the cohort and dna sequences
|
|
632
|
+
mut_profile = json.load(open(mut_profile_path, encoding="utf-8"))
|
|
633
|
+
logger.info("Computing missense mut probabilities..")
|
|
634
|
+
if not isinstance(mut_profile, dict):
|
|
635
|
+
mut_profile = mut_rate_vec_to_dict(mut_profile)
|
|
636
|
+
miss_prob_dict = get_miss_mut_prob_dict(mut_rate_dict=mut_profile, seq_df=seq_df)
|
|
637
|
+
else:
|
|
638
|
+
logger.warning("Mutation profile not provided: Uniform distribution will be used for scoring and simulations.")
|
|
639
|
+
miss_prob_dict = None
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
# Run 3D-clustering
|
|
643
|
+
# =================
|
|
644
|
+
|
|
645
|
+
if len(result_np_gene_lst):
|
|
646
|
+
result_np_gene = pd.concat(result_np_gene_lst)
|
|
647
|
+
result_np_gene["Uniprot_ID"] = [gene_to_uniprot_dict[gene] if gene in gene_to_uniprot_dict.keys() else np.nan for gene in result_np_gene["Gene"].values]
|
|
648
|
+
if len(genes_to_process) > 0:
|
|
649
|
+
logger.info(f"Performing 3D-clustering on [{len(seq_df)}] proteins..")
|
|
650
|
+
seq_df = seq_df[["Gene", "Uniprot_ID", "F", "Seq"]]
|
|
651
|
+
plddt_df = pd.read_csv(plddt_path, sep="\t", usecols=["Pos", "Confidence", "Uniprot_ID"], dtype={"Pos" : np.int32,
|
|
652
|
+
"Confidence" : np.float32,
|
|
653
|
+
"Uniprot_ID" : "object"})
|
|
654
|
+
|
|
655
|
+
result_pos, result_gene = clustering_3d_mp_wrapper(genes=genes_to_process,
|
|
656
|
+
data=data,
|
|
657
|
+
cmap_path=cmap_path,
|
|
658
|
+
miss_prob_dict=miss_prob_dict,
|
|
659
|
+
seq_df=seq_df,
|
|
660
|
+
plddt_df=plddt_df,
|
|
661
|
+
num_cores=cores,
|
|
662
|
+
alpha=alpha,
|
|
663
|
+
num_iteration=n_iterations,
|
|
664
|
+
cmap_prob_thr=cmap_prob_thr,
|
|
665
|
+
seed=seed,
|
|
666
|
+
pae_path=pae_path,
|
|
667
|
+
thr_mapping_issue=thr_mapping_issue,
|
|
668
|
+
sample_info=sample_info)
|
|
669
|
+
if result_np_gene_lst:
|
|
670
|
+
result_gene = pd.concat((result_gene, result_np_gene))
|
|
671
|
+
else:
|
|
672
|
+
result_gene = result_np_gene
|
|
673
|
+
result_pos = None
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
# Save
|
|
677
|
+
#=====
|
|
678
|
+
|
|
679
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
680
|
+
result_gene["Cancer"] = cancer_type
|
|
681
|
+
result_gene["Cohort"] = cohort
|
|
682
|
+
output_path_pos = os.path.join(output_dir, f"{cohort}.3d_clustering_pos.csv")
|
|
683
|
+
output_path_genes = os.path.join(output_dir, f"{cohort}.3d_clustering_genes.csv")
|
|
684
|
+
|
|
685
|
+
# Save processed seq_df and input files
|
|
686
|
+
seq_df_output = os.path.join(output_dir, f"{cohort}.seq_df.processed.tsv")
|
|
687
|
+
input_mut_output = os.path.join(output_dir, f"{cohort}.mutations.processed.tsv")
|
|
688
|
+
input_prob_output = os.path.join(output_dir, f"{cohort}.miss_prob.processed.json")
|
|
689
|
+
logger.info(f"Saving {seq_df_output}")
|
|
690
|
+
seq_df_all[metadata_cols + metadata_mapping_cols].to_csv(seq_df_output, sep="\t", index=False)
|
|
691
|
+
logger.info(f"Saving {input_mut_output}")
|
|
692
|
+
data.to_csv(input_mut_output, sep="\t", index=False)
|
|
693
|
+
logger.info(f"Saving {input_prob_output}")
|
|
694
|
+
with open(input_prob_output, "w") as json_file:
|
|
695
|
+
json.dump(miss_prob_dict, json_file)
|
|
696
|
+
|
|
697
|
+
# Add extra metadata
|
|
698
|
+
result_gene = result_gene.drop(columns=["F"]).merge(seq_df_all[metadata_cols], on=["Gene", "Uniprot_ID"], how="left")
|
|
699
|
+
|
|
700
|
+
if only_processed:
|
|
701
|
+
result_gene = result_gene[result_gene["Status"] == "Processed"]
|
|
702
|
+
|
|
703
|
+
if result_pos is None:
|
|
704
|
+
# Save gene-level result and empty res-level result
|
|
705
|
+
logger.warning("Did not processed any genes!")
|
|
706
|
+
result_gene = add_nan_clust_cols(result_gene)
|
|
707
|
+
result_gene = sort_cols(result_gene)
|
|
708
|
+
if not sample_info:
|
|
709
|
+
result_gene.drop(columns=[col for col in ['Tot_samples',
|
|
710
|
+
'Samples_in_top_vol',
|
|
711
|
+
'Samples_in_top_cl_vol'] if col in result_gene.columns], inplace=True)
|
|
712
|
+
if no_fragments:
|
|
713
|
+
result_gene = result_gene.drop(columns=[col for col in ["Mut_in_top_F", "Top_F"] if col in result_gene.columns])
|
|
714
|
+
empty_result_pos(sample_info).to_csv(output_path_pos, index=False)
|
|
715
|
+
result_gene.to_csv(output_path_genes, index=False)
|
|
716
|
+
|
|
717
|
+
logger.info(f"Saving (empty) {output_path_pos}")
|
|
718
|
+
logger.info(f"Saving {output_path_genes}")
|
|
719
|
+
|
|
720
|
+
else:
|
|
721
|
+
# Save res-level result
|
|
722
|
+
result_pos["Cancer"] = cancer_type
|
|
723
|
+
result_pos["Cohort"] = cohort
|
|
724
|
+
if not sample_info:
|
|
725
|
+
result_pos.drop(columns=[col for col in ['Tot_samples',
|
|
726
|
+
'Samples_in_vol',
|
|
727
|
+
'Samples_in_cl_vol'] if col in result_gene.columns], inplace=True)
|
|
728
|
+
result_pos = result_pos.sort_values(["Gene", "pval", "Score_obs_sim"], ascending=[True, True, False]).reset_index(drop=True)
|
|
729
|
+
result_pos.drop(columns=["F"], errors="ignore").to_csv(output_path_pos, index=False)
|
|
730
|
+
|
|
731
|
+
# Get gene global pval, qval, and clustering annotations and save gene-level result
|
|
732
|
+
result_gene = get_final_gene_result(result_pos, result_gene, alpha, sample_info)
|
|
733
|
+
result_gene = sort_cols(result_gene)
|
|
734
|
+
if not sample_info:
|
|
735
|
+
result_gene.drop(columns=[col for col in ['Tot_samples',
|
|
736
|
+
'Samples_in_top_vol',
|
|
737
|
+
'Samples_in_top_cl_vol'] if col in result_gene.columns], inplace=True)
|
|
738
|
+
if no_fragments:
|
|
739
|
+
result_gene.drop(columns=[col for col in ["Mut_in_top_F", "Top_F"] if col in result_gene.columns], inplace=True)
|
|
740
|
+
with np.printoptions(linewidth=10000):
|
|
741
|
+
result_gene.to_csv(output_path_genes, index=False)
|
|
742
|
+
|
|
743
|
+
logger.info(f"Saving {output_path_pos}")
|
|
744
|
+
logger.info(f"Saving {output_path_genes}")
|
|
745
|
+
|
|
746
|
+
logger.info("3D-clustering analysis completed!")
|
|
747
|
+
|
|
748
|
+
else:
|
|
749
|
+
logger.warning("No missense mutations were found in the input MAF. Consider checking your data: the field 'Variant_Classification' should include either 'Missense_Mutation' or 'missense_variant'")
|