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,155 @@
1
+ """
2
+ Contains function to assign clustering anomaly score and perform simulations
3
+ """
4
+
5
+ import daiquiri
6
+ import numpy as np
7
+ import pandas as pd
8
+ from scipy import stats
9
+ from decimal import Decimal, getcontext
10
+ from functools import reduce
11
+ import operator
12
+
13
+ from scripts import __logger_name__
14
+
15
+ logger = daiquiri.getLogger(__logger_name__ + ".run.score_and_simulations")
16
+
17
+
18
+ def dcm_factorial(n):
19
+ """
20
+ Compute factorial.
21
+ """
22
+ return reduce(operator.mul, [Decimal(i) for i in range(1, int(n)+1)], Decimal(1))
23
+
24
+
25
+ def dcm_binom_coeff(n, k):
26
+ """
27
+ Compute binomial coefficient.
28
+ """
29
+
30
+ return dcm_factorial(n) / (dcm_factorial(k) * dcm_factorial(n - k))
31
+
32
+
33
+ def dcm_binom_cdf(k, n, p):
34
+ """
35
+ Compute binomial cumulative distribution function (CDF).
36
+ """
37
+
38
+ p = Decimal(p)
39
+ q = Decimal(1) - p
40
+ cdf = Decimal(0)
41
+ for i in range(int(k) + 1):
42
+ cdf += dcm_binom_coeff(n, i) * (p ** i) * (q ** (n - i))
43
+
44
+ return cdf
45
+
46
+
47
+ def dcm_binom_sf(k, n, p):
48
+ """
49
+ Compute binomial survival function (SF).
50
+ """
51
+
52
+ return Decimal('1') - dcm_binom_cdf(k, n, p)
53
+
54
+
55
+ def dcm_binom_logsf(k, n, p):
56
+ """
57
+ Compute log binomial survival function.
58
+ """
59
+
60
+ sf = dcm_binom_sf(k, n, p)
61
+ if sf <= 0:
62
+ return np.inf
63
+ return sf.ln()
64
+
65
+
66
+ def get_dcm_anomaly_score(k, n, p, decimal=600):
67
+ """
68
+ Use the decimal package to compute the anomaly score
69
+ with high precision to avoid approximation of the
70
+ numerator.
71
+
72
+ Score: loglik equal or larger mut_count / loglik(N)
73
+ """
74
+
75
+ getcontext().prec = decimal
76
+ num = dcm_binom_logsf(k-1, n, p)
77
+ den = stats.binom.logpmf(k=n, n=n, p=p)
78
+
79
+ return float(num / Decimal(den))
80
+
81
+
82
+ def recompute_inf_score(result_pos_df, gene_mut, vol_missense_mut_prob):
83
+ """
84
+ Use high precision calculation to recompute the score that
85
+ were approximated to inf by scipy.
86
+
87
+ The issue happens in extreme cases when the numerator of the score
88
+ is so small that is approximated to 0.
89
+ """
90
+
91
+ inf_ix = np.isinf(result_pos_df.Score)
92
+ if sum(inf_ix) > 0:
93
+ for ix, k, n, p in zip(np.where(inf_ix)[0],
94
+ result_pos_df.Mut_in_vol[inf_ix],
95
+ np.repeat(gene_mut, sum(inf_ix)),
96
+ vol_missense_mut_prob[inf_ix]):
97
+
98
+ if np.isinf(result_pos_df.iloc[ix].Score):
99
+ result_pos_df.loc[ix, "Score"] = get_dcm_anomaly_score(k, n, p)
100
+ else:
101
+ logger.warning("Trying to overwrite a non-inf score: Skipping..")
102
+
103
+ return result_pos_df
104
+
105
+
106
+ def get_anomaly_score(vec_mut_in_vol, gene_mut, vec_vol_miss_mut_prob):
107
+ """
108
+ Compute a metric that scores the anomaly of observing a certain
109
+ number of mutations in the volume of a residue.
110
+ It takes into account the volume and the mutation rate of the codon
111
+ of each residue within that volume.
112
+
113
+ Score: loglik equal or larger mut_count / loglik(N)
114
+ """
115
+
116
+ den = stats.binom.logpmf(k=gene_mut, n=gene_mut, p=vec_vol_miss_mut_prob)
117
+
118
+ return stats.binom.logsf(k=vec_mut_in_vol-1, n=gene_mut, p=vec_vol_miss_mut_prob) / den
119
+
120
+
121
+ def simulate_mutations(n_mutations, p, size, seed=None):
122
+ """
123
+ Simulate the mutations given the mutation rate of a cohort.
124
+ """
125
+
126
+ rng = np.random.default_rng(seed=seed)
127
+ samples = rng.multinomial(n_mutations, p, size=size)
128
+
129
+ return samples
130
+
131
+
132
+ def get_sim_anomaly_score(mut_count,
133
+ cmap,
134
+ gene_miss_prob,
135
+ vol_missense_mut_prob,
136
+ num_iteration=1000,
137
+ seed=None):
138
+ """
139
+ Simulated mutations following the mutation profile of the cohort.
140
+ Compute the log-likelihood of observing k or more mutation in the
141
+ volume and compare it with the corresponding simualted rank.
142
+ """
143
+
144
+ # Generate x sets of random mutation distributed following the mut
145
+ # profile of the cohort, each with the same size of the observed mut
146
+ mut_sim = simulate_mutations(mut_count, gene_miss_prob, num_iteration, seed)
147
+
148
+ # Get the density of mutations of each position in each iteration
149
+ density_sim = np.einsum('ij,jk->ki', cmap, mut_sim.T.astype(float), optimize=True)
150
+
151
+ # Compute the ranked score of the densities obtained at each iteration
152
+ # sign is used to sort in descending order
153
+ loglik_plus = -np.sort(-get_anomaly_score(density_sim, mut_count, vol_missense_mut_prob))
154
+
155
+ return pd.DataFrame(loglik_plus).T
scripts/run/utils.py ADDED
@@ -0,0 +1,461 @@
1
+ import re
2
+
3
+ import daiquiri
4
+ import numpy as np
5
+ import pandas as pd
6
+ import subprocess
7
+ import io
8
+ import gzip
9
+ import sys
10
+
11
+ from scripts import __logger_name__
12
+
13
+ logger = daiquiri.getLogger(__logger_name__ + ".run.utils")
14
+
15
+
16
+ ## Parsers
17
+
18
+ def get_seq_df_input_symbols(input_df, seq_df, mane=False):
19
+ """
20
+ Update gene names (HUGO Symbols) of O3D built sequence with names in input file.
21
+ Do it only for entries in the sequence df with available transcript information
22
+ and use transcript ID to get gene name.
23
+ """
24
+
25
+ # Split sequence df by entries with available transcript info (Reference_info 0 and 1) and not available ones (-1)
26
+ seq_df_tr_missing = seq_df[seq_df["Reference_info"] == -1].reset_index(drop=True)
27
+ seq_df_tr_available = seq_df[seq_df["Reference_info"] != -1].reset_index(drop=True)
28
+
29
+ # Use names from input
30
+ df_mapping = input_df[["Hugo_Symbol", "Feature"]].rename(columns={"Hugo_Symbol" : "Gene", "Feature" : "Ens_Transcr_ID"})
31
+ seq_df_tr_available = seq_df_tr_available.drop(columns=["Gene"]).drop_duplicates().merge(df_mapping, how="left", on="Ens_Transcr_ID")
32
+
33
+ # If the same gene is associated to multiple structures, keep the first one obtained from Uniprot (descending, Reference_info 1) or keep the MANE (ascending, Reference_info 0)
34
+ # TO DO: Use the one reviewed (UniProtKB reviewed (Swiss-Prot)), if multiple Uniprot ones are present. The info must be added during the build step
35
+ order_ascending = [True, mane]
36
+ seq_df_tr_available = seq_df_tr_available.sort_values(by=["Gene", "Reference_info"], ascending=order_ascending).drop_duplicates(subset="Gene")
37
+
38
+ # If the same genes is associated to multiple structures, keep the one not obtained by Backtranseq (Reference_info 1 or 0)
39
+ seq_df = pd.concat([seq_df_tr_missing, seq_df_tr_available]).sort_values(by=["Gene", "Reference_info"], ascending=[True, False])
40
+
41
+ return seq_df.drop_duplicates(subset="Gene").reset_index(drop=True)
42
+
43
+
44
+ def get_hgvsp_mut(df_row):
45
+ """
46
+ Parse mutation entries to get HGVSp_Short format.
47
+ """
48
+
49
+ amino_acids = df_row["Amino_acids"]
50
+
51
+ if pd.isna(amino_acids):
52
+ return np.nan
53
+
54
+ amino_acids = amino_acids.split("/")
55
+ if len(amino_acids) > 1:
56
+ return f"p.{amino_acids[0]}{df_row['Protein_position']}{amino_acids[1]}"
57
+
58
+ return np.nan
59
+
60
+
61
+ def filter_transcripts(df, seq_df):
62
+ """
63
+ Filter VEP output by Oncodrive3D transcripts. For genes with NA
64
+ transcripts in the sequence dataframe, keep canonical ones.
65
+ """
66
+
67
+ if "CANONICAL" in df.columns and "Feature" in df.columns:
68
+
69
+ # Genes without available transcript info in O3D built datasets
70
+ df_tr_missing = df[df["Hugo_Symbol"].isin(seq_df.loc[seq_df["Reference_info"] == -1, "Gene"])]
71
+ df_tr_missing = df_tr_missing[df_tr_missing["CANONICAL"] == "YES"]
72
+
73
+ # Genes with transcript info
74
+ df_tr_available = df[df["Feature"].isin(seq_df.loc[seq_df["Reference_info"] != -1, "Ens_Transcr_ID"])]
75
+
76
+ return pd.concat((df_tr_available, df_tr_missing))
77
+
78
+ else:
79
+ logger.critical("Failed to filter input by O3D transcripts. Please provide as input the output of VEP with canonical and transcripts information: Exiting..")
80
+ sys.exit(1)
81
+
82
+
83
+ def parse_vep_output(df,
84
+ seq_df=None,
85
+ use_o3d_transcripts=False,
86
+ use_input_symbols=False,
87
+ mane=False):
88
+ """
89
+ Parse the dataframe in case it is the direct output of VEP without any
90
+ processing. Rename the columns to match the fields name of a MAF file,
91
+ and select the canonical transcripts if multiple ones are present.
92
+ """
93
+
94
+ df.rename(columns={"SYMBOL": "Hugo_Symbol",
95
+ "Consequence": "Variant_Classification"}, inplace=True)
96
+
97
+ # Adapt HUGO_Symbol in seq_df to input file
98
+ if seq_df is not None and use_input_symbols:
99
+ logger.debug("Adapting Oncodrive3D HUGO Symbols of built datasets to input file..")
100
+ seq_df = get_seq_df_input_symbols(df, seq_df, mane)
101
+
102
+ # Transcripts filtering
103
+ if use_o3d_transcripts and seq_df is not None:
104
+ logger.debug("Filtering input by Oncodrive3D built transcripts..")
105
+ df = filter_transcripts(df, seq_df)
106
+ elif "CANONICAL" in df.columns:
107
+ df = df[df["CANONICAL"] == "YES"]
108
+
109
+ # Get HGVSp
110
+ if "HGVSp_Short" not in df.columns and "Amino_acids" in df.columns and "Protein_position" in df.columns:
111
+ df["HGVSp_Short"] = df.apply(get_hgvsp_mut, axis=1)
112
+
113
+ return df, seq_df
114
+
115
+
116
+ def parse_mutations(maf):
117
+ """
118
+ Parse HGVSp_Short in maf.
119
+ """
120
+
121
+ # Ensure the required 'HGVSp_Short' column is present and not empty
122
+ if 'HGVSp_Short' not in maf.columns or maf['HGVSp_Short'].isnull().all():
123
+ logger.critical("Missing or empty 'HGVSp_Short' column in input MAF data.")
124
+ sys.exit(1)
125
+
126
+ # Parse the position, wild type, and mutation type from 'HGVSp_Short'
127
+ maf.dropna(subset="HGVSp_Short", inplace=True)
128
+ maf['Pos'] = maf['HGVSp_Short'].apply(lambda x: re.sub(r"\D", "", x)).astype(np.int32)
129
+ maf['WT'] = maf['HGVSp_Short'].apply(lambda x: re.findall(r"\D", x)[2])
130
+ maf['Mut'] = maf['HGVSp_Short'].apply(lambda x: re.findall(r"\D", x)[3])
131
+
132
+ # Parse cols
133
+ columns_to_keep = ['Hugo_Symbol', 'Pos', 'WT', 'Mut', 'Tumor_Sample_Barcode', 'Feature', 'Transcript_ID']
134
+ columns_to_keep = [col for col in columns_to_keep if col in maf.columns]
135
+ maf = maf[columns_to_keep]
136
+ maf = maf.rename(columns={'Hugo_Symbol' : 'Gene', 'Feature': 'Transcript_ID'})
137
+
138
+ return maf.sort_values(by=['Gene', 'Pos']).reset_index(drop=True)
139
+
140
+
141
+ def add_transcript_info(maf, seq_df):
142
+ """
143
+ Add transcript status information.
144
+ """
145
+
146
+ if 'Transcript_ID' not in maf.columns:
147
+ maf['Transcript_ID'] = np.nan
148
+ maf = maf.merge(seq_df[[col for col in ['Gene', 'Ens_Transcr_ID', 'Refseq_prot'] if col in seq_df.columns]].drop_duplicates(),
149
+ on='Gene', how='left').rename(columns={"Ens_Transcr_ID" : "O3D_transcript_ID"})
150
+
151
+ # Vectorized conditions for setting Transcript_status
152
+ conditions = [
153
+ maf['Transcript_ID'].isna(),
154
+ maf['O3D_transcript_ID'].isna(),
155
+ maf['Transcript_ID'] != maf['O3D_transcript_ID'],
156
+ maf['Transcript_ID'] == maf['O3D_transcript_ID']
157
+ ]
158
+ choices = ['Input_missing', 'O3D_missing', 'Mismatch', 'Match']
159
+ maf['Transcript_status'] = np.select(conditions, choices, default=np.nan)
160
+
161
+ # Log transcript report
162
+ transcript_report = maf['Transcript_status'].value_counts().reset_index(name='Count')
163
+ transcript_report = ", ".join([f"{status} = {count}" for status, count in transcript_report.to_numpy()])
164
+ logger.info(f"Transcript status of {len(maf)} mutations: {transcript_report}")
165
+
166
+ return maf
167
+
168
+
169
+ def read_input(input_path):
170
+ """
171
+ Read input file optimizing memory usage.
172
+ """
173
+
174
+ cols_to_read = ["Variant_Classification",
175
+ "Tumor_Sample_Barcode",
176
+ "Feature",
177
+ "Transcript_ID",
178
+ "Consequence",
179
+ "SYMBOL",
180
+ "Hugo_Symbol",
181
+ "CANONICAL",
182
+ "HGVSp_Short",
183
+ "Amino_acids",
184
+ "Protein_position"]
185
+
186
+ header = pd.read_table(input_path, nrows=0)
187
+ cols_to_read = [col for col in cols_to_read if col in header.columns]
188
+ dtype_mapping = {col : "object" for col in cols_to_read}
189
+ dtype = {key: dtype_mapping[key] for key in cols_to_read if key in dtype_mapping}
190
+
191
+ return pd.read_table(input_path, usecols=cols_to_read, dtype=dtype)
192
+
193
+
194
+ def parse_maf_input(input_path, seq_df=None, use_o3d_transcripts=False, use_input_symbols=False, mane=False):
195
+ """
196
+ Parsing and process MAF input data.
197
+ """
198
+
199
+ # Load, parse from VEP and update seq_df if needed
200
+ logger.info(f"Reading input mutations file..")
201
+ maf = read_input(input_path)
202
+ logger.debug(f"Processing [{len(maf)}] total mutations..")
203
+ maf, seq_df = parse_vep_output(maf, seq_df, use_o3d_transcripts, use_input_symbols, mane)
204
+
205
+ # Extract and parse missense mutations
206
+ maf = maf[maf['Variant_Classification'].str.contains('Missense_Mutation|missense_variant')]
207
+ if "Protein_position" in maf.columns:
208
+ maf = maf[~maf['Protein_position'].astype(str).str.contains('-')] # Filter DBS
209
+ logger.debug(f"Processing [{len(maf)}] missense mutations..")
210
+ maf = parse_mutations(maf)
211
+
212
+ # Add transcript status from seq_df
213
+ if seq_df is not None:
214
+ maf = add_transcript_info(maf, seq_df)
215
+
216
+ return maf.reset_index(drop=True), seq_df
217
+
218
+
219
+ ## Other utils
220
+
221
+ def get_gene_entry(data, genes, entry):
222
+
223
+ return [data.loc[data["Gene"] == gene, entry].values[0] for gene in genes]
224
+
225
+
226
+ def weighted_avg_plddt_vol(target_pos, mut_plddt_df, cmap):
227
+ """
228
+ Get the weighted average pLDDT score across residues in
229
+ the volume, based on number of mutations hitting each residue.
230
+ """
231
+
232
+ return mut_plddt_df[[pos in np.where(cmap[target_pos-1])[0]+1 for pos in mut_plddt_df.Pos]].Confidence.mean()
233
+
234
+
235
+ def weighted_avg_pae_vol(target_pos, mut_plddt_df, cmap, pae):
236
+ """
237
+ Get the weighted average PAE across residues in the volume,
238
+ based on number of mutations hitting each residue.
239
+ """
240
+
241
+ contacts = mut_plddt_df[[pos in np.where(cmap[target_pos-1])[0]+1 for pos in mut_plddt_df.Pos]].Pos.values
242
+ pae_vol = pae[np.repeat(target_pos-1, len(contacts)), contacts-1].mean()
243
+
244
+ return pae_vol
245
+
246
+
247
+ def get_samples_info(mut_gene_df, cmap):
248
+ """
249
+ Get total samples and ratio of unique samples having
250
+ mutations in the volume of each mutated residues.
251
+ """
252
+
253
+ # Get total samples and # mutated samples of each mutated res
254
+ tot_samples = len(mut_gene_df["Tumor_Sample_Barcode"].unique())
255
+ pos_barcodes = mut_gene_df.groupby("Pos").apply(lambda x: x["Tumor_Sample_Barcode"].unique())
256
+ pos_barcodes = pos_barcodes.reset_index().rename(columns={0 : "Barcode"})
257
+
258
+ # Get the ratio of unique samples with mut in the vol of each mutated res
259
+ uniq_pos_barcodes = [len(pos_barcodes[[pos in np.where(cmap[i-1])[0]+1 for
260
+ pos in pos_barcodes.Pos]].Barcode.explode().unique()) for i in pos_barcodes.Pos]
261
+ pos_barcodes["Tot_samples"] = tot_samples
262
+ pos_barcodes["Samples_in_vol"] = uniq_pos_barcodes
263
+ #pos_barcodes["Ratio_samples_in_vol"] = np.array(uniq_pos_barcodes) / tot_samples
264
+
265
+ return pos_barcodes
266
+
267
+
268
+ def get_unique_pos_in_contact(lst_pos, cmap):
269
+ """
270
+ Given a list of position and a contact map, return a numpy
271
+ array of unique positions in contact with the given ones.
272
+ """
273
+
274
+ return np.unique(np.concatenate([np.where(cmap[pos-1])[0]+1 for pos in lst_pos]))
275
+
276
+
277
+ def add_info(mut_gene_df, result_pos_df, cmap, pae=None, sample_info=False):
278
+ """
279
+ Add information about the ratio of unique samples in the volume of
280
+ each mutated residues and in each detected community (meta-cluster)
281
+ to the residues-level output of the tool.
282
+ """
283
+
284
+ # Add sample info
285
+ if sample_info:
286
+ if "Tumor_Sample_Barcode" in mut_gene_df.columns:
287
+ samples_info = get_samples_info(mut_gene_df, cmap)
288
+ result_pos_df = result_pos_df.merge(samples_info.drop(columns=["Barcode"]), on="Pos", how="outer")
289
+ else:
290
+ result_pos_df["Tot_samples"] = np.nan
291
+ result_pos_df["Samples_in_vol"] = np.nan
292
+
293
+ # Get per-community info
294
+ if result_pos_df["Clump"].isna().all():
295
+ if sample_info:
296
+ result_pos_df["Samples_in_cl_vol"] = np.nan
297
+ result_pos_df["Mut_in_cl_vol"] = np.nan
298
+ result_pos_df["Res_in_cl"] = np.nan
299
+ result_pos_df["pLDDT_cl_vol"] = np.nan
300
+ else:
301
+ community_pos = result_pos_df.groupby("Clump").apply(lambda x: x.Pos.values)
302
+ community_mut = community_pos.apply(lambda x: sum([pos in get_unique_pos_in_contact(x, cmap) for
303
+ pos in mut_gene_df.Pos]))
304
+ community_plddt = community_pos.apply(lambda x: mut_gene_df.Confidence[[pos in get_unique_pos_in_contact(x, cmap)
305
+ for pos in mut_gene_df.Pos]].mean())
306
+ community_pos_count = community_pos.apply(lambda x: len(x))
307
+
308
+ community_info = pd.DataFrame({"Mut_in_cl_vol" : community_mut,
309
+ "Res_in_cl" : community_pos_count,
310
+ "pLDDT_cl_vol" : np.round(community_plddt, 2)})
311
+
312
+ if sample_info:
313
+ if "Tumor_Sample_Barcode" in mut_gene_df.columns:
314
+ community_samples = community_pos.apply(lambda x:
315
+ len(mut_gene_df[[pos in get_unique_pos_in_contact(x, cmap) for
316
+ pos in mut_gene_df.Pos]].Tumor_Sample_Barcode.unique()))
317
+ else:
318
+ community_samples = np.nan
319
+ community_info["Samples_in_cl_vol"] = community_samples
320
+
321
+ # Add to residues-level result
322
+ result_pos_df = result_pos_df.merge(community_info, on="Clump", how="outer")
323
+
324
+ # AF PAE
325
+ if pae is not None:
326
+ result_pos_df["PAE_vol"] = np.round(result_pos_df.apply(lambda x: weighted_avg_pae_vol(x["Pos"], mut_gene_df, cmap, pae), axis=1), 2)
327
+ else:
328
+ result_pos_df["PAE_vol"] = np.nan
329
+
330
+ # AF confidence
331
+ result_pos_df["pLDDT_res"] = result_pos_df.apply(lambda x: mut_gene_df.Confidence[mut_gene_df["Pos"] == x.Pos].values[0]
332
+ if len(mut_gene_df.Confidence[mut_gene_df["Pos"] == x.Pos].values > 0) else np.nan, axis=1)
333
+ result_pos_df["pLDDT_vol"] = np.round(result_pos_df.apply(lambda x: weighted_avg_plddt_vol(x["Pos"], mut_gene_df, cmap), axis=1), 2)
334
+ result_pos_df["pLDDT_cl_vol"] = result_pos_df.pop("pLDDT_cl_vol")
335
+
336
+ # WT AA mismatches
337
+ if "WT_mismatch" in mut_gene_df:
338
+ result_pos_df["WT_mismatch"] = result_pos_df.apply(lambda x: sum(mut_gene_df[mut_gene_df["Pos"] == x.Pos].WT_mismatch.dropna()), axis=1)
339
+
340
+ # Sort positions
341
+ result_pos_df = result_pos_df.sort_values("Rank").reset_index(drop=True)
342
+
343
+ return result_pos_df
344
+
345
+
346
+ def add_nan_clust_cols(result_gene, sample_info=False):
347
+ """
348
+ Add columns showing clustering results with only NA for
349
+ genes that are not tested (not enough mutations, etc).
350
+ """
351
+
352
+ result_gene = result_gene.copy()
353
+
354
+ columns = ["pval",
355
+ "qval",
356
+ "C_gene",
357
+ "C_pos",
358
+ 'C_label',
359
+ 'Score_obs_sim_top_vol',
360
+ "Clust_res",
361
+ 'Clust_mut',
362
+ 'Pos_top_vol',
363
+ 'Mut_in_top_vol',
364
+ "Mut_in_top_cl_vol",
365
+ "PAE_top_vol",
366
+ "pLDDT_top_vol",
367
+ "pLDDT_top_cl_vol",
368
+ 'F']
369
+
370
+ if sample_info:
371
+ columns.extend(['Tot_samples',
372
+ 'Samples_in_top_vol',
373
+ 'Samples_in_top_cl_vol'])
374
+
375
+ for col in columns:
376
+ result_gene[col] = np.nan
377
+
378
+ return result_gene
379
+
380
+
381
+ def sort_cols(result_gene):
382
+ """
383
+ Simply change the order of columns of the genes-level result.
384
+ """
385
+
386
+ cols = ['Gene',
387
+ 'Uniprot_ID',
388
+ 'pval',
389
+ 'qval',
390
+ 'C_gene',
391
+ 'C_pos',
392
+ 'C_label',
393
+ 'Pos_top_vol',
394
+ 'Score_obs_sim_top_vol',
395
+ 'Mut_in_gene',
396
+ 'Clust_mut',
397
+ "Clust_res",
398
+ 'Mut_in_top_vol',
399
+ "Mut_in_top_cl_vol",
400
+ 'Tot_samples',
401
+ 'Samples_in_top_vol',
402
+ 'Samples_in_top_cl_vol',
403
+ "PAE_top_vol",
404
+ "pLDDT_top_vol",
405
+ "pLDDT_top_cl_vol",
406
+ 'Ratio_not_in_structure',
407
+ 'Ratio_WT_mismatch',
408
+ 'Mut_zero_mut_prob',
409
+ 'Pos_zero_mut_prob',
410
+ 'Ratio_mut_zero_prob',
411
+ 'Cancer',
412
+ 'Cohort',
413
+ 'F',
414
+ 'Transcript_ID',
415
+ 'O3D_transcript_ID',
416
+ 'Transcript_status',
417
+ # 'HGNC_ID',
418
+ # 'Refseq_prot',
419
+ # 'Ens_Gene_ID'
420
+ 'Status']
421
+
422
+ return result_gene[[col for col in cols if col in result_gene.columns]]
423
+
424
+
425
+ def empty_result_pos(sample_info=False):
426
+ """
427
+ Get an empty position-level result of the clustering method.
428
+ """
429
+
430
+ cols = ['Gene',
431
+ 'Uniprot_ID',
432
+ 'Pos',
433
+ 'Mut_in_gene',
434
+ 'Mut_in_res',
435
+ 'Mut_in_vol',
436
+ 'Score',
437
+ 'Score_obs_sim',
438
+ 'pval',
439
+ 'C',
440
+ 'C_ext',
441
+ 'Clump',
442
+ 'Rank',
443
+ 'Tot_samples',
444
+ 'Samples_in_vol',
445
+ 'Samples_in_cl_vol',
446
+ 'Mut_in_cl_vol',
447
+ 'Res_in_cl',
448
+ 'PAE_vol',
449
+ 'pLDDT_res',
450
+ 'pLDDT_vol',
451
+ 'pLDDT_cl_vol',
452
+ 'Cancer',
453
+ 'Cohort']
454
+
455
+ df = pd.DataFrame(columns=cols)
456
+ if not sample_info:
457
+ df = df.drop(columns=['Tot_samples',
458
+ 'Samples_in_vol',
459
+ 'Samples_in_cl_vol'])
460
+
461
+ return df