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,53 @@
1
+ """
2
+ Contains function to initialize a network for communities detection.
3
+ """
4
+
5
+ import networkx as nx
6
+ import numpy as np
7
+
8
+
9
+ def get_network(nodes, mut_count_v, cmap):
10
+ """
11
+ Generate a network with significative pos as nodes
12
+ and ratio of shared mutation (Jaccard score) as edges.
13
+ """
14
+
15
+ G = nx.Graph()
16
+ G.add_nodes_from(nodes)
17
+
18
+ # Iterate through each hit
19
+ for i, ipos in enumerate(nodes):
20
+ for j, jpos in enumerate(nodes):
21
+ if i > j:
22
+
23
+ # Add an edge if they are in contact
24
+ ix, jx = ipos-1, jpos-1
25
+ if cmap[ix, jx] == 1:
26
+
27
+ # Get the common res and their mut
28
+ neigh_vec_i, neigh_vec_j = cmap[ix], cmap[jx]
29
+ common_neigh = neigh_vec_j * neigh_vec_i
30
+ num_mut = np.dot(common_neigh, mut_count_v)
31
+
32
+ # Get the sum of the union of the mut
33
+ all_neigh = (neigh_vec_i + neigh_vec_j != 0).astype(int)
34
+ union_num_mut = np.dot(all_neigh, mut_count_v)
35
+
36
+ # Compute the Jaccard score or avg ratio between ij shared mut
37
+ jaccard = np.round(num_mut/union_num_mut, 3)
38
+ G.add_edge(ipos, jpos, weight = jaccard)
39
+ return G
40
+
41
+
42
+ def get_community_index_nx(pos_hits, communities):
43
+
44
+ """
45
+ Parse the labels returned by communities detection algorithms from NetworkX.
46
+ """
47
+
48
+ communities_mapper = {}
49
+ for ic, c in enumerate(communities):
50
+ for p in c:
51
+ communities_mapper[p] = ic
52
+
53
+ return pos_hits.map(communities_mapper).values
@@ -0,0 +1,206 @@
1
+ """
2
+ Contain functions to compute the per-residues probability of missense
3
+ mutation of any protein given the mutation profile of the cohort.
4
+ """
5
+
6
+
7
+ from itertools import product
8
+
9
+ import daiquiri
10
+ import numpy as np
11
+
12
+ from scripts.run.mutability import Mutabilities
13
+ from scripts import __logger_name__
14
+
15
+ logger = daiquiri.getLogger(__logger_name__ + ".run.miss_mut_prob")
16
+
17
+
18
+ def get_unif_gene_miss_prob(size):
19
+ """
20
+ Get a uniformly distributed gene missense mutation
21
+ probability vector.
22
+ """
23
+
24
+ vector = np.ones(size)
25
+ vector[0] = 0
26
+
27
+ return vector / sum(vector)
28
+
29
+
30
+ def mut_rate_vec_to_dict(mut_rate):
31
+ """
32
+ Convert the vector of mut mut_rate of 96 channels to a dictionary of 192
33
+ items: the keys are mutations in trinucleotide context (e.g., "ACA>A")
34
+ and values are the corresponding mut rate (frequency of mut normalized
35
+ for the nucleotide content).
36
+ """
37
+
38
+ cb = dict(zip('ACGT', 'TGCA'))
39
+ mut_rate_dict = {}
40
+ i = 0
41
+ for ref in ['C', 'T']:
42
+ for alt in cb.keys():
43
+ if ref == alt:
44
+ continue
45
+ else:
46
+ for p in product(cb.keys(), repeat=2):
47
+ mut = f"{p[0]}{ref}{p[1]}>{alt}"
48
+ cmut = f"{cb[p[1]]}{cb[ref]}{cb[p[0]]}>{cb[alt]}"
49
+ mut_rate_dict[mut] = mut_rate[i]
50
+ mut_rate_dict[cmut] = mut_rate[i]
51
+ i +=1
52
+
53
+ return mut_rate_dict
54
+
55
+
56
+ def get_codons(dna_seq):
57
+ """
58
+ Get the list of codons from a DNA sequence.
59
+ """
60
+
61
+ return [dna_seq[i:i+3] for i in [n*3 for n in range(int(len(dna_seq) / 3))]]
62
+
63
+
64
+ def translate_dna_to_prot(dna_seq, gencode):
65
+ """
66
+ Translate a DNA sequence into amino acid sequence.
67
+ """
68
+
69
+ return "".join([gencode[codon] for codon in get_codons(dna_seq)])
70
+
71
+
72
+ def codons_trinucleotide_context(lst_contexts):
73
+
74
+ return list(zip(lst_contexts[::3], lst_contexts[1::3], lst_contexts[2::3]))
75
+
76
+
77
+ # TODO: doc function
78
+
79
+ def get_miss_mut_prob(dna_seq,
80
+ dna_tricontext,
81
+ mut_rate_dict,
82
+ mutability=False,
83
+ get_probability=True,
84
+ mut_start_codon=False):
85
+ """
86
+ Generate a list including the probabilities that the
87
+ codons can mutate resulting into a missense mutations.
88
+
89
+ Arguments
90
+ ---------
91
+ dna_seq: str
92
+ Sequence of DNA
93
+ mut_rate_dict: dict
94
+ Mutation rate probability as values and the 96 possible
95
+ trinucleotide contexts as keys
96
+ gencode: dict
97
+ Nucleotide as values and codons as keys
98
+
99
+ Returns
100
+ -------
101
+ missense_prob_vec: list
102
+ List of probabilities (one for each codon or prot res)
103
+ of a missense mutation
104
+ """
105
+
106
+ # Initialize
107
+ gencode = {
108
+ 'ATA':'I', 'ATC':'I', 'ATT':'I', 'ATG':'M',
109
+ 'ACA':'T', 'ACC':'T', 'ACG':'T', 'ACT':'T',
110
+ 'AAC':'N', 'AAT':'N', 'AAA':'K', 'AAG':'K',
111
+ 'AGC':'S', 'AGT':'S', 'AGA':'R', 'AGG':'R',
112
+ 'CTA':'L', 'CTC':'L', 'CTG':'L', 'CTT':'L',
113
+ 'CCA':'P', 'CCC':'P', 'CCG':'P', 'CCT':'P',
114
+ 'CAC':'H', 'CAT':'H', 'CAA':'Q', 'CAG':'Q',
115
+ 'CGA':'R', 'CGC':'R', 'CGG':'R', 'CGT':'R',
116
+ 'GTA':'V', 'GTC':'V', 'GTG':'V', 'GTT':'V',
117
+ 'GCA':'A', 'GCC':'A', 'GCG':'A', 'GCT':'A',
118
+ 'GAC':'D', 'GAT':'D', 'GAA':'E', 'GAG':'E',
119
+ 'GGA':'G', 'GGC':'G', 'GGG':'G', 'GGT':'G',
120
+ 'TCA':'S', 'TCC':'S', 'TCG':'S', 'TCT':'S',
121
+ 'TTC':'F', 'TTT':'F', 'TTA':'L', 'TTG':'L',
122
+ 'TAC':'Y', 'TAT':'Y', 'TAA':'_', 'TAG':'_',
123
+ 'TGC':'C', 'TGT':'C', 'TGA':'_', 'TGG':'W'}
124
+
125
+ # Get all codons of the seq
126
+ #logger.debug("Getting codons of seq..")
127
+ codons = get_codons(dna_seq)
128
+ missense_prob_vec = []
129
+
130
+ # Get the trinucleotide context as list of tuples of 3 elements corresponding to each codon
131
+ #logger.debug("Getting tri context seq..")
132
+ tricontext = codons_trinucleotide_context(dna_tricontext.split(","))
133
+
134
+ # Iterate through codons and get prob of missense based on context
135
+ for c in range(len(codons)):
136
+ missense_prob = 0
137
+ codon = codons[c]
138
+ aa = gencode[codon]
139
+ trinucl0, trinucl1, trinucl2 = tricontext[c]
140
+
141
+ # Iterate through the possible contexts of a missense mut
142
+ for i, trinucl in enumerate([trinucl0, trinucl1, trinucl2]):
143
+ ref = trinucl[1]
144
+ aa = gencode[codon]
145
+
146
+ # Iterate through the possible alt
147
+ for alt in "ACGT":
148
+ if alt != ref:
149
+ alt_codon = [n for n in codon]
150
+ alt_codon[i] = alt
151
+ alt_codon = "".join(alt_codon)
152
+ alt_aa = gencode[alt_codon]
153
+ # If there is a missense mut, get prob from context and sum it
154
+ if alt_aa != aa and alt_aa != "_":
155
+ if not mutability:
156
+ mut = f"{trinucl}>{alt}" # query using only the trinucleotide change
157
+ mut_prob = mut_rate_dict[mut] if mut in mut_rate_dict else 0
158
+ missense_prob += mut_prob
159
+
160
+ else:
161
+ # TODO this has not been tested
162
+ cdna_pos = (c * 3) + i # compute the cDNA position of the residue
163
+ if cdna_pos in mut_rate_dict:
164
+ missense_prob += mut_rate_dict[cdna_pos].get(alt, 0)
165
+ else:
166
+ missense_prob += 0
167
+
168
+ missense_prob_vec.append(missense_prob)
169
+
170
+ # Assign 0 prob to the first residue
171
+ if mut_start_codon == False:
172
+ missense_prob_vec[0] = 0
173
+
174
+ # Convert into probabilities
175
+ if get_probability:
176
+ missense_prob_vec = np.array(missense_prob_vec) / sum(missense_prob_vec)
177
+
178
+ return list(missense_prob_vec)
179
+
180
+
181
+ def get_miss_mut_prob_dict(mut_rate_dict, seq_df, mutability=False, mutability_config=None):
182
+ """
183
+ Given a dictionary of mut rate in 96 contexts (mut profile) and a
184
+ dataframe including Uniprot ID, HUGO symbol and DNA sequences,
185
+ get a dictionary with UniprotID-Fragment as keys and corresponding
186
+ vectors of missense mutation probabilities as values.
187
+ """
188
+
189
+ miss_prob_dict = {}
190
+
191
+ if mutability:
192
+ # TODO if the execution time of this step is too long we could
193
+ # parallelize all these loops so that each gene is done in parallel
194
+
195
+ # Process any Protein/fragment in the sequence df
196
+ for _, row in seq_df.iterrows():
197
+ # Mutabilities
198
+ mutability_dict = Mutabilities(row.Uniprot_ID, row.Chr, row.Exons_coord, len(row.Seq_dna), row.Reverse_strand, mutability_config).mutabilities_by_pos
199
+ miss_prob_dict[f"{row.Uniprot_ID}-F{row.F}"] = get_miss_mut_prob(row.Seq_dna, row.Tri_context, mutability_dict, mutability=True)
200
+
201
+ else:
202
+ # Process any Protein/fragment in the sequence df
203
+ for _, row in seq_df.iterrows():
204
+ miss_prob_dict[f"{row.Uniprot_ID}-F{row.F}"] = get_miss_mut_prob(row.Seq_dna, row.Tri_context, mut_rate_dict)
205
+
206
+ return miss_prob_dict
@@ -0,0 +1,289 @@
1
+ """
2
+ This module contains the methods associated with the
3
+ mutabilities that are assigned to the mutations.
4
+
5
+ The mutabilities are read from a file.
6
+ The file must be compressed using bgzip, and then indexed using tabix.
7
+ $ bgzip ..../all_samples.mutability_per_site.tsv
8
+ $ tabix -b 2 -e 2 ..../all_samples.mutability_per_site.tsv.gz
9
+ """
10
+
11
+ import logging
12
+ from collections import defaultdict, namedtuple
13
+ from typing import List
14
+
15
+ import tabix
16
+
17
+ # TODO uncomment these lines to make sure that the logger and everything is working
18
+
19
+ from scripts import __logger_name__
20
+ # from oncodrivefml.reference import get_ref_triplet
21
+
22
+ logger = logging.getLogger(__logger_name__ + ".run.mutability")
23
+
24
+ transcribe = {"A":"T", "C":"G", "G":"C", "T":"A"}
25
+
26
+ MutabilityValue = namedtuple('MutabilityValue', ['ref', 'alt', 'value'])
27
+ """
28
+ Tuple that contains the reference, the alteration, the mutability value
29
+
30
+ Parameters:
31
+ ref (str): reference base
32
+ alt (str): altered base
33
+ value (float): mutability value of that substitution
34
+ """
35
+
36
+ mutabilities_reader = None
37
+
38
+
39
+ class ReaderError(Exception):
40
+
41
+ def __init__(self, msg):
42
+ self.message = msg
43
+
44
+
45
+ class ReaderGetError(ReaderError):
46
+ def __init__(self, chr, start, end):
47
+ self.message = 'Error reading chr: {} start: {} end: {}'.format(chr, start, end)
48
+
49
+ class MutabilityTabixReader:
50
+
51
+ def __init__(self, conf):
52
+ self.file = conf['file']
53
+ self.conf_chr_prefix = conf['chr_prefix']
54
+ self.ref_pos = conf['ref']
55
+ self.alt_pos = conf['alt']
56
+ self.pos_pos = conf['pos']
57
+ self.mutability_pos = conf['mutab']
58
+ self.element_pos = None
59
+
60
+ def __enter__(self):
61
+ self.tb = tabix.open(self.file)
62
+ self.index_errors = 0
63
+ self.elements_errors = 0
64
+ return self
65
+
66
+ def __exit__(self, exc_type, exc_val, exc_tb):
67
+ if self.index_errors > 0 or self.elements_errors > 0:
68
+ raise ReaderError('{} index errors and {} discrepancies between the expected and retrieved element'.format(self.index_errors, self.elements_errors))
69
+ return True
70
+
71
+ def _read_row(self, row):
72
+ mutability = float(row[self.mutability_pos])
73
+ ref = None if self.ref_pos is None else row[self.ref_pos]
74
+ alt = None if self.alt_pos is None else row[self.alt_pos]
75
+ pos = None if self.pos_pos is None else int(row[self.pos_pos])
76
+ element = None if self.element_pos is None else row[self.element_pos]
77
+ return (mutability, ref, alt, pos), element
78
+
79
+ def get(self, chromosome, start, stop, element=None):
80
+ try:
81
+ for row in self.tb.query("{}{}".format(self.conf_chr_prefix, chromosome), start, stop):
82
+ try:
83
+ r = self._read_row(row)
84
+ except IndexError:
85
+ self.index_errors += 1
86
+ continue
87
+ else:
88
+ if self.element_pos is not None and element is not None and r[1] != element:
89
+ self.elements_errors += 1
90
+ continue
91
+ yield r[0]
92
+ except tabix.TabixError:
93
+ raise ReaderGetError(chromosome, start, stop)
94
+
95
+
96
+ def init_mutabilities_module(conf):
97
+ global mutabilities_reader
98
+ mutabilities_reader = MutabilityTabixReader(conf)
99
+
100
+
101
+ class Mutabilities(object):
102
+ """
103
+
104
+ Args:
105
+ element (str): element ID
106
+ segments (list): list of the segments associated to the element
107
+ config (dict): configuration
108
+
109
+ Attributes:
110
+ mutabilities_by_pos (dict): for each positions get all possible changes
111
+
112
+ .. code-block:: python
113
+
114
+ { position:
115
+ [
116
+ MutabilityValue(
117
+ ref,
118
+ alt_1,
119
+ value
120
+ ),
121
+ MutabilityValue(
122
+ ref,
123
+ alt_2,
124
+ value
125
+ ),
126
+ MutabilityValue(
127
+ ref,
128
+ alt_3,
129
+ value
130
+ )
131
+ ]
132
+ }
133
+ """
134
+
135
+ def __init__(self, element: str, chromosome:str, segments: list, gene_len: int, gene_reverse_strand: bool, config: dict):
136
+
137
+
138
+ self.element = element
139
+ self.chromosome = chromosome
140
+ self.segments = segments
141
+ self.gene_length = gene_len
142
+ self.reverse = True if float(gene_reverse_strand) == 1.0 else False
143
+
144
+ # mutability configuration
145
+ self.conf_file = config['file']
146
+ self.conf_chr = config['chr']
147
+ self.conf_chr_prefix = config['chr_prefix']
148
+ self.conf_ref = config['ref']
149
+ self.conf_alt = config['alt']
150
+ self.conf_pos = config['pos']
151
+ self.conf_element = config['element'] if 'element' in config.keys() else None
152
+ self.conf_extra = config['extra'] if 'extra' in config.keys() else None
153
+
154
+ # mutabilities to load
155
+ self.mutabilities_by_pos = defaultdict(dict)
156
+
157
+
158
+ # Initialize background mutabilities
159
+ self._load_mutabilities()
160
+
161
+ def get_mutability_by_position(self, position: int):
162
+ """
163
+ Get all MutabilityValue objects that are associated with that position
164
+
165
+ Args:
166
+ position (int): position
167
+
168
+ Returns:
169
+ :obj:`list` of :obj:`MutabilityValue`: list of all MutabilityValue related to that position
170
+
171
+ """
172
+ return self.mutabilities_by_pos.get(position, [])
173
+
174
+ def get_all_positions(self) -> List[int]:
175
+ """
176
+ Get all positions in the element
177
+
178
+ Returns:
179
+ :obj:`list` of :obj:`int`: list of positions
180
+
181
+ """
182
+ return self.mutabilities_by_pos.keys()
183
+
184
+ def _load_mutabilities(self):
185
+ """
186
+ For each position get all possible substitutions and for each
187
+ obtains the assigned mutability
188
+
189
+ Returns:
190
+ dict: for each positions get a list of MutabilityValue
191
+ (see :attr:`mutabilities_by_pos`)
192
+ """
193
+ cdna_pos = 0
194
+ starting_cdna_pos = 0
195
+ start = 0 if not self.reverse else 1
196
+ end = 1 if not self.reverse else 0
197
+ update_pos = 1 if not self.reverse else -1
198
+ try:
199
+ with mutabilities_reader as reader:
200
+ for region in self.segments:
201
+ # each region corresponds to an exon
202
+ try:
203
+ segment_len = region[end] - region[start] + 1
204
+ cdna_pos = starting_cdna_pos if not self.reverse else starting_cdna_pos + segment_len
205
+ starting_cdna_pos = int(cdna_pos)
206
+ prev_pos = region[start] - 1
207
+ # print(self.chromosome, region[start], region[end], self.element, segment_len, cdna_pos, prev_pos, starting_cdna_pos, update_pos)
208
+ for row in reader.get(self.chromosome, region[start], region[end], self.element):
209
+ # every row is a site
210
+
211
+ mutability, ref, alt, pos = row
212
+ # print(mutability, ref, alt, pos, sep = "\t")
213
+
214
+ # TODO decide if we want to do this check or not,
215
+ # I would say it is fine as it is now without the check
216
+
217
+ # ref_triplet = get_ref_triplet(region['CHROMOSOME'], pos - 1)
218
+ # ref = ref_triplet[1] if ref is None else ref
219
+ # if ref_triplet[1] != ref:
220
+ # logger.warning("Background mismatch at position %d at '%s'", pos, self.element)
221
+
222
+
223
+ # if the current position is different from the previous
224
+ # update the cdna position accordingly to the strand
225
+ # and also update the value of prev_pos
226
+ if pos != prev_pos:
227
+ cdna_pos += update_pos
228
+ # print("changing position", pos, prev_pos, cdna_pos)
229
+
230
+ # if it is not the first position of an exon and
231
+ # the current position is not the one right after/before the previous position,
232
+ # it means that the mutability for a given position(s) is missing
233
+ # then
234
+ # add a dictionary with all the alts and probability equals to 0,
235
+ # if there are more mutabilities of the consecutive positions missing, keep adding 0s
236
+
237
+ if pos != region[start]:
238
+ expected_previous_pos = pos - 1
239
+ # print(pos, prev_pos, expected_previous_pos)
240
+ while prev_pos != expected_previous_pos:
241
+ # print(pos, region[start], region[end], prev_pos, expected_previous_pos, cdna_pos)
242
+ for altt in "ACGT":
243
+ self.mutabilities_by_pos[cdna_pos][altt] = 0
244
+ cdna_pos += update_pos
245
+ expected_previous_pos -= 1
246
+
247
+ prev_pos = pos
248
+
249
+ # since at protein level we are looking at the nucleotide
250
+ # changes of the translated codons we store them as they will be queried later
251
+ if self.reverse:
252
+ alt = transcribe[alt]
253
+
254
+ # add the mutability
255
+ self.mutabilities_by_pos[cdna_pos][alt] = mutability
256
+ # print(mutability, ref, alt, pos, cdna_pos, sep = "\t")
257
+
258
+ # segment_starting_pos = cdna_pos if not self.reverse else cdna_pos + segment_len
259
+ # print(self.chromosome, region[start], region[end], self.element, segment_len, cdna_pos, prev_pos, starting_cdna_pos, update_pos)
260
+
261
+ ##
262
+ # IMPORTANT: filling step
263
+ ##
264
+ # check that all the positions at the end have been filled
265
+ # otherwise add 0s to them
266
+ if not self.reverse:
267
+ while cdna_pos < (starting_cdna_pos + segment_len):
268
+ for altt in "ACGT":
269
+ self.mutabilities_by_pos[cdna_pos][altt] = 0
270
+ cdna_pos += 1
271
+ else:
272
+ while cdna_pos > (starting_cdna_pos - segment_len):
273
+ for altt in "ACGT":
274
+ self.mutabilities_by_pos[cdna_pos][altt] = 0
275
+ cdna_pos -= 1
276
+
277
+ # print(self.chromosome, region[start], region[end], self.element, segment_len, cdna_pos, prev_pos, starting_cdna_pos, update_pos)
278
+ # print("\n")
279
+
280
+ # this is to get the cdna position pointer
281
+ # back to the biggest cdna position annotated so far
282
+ starting_cdna_pos = cdna_pos if not self.reverse else cdna_pos + segment_len
283
+
284
+
285
+ except ReaderError as e:
286
+ logger.warning(e.message)
287
+ continue
288
+ except ReaderError as e:
289
+ logger.warning("Reader error: %s. Regions being analysed %s", e.message, self.segments)
scripts/run/pvalues.py ADDED
@@ -0,0 +1,91 @@
1
+ """
2
+ Contains function to process the experimental p-values.
3
+ """
4
+
5
+ import pandas as pd
6
+ import numpy as np
7
+ from statsmodels.stats.multitest import multipletests
8
+
9
+
10
+ def fdr(p_vals, alpha=0.05):
11
+ """
12
+ Compute false discovery rate using Benjamini-Hochberg method.
13
+ """
14
+
15
+ return multipletests(p_vals, alpha=alpha, method='fdr_bh', is_sorted=True)[1]
16
+
17
+
18
+ def get_top_vol_info(gene_result_pos):
19
+ """
20
+ Get score, mutations count and other info of the top volume
21
+ (most significant one) across the mutated ones in each gene.
22
+ """
23
+
24
+ if len(gene_result_pos) > 1:
25
+ lowest_pval = gene_result_pos[gene_result_pos['pval'] == gene_result_pos['pval'].min()]
26
+ if len(lowest_pval) > 1:
27
+ top_vol = lowest_pval[lowest_pval['Score_obs_sim'] == lowest_pval['Score_obs_sim'].max()].iloc[0]
28
+ else:
29
+ top_vol = lowest_pval.iloc[0]
30
+ else:
31
+ top_vol = gene_result_pos.iloc[0]
32
+
33
+ pos_top_vol = top_vol.Pos
34
+ mut_in_top_vol = np.round(top_vol.Mut_in_vol, 2)
35
+ mut_in_top_cl_vol = np.round(top_vol.Mut_in_cl_vol, 2)
36
+ score_obs_sim_top_vol = top_vol.Score_obs_sim
37
+ pae_top_vol = np.round(top_vol.PAE_vol, 2)
38
+ plddt_top_vol = top_vol.pLDDT_vol
39
+ pLDDT_top_cl_vol = np.round(top_vol.pLDDT_cl_vol, 2)
40
+
41
+ return pos_top_vol, mut_in_top_vol, mut_in_top_cl_vol, score_obs_sim_top_vol, pae_top_vol, plddt_top_vol, pLDDT_top_cl_vol
42
+
43
+
44
+ def get_final_gene_result(result_pos, result_gene, alpha_gene=0.05, sample_info=False):
45
+ """
46
+ Output the final dataframe including gene global pval, qval,
47
+ significant positions, clumps, processing status, etc.
48
+ """
49
+
50
+ pos_hits = result_pos[result_pos["C"] == 1]
51
+
52
+ if len(pos_hits) > 0:
53
+ # Get significant positions and communities for each gene
54
+ clumps = pos_hits.groupby("Gene").apply(lambda x: (x["Pos"].values)).reset_index().rename(columns={0 : "C_pos"})
55
+ clumps["C_label"] = pos_hits.groupby("Gene").apply(lambda x: x["Clump"].values).reset_index(drop=True)
56
+ # Annotate each gene with significant hits
57
+ result_gene = clumps.merge(result_gene, on="Gene", how="outer")
58
+ else:
59
+ result_gene["C_pos"] = np.nan
60
+ result_gene["C_label"] = np.nan
61
+
62
+ # Gene pval
63
+ gene_pvals = result_pos.groupby("Gene").apply(lambda x: min(x["pval"].values)).reset_index().rename(columns={0 : "pval"})
64
+
65
+ # Top volume info
66
+ gene_top_vol_info = result_pos.groupby("Gene").apply(lambda x: get_top_vol_info(x)).apply(pd.Series)
67
+ gene_top_vol_info.columns = ["Pos_top_vol",
68
+ "Mut_in_top_vol",
69
+ "Mut_in_top_cl_vol",
70
+ "Score_obs_sim_top_vol",
71
+ "PAE_top_vol",
72
+ "pLDDT_top_vol",
73
+ "pLDDT_top_cl_vol"]
74
+ gene_top_vol_info = gene_top_vol_info.reset_index()
75
+ gene_pvals = gene_pvals.merge(gene_top_vol_info, on="Gene")
76
+
77
+ # Sort positions and get qval
78
+ gene_pvals = gene_pvals.sort_values(["pval", "Score_obs_sim_top_vol"], ascending=[True, False]).reset_index(drop=True)
79
+ not_processed_genes_count = sum(~result_gene.Status.str.contains("Processed", na=False))
80
+ gene_pvals["qval"] = fdr(np.concatenate((gene_pvals["pval"], np.repeat(1, not_processed_genes_count))))[:len(gene_pvals)]
81
+
82
+ # Combine gene-level clustering result, add label, sort genes, add fragment info
83
+ result_gene = gene_pvals.merge(result_gene, on="Gene", how="outer")
84
+ result_gene["C_gene"] = result_gene.apply(lambda x: 1 if x.qval < alpha_gene else 0, axis=1)
85
+ # result_gene = result_gene.sort_values(["pval", "Score_obs_sim_top_vol"], ascending=[True, False])
86
+
87
+ # Convert C_pos and C_label to str
88
+ result_gene["C_pos"] = result_gene["C_pos"].apply(lambda x: str(x) if isinstance(x, list) else x)
89
+ result_gene["C_label"] = result_gene["C_label"].apply(lambda x: str(x) if isinstance(x, list) else x)
90
+
91
+ return result_gene