ModDotPlot 0.9.0__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,537 @@
1
+ #!/usr/bin/env python3
2
+ import math
3
+ import numpy as np
4
+ from moddotplot.const import (
5
+ SEQUENTIAL_PALETTES,
6
+ DIVERGING_PALETTES,
7
+ QUALITATIVE_PALETTES,
8
+ )
9
+ from palettable import colorbrewer
10
+ from typing import List, Set, Dict, Tuple
11
+ import mmh3
12
+
13
+ from moddotplot.parse_fasta import printProgressBar
14
+
15
+
16
+ def removeAmbiguousBases(mod_list, k):
17
+ # Ambiguous IUPAC codes
18
+ bases_to_remove = ["R", "Y", "M", "K", "S", "W", "H", "B", "V", "D", "N"]
19
+ kmers_to_remove = set()
20
+ for i in range(len(bases_to_remove)):
21
+ result_string = str(bases_to_remove[i]) * k
22
+ kmers_to_remove.add(mmh3.hash(result_string))
23
+ mod_set = set(mod_list)
24
+ # Remove homopolymers of ambiguous nucleotides
25
+ mod_set.difference_update(kmers_to_remove)
26
+ return mod_set
27
+
28
+
29
+ def createSelfMatrix(
30
+ sequence_length,
31
+ sequence,
32
+ window_size,
33
+ sparsity,
34
+ delta,
35
+ k,
36
+ identity,
37
+ ambiguous,
38
+ sketch_size,
39
+ ):
40
+ no_neighbors = partitionOverlaps(sequence, window_size, 0, sequence_length, k)
41
+ if delta > 0:
42
+ neighbors = partitionOverlaps(sequence, window_size, delta, sequence_length, k)
43
+ else:
44
+ neighbors = no_neighbors
45
+
46
+ neighbors_mods = convertToModimizers(neighbors, sparsity, ambiguous, k, sketch_size)
47
+ no_neighbors_mods = convertToModimizers(
48
+ no_neighbors, sparsity, ambiguous, k, sketch_size
49
+ )
50
+ matrix = selfContainmentMatrix(
51
+ no_neighbors_mods, neighbors_mods, k, identity, ambiguous
52
+ )
53
+ return matrix
54
+
55
+
56
+ def createPairwiseMatrix(
57
+ larger_length,
58
+ smaller_length,
59
+ larger_seq,
60
+ smaller_seq,
61
+ window_size,
62
+ sparsity,
63
+ delta,
64
+ k,
65
+ identity,
66
+ ambiguous,
67
+ expectation,
68
+ ):
69
+ no_neighbors_large = partitionOverlaps(larger_seq, window_size, 0, larger_length, k)
70
+ no_neighbors_small = partitionOverlaps(
71
+ smaller_seq, window_size, 0, smaller_length, k
72
+ )
73
+ if delta > 0:
74
+ neighbors_large = partitionOverlaps(
75
+ larger_seq, window_size, delta, larger_length, k
76
+ )
77
+ neighbors_small = partitionOverlaps(
78
+ smaller_seq, window_size, delta, smaller_length, k
79
+ )
80
+ else:
81
+ neighbors_large = no_neighbors_large
82
+ neighbors_small = no_neighbors_small
83
+
84
+ neighbors_mods_large = convertToModimizers(
85
+ neighbors_large, sparsity, ambiguous, k, expectation
86
+ )
87
+ no_neighbors_mods_large = convertToModimizers(
88
+ no_neighbors_large, sparsity, ambiguous, k, expectation
89
+ )
90
+ neighbors_mods_small = convertToModimizers(
91
+ neighbors_small, sparsity, ambiguous, k, expectation
92
+ )
93
+ no_neighbors_mods_small = convertToModimizers(
94
+ no_neighbors_small, sparsity, ambiguous, k, expectation
95
+ )
96
+ matrix = pairwiseContainmentMatrix(
97
+ no_neighbors_mods_large,
98
+ no_neighbors_mods_small,
99
+ neighbors_mods_large,
100
+ neighbors_mods_small,
101
+ identity,
102
+ k,
103
+ False,
104
+ )
105
+ return matrix
106
+
107
+
108
+ def partitionOverlaps(
109
+ lst: List[int], win: int, delta: float, seq_len: int, k: int
110
+ ) -> List[List[int]]:
111
+ kmer_list = []
112
+ kmer_to_genomic_coordinate_offset = win - k + 1
113
+ delta_offset = win * delta
114
+
115
+ # Set the first window to contain win - k + 1 kmers.
116
+ starting_end_index = int(round(kmer_to_genomic_coordinate_offset + delta_offset))
117
+ kmer_list.append(lst[0:starting_end_index])
118
+ counter = win - k + 1
119
+
120
+ # Set normal windows
121
+ while counter <= (seq_len - win):
122
+ start_index = counter + 1
123
+ end_index = win + counter + 1
124
+ delta_start_index = int(round(start_index - delta_offset))
125
+ delta_end_index = int(round(end_index + delta_offset))
126
+ if delta_end_index > seq_len:
127
+ delta_end_index = seq_len
128
+ try:
129
+ kmer_list.append(lst[delta_start_index:delta_end_index])
130
+ except Exception as e:
131
+ print("test")
132
+ print(e)
133
+ kmer_list.append(lst[delta_start_index:seq_len])
134
+ counter += win
135
+
136
+ # Set the last window to get the remainder
137
+ if counter <= seq_len - 2:
138
+ final_start_index = int(round(counter + 1 - delta_offset))
139
+ kmer_list.append(lst[final_start_index:seq_len])
140
+
141
+ # Test that last value was added on correctly
142
+
143
+ assert kmer_list[-1][-1] == lst[-1]
144
+ return kmer_list
145
+
146
+
147
+ def populateModimizers(partition, sparsity, ambiguous, expectation, k):
148
+ mod_set = set()
149
+ for kmer in partition:
150
+ if kmer % sparsity == 0:
151
+ mod_set.add(kmer)
152
+ if not ambiguous:
153
+ mod_set = removeAmbiguousBases(mod_set, k)
154
+ if (len(mod_set) < round(expectation / 2)) and (sparsity > 1):
155
+ populateModimizers(partition, sparsity / 2, ambiguous, expectation, k)
156
+ return mod_set
157
+
158
+
159
+ def convertToModimizers(
160
+ kmer_list: List[List[int]], sparsity: int, ambiguous: bool, k: int, expectation: int
161
+ ) -> List[Set[int]]:
162
+ mod_total = []
163
+ for partition in kmer_list:
164
+ mod_set = populateModimizers(partition, sparsity, ambiguous, expectation, k)
165
+ mod_total.append(mod_set)
166
+ return mod_total
167
+
168
+
169
+ def convertMatrixToBed(
170
+ matrix, window_size, id_threshold, x_name, y_name, self_identity
171
+ ):
172
+ bed = [
173
+ (
174
+ "#query_name",
175
+ "query_start",
176
+ "query_end",
177
+ "reference_name",
178
+ "reference_start",
179
+ "reference_end",
180
+ "perID_by_events",
181
+ )
182
+ ]
183
+
184
+ rows, cols = matrix.shape
185
+ for x in range(rows):
186
+ for y in range(cols):
187
+ value = matrix[x, y]
188
+ if (not self_identity) or (self_identity and x <= y):
189
+ if value >= id_threshold / 100:
190
+ start_x = x * window_size + 1
191
+ end_x = (x + 1) * window_size
192
+ start_y = y * window_size + 1
193
+ end_y = (y + 1) * window_size
194
+
195
+ bed.append(
196
+ (
197
+ x_name,
198
+ int(start_x),
199
+ int(end_x),
200
+ y_name,
201
+ int(start_y),
202
+ int(end_y),
203
+ float(value),
204
+ )
205
+ )
206
+ return bed
207
+
208
+
209
+ def divide_into_chunks(lst: List[int], res: int) -> List[List[int]]:
210
+ """
211
+ Divide a list into approximately equal-sized chunks.
212
+
213
+ Args:
214
+ lst (List[int]): The input list to be divided.
215
+ res (int): The desired number of result chunks.
216
+
217
+ Returns:
218
+ List[List[int]]: A list of lists, where each inner list contains elements from the input list.
219
+ """
220
+ chunk_size = len(lst) // res # Calculate the target chunk size
221
+ remainder = len(lst) % res # Calculate the remainder
222
+
223
+ chunks = []
224
+ start = 0
225
+
226
+ for i in range(res):
227
+ end = (
228
+ start + chunk_size + (1 if i < remainder else 0)
229
+ ) # Adjust chunk size for remainder
230
+ chunks.append(lst[start:end])
231
+ start = end
232
+
233
+ return chunks
234
+
235
+
236
+ def binomial_distance(containment_value: float, kmer_value: int) -> float:
237
+ """
238
+ Calculate the binomial distance based on containment and kmer values.
239
+
240
+ Args:
241
+ containment_value (float): The containment value.
242
+ kmer_value (int): The k-mer value.
243
+
244
+ Returns:
245
+ float: The binomial distance.
246
+ """
247
+ return math.pow(containment_value, 1.0 / kmer_value)
248
+
249
+
250
+ def containment_neighbors(
251
+ set1: Set[int],
252
+ set2: Set[int],
253
+ set3: Set[int],
254
+ set4: Set[int],
255
+ identity: int,
256
+ k: int,
257
+ ) -> float:
258
+ """
259
+ Calculate the containment neighbors based on four sets and an identity threshold.
260
+
261
+ Args:
262
+ set1 (Set[int]): The first set.
263
+ set2 (Set[int]): The second set.
264
+ set3 (Set[int]): The third set.
265
+ set4 (Set[int]): The fourth set.
266
+ identity (int): The identity threshold.
267
+ k (int): Kmer value.
268
+
269
+ Returns:
270
+ float: The containment neighbors value.
271
+ """
272
+ len_a = len(set1)
273
+ len_b = len(set2)
274
+
275
+ intersection_a_b_prime = len(set1 & set4)
276
+ if len_a != 0:
277
+ containment_a_b_prime = intersection_a_b_prime / len_a
278
+ else:
279
+ # If len_a is zero, handle it by setting containment_a_b_prime to a default value
280
+ containment_a_b_prime = 0
281
+
282
+ if binomial_distance(containment_a_b_prime, k) < identity / 100:
283
+ return 0.0
284
+
285
+ else:
286
+ intersection_a_prime_b = len(set2 & set3)
287
+ if len_b != 0:
288
+ containment_a_prime_b = intersection_a_prime_b / len_b
289
+ else:
290
+ # If len_a is zero, handle it by setting containment_a_b_prime to a default value
291
+ containment_a_prime_b = 0
292
+
293
+ return max(containment_a_b_prime, containment_a_prime_b)
294
+
295
+
296
+ def selfContainmentMatrix(
297
+ mod_set: List[set],
298
+ mod_set_neighbors: List[set],
299
+ k: int,
300
+ identity: int,
301
+ ambiguous: bool,
302
+ ) -> np.ndarray:
303
+ """
304
+ Create a self-containment matrix based on containment similarity calculations.
305
+
306
+ Args:
307
+ mod_set (List[set]): A list of sets representing elements.
308
+ mod_set_neighbors (List[set]): A list of sets representing neighbors for each element.
309
+ k (int): A parameter for containment similarity calculation.
310
+
311
+ Returns:
312
+ np.ndarray: A NumPy array representing the self-containment matrix.
313
+ """
314
+ n = len(mod_set)
315
+ progress_thresholds = round(n / 77)
316
+
317
+ printProgressBar(0, n, prefix="Progress:", suffix="Complete", length=40)
318
+ containment_matrix = np.empty((n, n))
319
+
320
+ for w in range(n):
321
+ if w % progress_thresholds == 0:
322
+ printProgressBar(w, n, prefix="Progress:", suffix="Complete", length=40)
323
+ containment_matrix[w, w] = 100.0
324
+ if len(mod_set[w]) == 0 and not ambiguous:
325
+ containment_matrix[w, w] = 0
326
+
327
+ for r in range(w + 1, n):
328
+ c_hat = binomial_distance(
329
+ containment_neighbors(
330
+ mod_set[w],
331
+ mod_set[r],
332
+ mod_set_neighbors[w],
333
+ mod_set_neighbors[r],
334
+ identity,
335
+ k,
336
+ ),
337
+ k,
338
+ )
339
+ containment_matrix[r, w] = c_hat * 100.0
340
+ containment_matrix[w, r] = c_hat * 100.0
341
+
342
+ printProgressBar(
343
+ n, n, prefix="Progress:", suffix="Completed", length=40
344
+ ) # show completed progress bar
345
+ print("\n")
346
+ return containment_matrix
347
+
348
+
349
+ def pairwiseContainmentMatrix(
350
+ mod_set_x: List[int],
351
+ mod_set_y: List[int],
352
+ mod_set_x_neighbors: List[List[int]],
353
+ mod_set_y_neighbors: List[List[int]],
354
+ identity: int,
355
+ k: int,
356
+ supress_progress: bool,
357
+ ) -> np.ndarray:
358
+ """
359
+ Calculate an updated identity matrix using specified parameters.
360
+
361
+ Args:
362
+ mod_set_x (List[int]): List of values for the x-axis.
363
+ mod_set_y (List[int]): List of values for the y-axis.
364
+ mod_set_x_neighbors (List[List[int]]): List of lists representing neighbors of mod_set_x values.
365
+ mod_set_y_neighbors (List[List[int]]): List of lists representing neighbors of mod_set_y values.
366
+ identity (int): Resolution parameter.
367
+ k (int): Value for the k parameter in the binomial_distance function.
368
+ supress_progress (bool): if true supresses the progress bar
369
+
370
+ Returns:
371
+ np.ndarray: An identity matrix containing containment values.
372
+ """
373
+ n = max(len(mod_set_y), len(mod_set_x))
374
+ progress_thresholds = round(n / 77)
375
+
376
+ if not supress_progress:
377
+ printProgressBar(0, n, prefix="Progress:", suffix="Complete", length=40)
378
+ containment_matrix = np.zeros((n, n), dtype=float)
379
+
380
+ for w in range(len(mod_set_y)):
381
+ if not supress_progress:
382
+ if w % progress_thresholds == 0:
383
+ printProgressBar(w, n, prefix="Progress:", suffix="Complete", length=40)
384
+ for q in range(n):
385
+ try:
386
+ containment_matrix[w, q] = (
387
+ binomial_distance(
388
+ containment_neighbors(
389
+ mod_set_x[q],
390
+ mod_set_y[w],
391
+ mod_set_x_neighbors[q],
392
+ mod_set_y_neighbors[w],
393
+ identity,
394
+ k,
395
+ ),
396
+ k,
397
+ )
398
+ * 100.0
399
+ )
400
+ # Bandaid solution for too sequences that are too small.
401
+ except IndexError as e:
402
+ pass
403
+
404
+ if not supress_progress:
405
+ printProgressBar(
406
+ n, n, prefix="Progress:", suffix="Completed", length=40
407
+ ) # show completed progress bar
408
+ print("\n")
409
+ return containment_matrix
410
+
411
+
412
+ # Function used to find matching color palette to those available in const.py
413
+ def findElementsWithPrefix(lst, prefix):
414
+ matching_elements = []
415
+ for element in lst:
416
+ if element.startswith(prefix):
417
+ matching_elements.append(element)
418
+ return matching_elements
419
+
420
+
421
+ def getInteractiveColor(palette_name, palette_orientation):
422
+ palettes = colorbrewer.COLOR_MAPS
423
+ tmp_color = []
424
+ new_palette = palette_name.split("_")
425
+ if palette_name in DIVERGING_PALETTES:
426
+ tmp_color = palettes["Diverging"][new_palette[0]][new_palette[1]]["Colors"]
427
+ if palette_orientation == "+":
428
+ palette_orientation = "-"
429
+ else:
430
+ palette_orientation = "+"
431
+ elif palette_name in SEQUENTIAL_PALETTES:
432
+ tmp_color = palettes["Sequential"][new_palette[0]][new_palette[1]]["Colors"]
433
+ elif palette_name in QUALITATIVE_PALETTES:
434
+ tmp_color = palettes["Qualitative"][new_palette[0]][new_palette[1]]["Colors"]
435
+ else:
436
+ print("Unable to determine color palette. Selecting default \n")
437
+ tmp_color = palettes["Diverging"]["Spectral"]["11"]["Colors"]
438
+ palette_orientation = "-"
439
+ if palette_orientation == "-":
440
+ tmp_color = tmp_color[::-1]
441
+ tmp_color = [[255, 255, 255]] + tmp_color
442
+ total_values = len(tmp_color)
443
+ formatted_values = [
444
+ [i / (total_values - 1), f"rgb({r}, {g}, {b})"]
445
+ for i, (r, g, b) in enumerate(tmp_color)
446
+ ]
447
+ return formatted_values
448
+
449
+
450
+ def getMatchingColors(color_name):
451
+ available_colors = [
452
+ element
453
+ for sublist in [DIVERGING_PALETTES, QUALITATIVE_PALETTES, SEQUENTIAL_PALETTES]
454
+ for element in sublist
455
+ ]
456
+ matching_elements = findElementsWithPrefix(available_colors, color_name)
457
+ return matching_elements[-1]
458
+
459
+
460
+ def containment(set1, set2):
461
+ intersection = set1.intersection(set2)
462
+ try:
463
+ if len(set1) > 0 and len(set2) > 0:
464
+ if len(set1) > len(set2):
465
+ return float(len(intersection) / len(set1))
466
+ else:
467
+ return float(len(intersection) / len(set2))
468
+ else:
469
+ return 0.0
470
+ except ZeroDivisionError:
471
+ return 0.0
472
+
473
+
474
+ def verifyModimizers(sparsity, l):
475
+ # Get the next highest power of 2, if not provided
476
+ updated_sparsity = nextPowerOfTwo(sparsity)
477
+
478
+ sparsity_layers = [updated_sparsity]
479
+ while l > 0:
480
+ if sparsity_layers[-1] == 1:
481
+ return sparsity_layers
482
+ elif sparsity_layers[-1] % 2 == 1:
483
+ sparsity_layers[-1] = int(sparsity_layers[-1] + 1)
484
+ sparsity_layers.append(int(sparsity_layers[-1] / 2))
485
+ l -= 1
486
+
487
+ return sparsity_layers
488
+
489
+
490
+ def nextPowerOfTwo(n):
491
+ if n <= 0:
492
+ return 1
493
+ n -= 1
494
+ n |= n >> 1
495
+ n |= n >> 2
496
+ n |= n >> 4
497
+ n |= n >> 8
498
+ n |= n >> 16
499
+ return n + 1
500
+
501
+
502
+ def generateDictionaryFromList(lst: List[int]) -> Dict[Tuple[int, int], int]:
503
+ result = {}
504
+ for i in range(len(lst) - 1):
505
+ result[(lst[i], lst[i + 1])] = i
506
+ return result
507
+
508
+
509
+ def findValueInRange(integer: int, range_dict: dict) -> int:
510
+ if integer > max(key[0] for key in range_dict.keys()):
511
+ return 0
512
+ highest_value = max(range_dict.values()) + 1
513
+ for key, value in range_dict.items():
514
+ if key[0] >= integer >= key[1]:
515
+ return value
516
+ return highest_value
517
+
518
+
519
+ def setZoomLevels(axis_length, sparsity_layers):
520
+ zoom_levels = []
521
+ zoom_levels.append(axis_length)
522
+ for i in range(1, len(sparsity_layers)):
523
+ zoom_levels.append(round(axis_length / pow(2, i)))
524
+ return zoom_levels
525
+
526
+
527
+ def makeDifferencesEqual(x, x_prime, y, y_prime):
528
+ difference_x = abs(x_prime - x)
529
+ difference_y = abs(y_prime - y)
530
+
531
+ if difference_x != difference_y:
532
+ if difference_x < difference_y:
533
+ x_prime += difference_y - difference_x
534
+ else:
535
+ y_prime += difference_x - difference_y
536
+
537
+ return x_prime, y_prime