agglovar 0.0.1.dev8__tar.gz → 0.0.1.dev10__tar.gz
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.
- {agglovar-0.0.1.dev8/src/agglovar.egg-info → agglovar-0.0.1.dev10}/PKG-INFO +1 -1
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/__init__.py +3 -1
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/bed/intersect.py +13 -3
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/bed/join.py +17 -11
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/dev/imports.py +41 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/kmer/plot.py +22 -6
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/base.py +47 -21
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/cumulative.py +53 -27
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/meta/descriptors.py +1 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/base.py +9 -4
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/overlap/_overlap.py +55 -38
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/overlap/_stage.py +10 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/util/var.py +4 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10/src/agglovar.egg-info}/PKG-INFO +1 -1
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/LICENSE +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/README.md +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/pyproject.toml +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/setup.cfg +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/align/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/align/op.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/align/score.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/bed/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/bed/col.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/bed/merge.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/dev/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/fa.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/io.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/kmer/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/kmer/util.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/config/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/config/parser.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/config/stage.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/config/strategy.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/om/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/om/_executor_base.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/merge/om/_executor_nr.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/meta/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/meta/decorators.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/overlap/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/overlap/_const.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/pairwise/weights.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/schema.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/seqmatch.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/util/__init__.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar/util/str.py +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar.egg-info/SOURCES.txt +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar.egg-info/dependency_links.txt +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar.egg-info/requires.txt +0 -0
- {agglovar-0.0.1.dev8 → agglovar-0.0.1.dev10}/src/agglovar.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Agglovar: A toolkit for fast genomic variant transformations and intersects."""
|
|
2
2
|
|
|
3
|
-
__version__ = '0.0.1.
|
|
3
|
+
__version__ = '0.0.1.dev10'
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
6
|
'align',
|
|
@@ -13,6 +13,7 @@ __all__ = [
|
|
|
13
13
|
'meta',
|
|
14
14
|
'schema',
|
|
15
15
|
'seqmatch',
|
|
16
|
+
'util',
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
from . import align
|
|
@@ -25,3 +26,4 @@ from . import merge
|
|
|
25
26
|
from . import meta
|
|
26
27
|
from . import schema
|
|
27
28
|
from . import seqmatch
|
|
29
|
+
from . import util
|
|
@@ -86,6 +86,17 @@ def as_proportion(
|
|
|
86
86
|
if name is None or not (name := name.strip()):
|
|
87
87
|
raise ValueError('Name must be a non-empty string')
|
|
88
88
|
|
|
89
|
+
if isinstance(df_a, pl.DataFrame):
|
|
90
|
+
df_a = df_a.lazy()
|
|
91
|
+
|
|
92
|
+
if isinstance(df_b, pl.DataFrame):
|
|
93
|
+
df_b = df_b.lazy()
|
|
94
|
+
|
|
95
|
+
col_set_a = set(df_a.collect_schema().names())
|
|
96
|
+
|
|
97
|
+
if '_index' not in col_set_a:
|
|
98
|
+
df_a = df_a.with_row_index('_index')
|
|
99
|
+
|
|
89
100
|
# Collapse b, index a
|
|
90
101
|
df_b_nr = (
|
|
91
102
|
merge_depth(df_b, 0, col_names_b)
|
|
@@ -96,8 +107,6 @@ def as_proportion(
|
|
|
96
107
|
|
|
97
108
|
df_a = (
|
|
98
109
|
df_a
|
|
99
|
-
.drop('_index', strict=False)
|
|
100
|
-
.with_row_index('_index')
|
|
101
110
|
.filter(
|
|
102
111
|
pl.col(col_names_a.pos).is_not_null(),
|
|
103
112
|
pl.col(col_names_a.end).is_not_null(),
|
|
@@ -128,11 +137,12 @@ def as_proportion(
|
|
|
128
137
|
df_a
|
|
129
138
|
.select(
|
|
130
139
|
'_index',
|
|
140
|
+
'_index_a_bed_as_prop',
|
|
131
141
|
(col_expr_a.end - col_expr_a.pos).alias('len')
|
|
132
142
|
)
|
|
133
143
|
.join(
|
|
134
144
|
df_join.lazy(),
|
|
135
|
-
on='
|
|
145
|
+
on='_index_a_bed_as_prop',
|
|
136
146
|
how='left',
|
|
137
147
|
)
|
|
138
148
|
.select(
|
|
@@ -46,6 +46,15 @@ class _JoinResources:
|
|
|
46
46
|
if isinstance(df_b, pl.DataFrame):
|
|
47
47
|
df_b = df_b.lazy()
|
|
48
48
|
|
|
49
|
+
cols_a = set(df_a.collect_schema().keys())
|
|
50
|
+
cols_b = set(df_b.collect_schema().keys())
|
|
51
|
+
|
|
52
|
+
if '_index' not in cols_a:
|
|
53
|
+
df_a = df_a.with_row_index('_index')
|
|
54
|
+
|
|
55
|
+
if '_index' not in cols_b:
|
|
56
|
+
df_b = df_b.with_row_index('_index')
|
|
57
|
+
|
|
49
58
|
# Set column names
|
|
50
59
|
ref_cols = get_coord_cols('ref')
|
|
51
60
|
|
|
@@ -65,16 +74,18 @@ class _JoinResources:
|
|
|
65
74
|
# Prepare tables
|
|
66
75
|
df_a = (
|
|
67
76
|
df_a
|
|
68
|
-
.select(
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
.select(
|
|
78
|
+
pl.col('_index').alias('_index_a'),
|
|
79
|
+
*col_select_a
|
|
80
|
+
)
|
|
71
81
|
)
|
|
72
82
|
|
|
73
83
|
df_b = (
|
|
74
84
|
df_b
|
|
75
|
-
.select(
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
.select(
|
|
86
|
+
pl.col('_index').alias('_index_b'),
|
|
87
|
+
*col_select_b
|
|
88
|
+
)
|
|
78
89
|
)
|
|
79
90
|
|
|
80
91
|
self.df_a = df_a
|
|
@@ -322,13 +333,10 @@ def pairwise_join_tree(
|
|
|
322
333
|
|
|
323
334
|
for df_batch_b in (
|
|
324
335
|
df_b
|
|
325
|
-
.drop('_index_b', strict=False)
|
|
326
|
-
.with_row_index('_index_b')
|
|
327
336
|
.select(pl.col('_index_b'), *col_b.exprs())
|
|
328
337
|
.collect_batches(chunk_size=chunk_size)
|
|
329
338
|
):
|
|
330
339
|
for index_b, chrom_b, pos_b, end_b in df_batch_b.iter_rows():
|
|
331
|
-
# print(f'Adding: {chrom_b} - {pos_b} - {end_b}')
|
|
332
340
|
itree[chrom_b].addi(pos_b - distance, end_b + distance, index_b)
|
|
333
341
|
|
|
334
342
|
# Intersect
|
|
@@ -336,8 +344,6 @@ def pairwise_join_tree(
|
|
|
336
344
|
|
|
337
345
|
for df_chunk in (
|
|
338
346
|
df_a
|
|
339
|
-
.drop('_index_a', strict=False)
|
|
340
|
-
.with_row_index('_index_a')
|
|
341
347
|
.select(pl.col('_index_a'), *col_a.exprs())
|
|
342
348
|
.collect_batches(chunk_size=chunk_size)
|
|
343
349
|
):
|
|
@@ -6,13 +6,17 @@ __all__ = [
|
|
|
6
6
|
'find_imports',
|
|
7
7
|
'get_defined_names',
|
|
8
8
|
'get_module_definitions',
|
|
9
|
+
'librl',
|
|
9
10
|
]
|
|
10
11
|
|
|
11
12
|
import ast
|
|
13
|
+
from collections.abc import Iterable
|
|
12
14
|
from dataclasses import dataclass, field
|
|
15
|
+
from graphlib import TopologicalSorter
|
|
13
16
|
import importlib
|
|
14
17
|
import inspect
|
|
15
18
|
import re
|
|
19
|
+
import sys
|
|
16
20
|
from types import ModuleType
|
|
17
21
|
from typing import Any, Generator, Optional
|
|
18
22
|
|
|
@@ -439,3 +443,40 @@ def get_module_definitions(
|
|
|
439
443
|
if include_dunder or not target.id.startswith('__'):
|
|
440
444
|
yield target.id
|
|
441
445
|
|
|
446
|
+
def librl(
|
|
447
|
+
packages: Iterable[str] | str = 'agglovar',
|
|
448
|
+
) -> None:
|
|
449
|
+
"""Reload libraries.
|
|
450
|
+
|
|
451
|
+
Reloads all modules in the given packages and their dependencies. Attempts to resolve import
|
|
452
|
+
order by inspecting members of each package.
|
|
453
|
+
|
|
454
|
+
:param packages: Names of packages to reload. Must be top-level package names (name
|
|
455
|
+
before the first dot).
|
|
456
|
+
"""
|
|
457
|
+
if isinstance(packages, str):
|
|
458
|
+
packages = {packages,}
|
|
459
|
+
else:
|
|
460
|
+
packages = set(packages)
|
|
461
|
+
|
|
462
|
+
deps: dict[str, set[str]] = {}
|
|
463
|
+
|
|
464
|
+
for key in sorted(sys.modules.keys()):
|
|
465
|
+
if key.split('.', 1)[0] not in packages:
|
|
466
|
+
continue
|
|
467
|
+
|
|
468
|
+
deps.setdefault(key, set())
|
|
469
|
+
|
|
470
|
+
for attr_name in dir(sys.modules[key]):
|
|
471
|
+
attr = getattr(sys.modules[key], attr_name)
|
|
472
|
+
|
|
473
|
+
if '__module__' in dir(attr):
|
|
474
|
+
module = attr.__module__
|
|
475
|
+
|
|
476
|
+
if not attr.__module__.split('.', 1)[0] in packages or module == key:
|
|
477
|
+
continue
|
|
478
|
+
|
|
479
|
+
deps[key].add(attr.__module__)
|
|
480
|
+
|
|
481
|
+
for key in TopologicalSorter(deps).static_order():
|
|
482
|
+
importlib.reload(sys.modules[key])
|
|
@@ -32,6 +32,19 @@ def dotplot(
|
|
|
32
32
|
) -> t_mpl_Figure:
|
|
33
33
|
"""Generate a dotplot for two sequences.
|
|
34
34
|
|
|
35
|
+
Configuration items:
|
|
36
|
+
|
|
37
|
+
* label_x, label_y: Axis labels (default "Sequence", and "Reference")
|
|
38
|
+
* start_x, start_y: Start position along each axis. Should be the first base position in the extracted region (Default 0).
|
|
39
|
+
* linewidth: Width of the lines (default 1)
|
|
40
|
+
* plot_width, plot_height, plot_dpi: Plot dimensions (default 7, 7, 300)
|
|
41
|
+
* invert_y: Invert the y axis (default False)
|
|
42
|
+
* color: A tuple of matplotlib colors for forward and reverse k-mer dots (default ("black", "red"))
|
|
43
|
+
* May be "reverse" or "rev" to reverse the order of the default colors.
|
|
44
|
+
* color_reverse: Reverse the order of the default colors (defaults to invert_y)
|
|
45
|
+
* kmer_util: A kmer_util.KmerUtil object (default None)
|
|
46
|
+
* k: K-mer size if kmer_util is missing (default 32)
|
|
47
|
+
|
|
35
48
|
:params seq_x: Sequence 1 (horizontal axis).
|
|
36
49
|
:params seq_y: Sequence 2 (vertical axis).
|
|
37
50
|
:params config: Configuration dictionary.
|
|
@@ -56,10 +69,8 @@ def dotplot(
|
|
|
56
69
|
# Get k-mer util.
|
|
57
70
|
if 'kutil' in config:
|
|
58
71
|
kutil = config['kutil']
|
|
59
|
-
elif 'k' in config:
|
|
60
|
-
kutil = kmer_util.KmerUtil(int(config['k']))
|
|
61
72
|
else:
|
|
62
|
-
kutil = kmer_util.KmerUtil(32)
|
|
73
|
+
kutil = kmer_util.KmerUtil(int(config.get('k', 32)))
|
|
63
74
|
|
|
64
75
|
if anno_list is None:
|
|
65
76
|
anno_list = list()
|
|
@@ -72,8 +83,10 @@ def dotplot(
|
|
|
72
83
|
elif isinstance(color, str):
|
|
73
84
|
if color.lower() in {'reverse', 'rev'}:
|
|
74
85
|
color = ('red', 'black')
|
|
86
|
+
elif color.lower() in {'forward', 'fwd'}:
|
|
87
|
+
color = ('black', 'red')
|
|
75
88
|
else:
|
|
76
|
-
raise ValueError(f'Argument color is a string: Must be "reverse" or "rev": {color}')
|
|
89
|
+
raise ValueError(f'Argument color is a string: Must be "reverse" or "rev" or a tuple of two colors: {color}')
|
|
77
90
|
|
|
78
91
|
elif isinstance(color, (list, tuple)):
|
|
79
92
|
if len(color) != 2:
|
|
@@ -99,9 +112,12 @@ def dotplot(
|
|
|
99
112
|
|
|
100
113
|
plot_props = dict() # Plot properties, such as x and y limits
|
|
101
114
|
|
|
115
|
+
if config.get('color_reverse', invert_y):
|
|
116
|
+
color = color[::-1]
|
|
117
|
+
|
|
102
118
|
# Process annotation dict
|
|
103
|
-
anno_list_background =
|
|
104
|
-
anno_list_foreground =
|
|
119
|
+
anno_list_background = []
|
|
120
|
+
anno_list_foreground = []
|
|
105
121
|
|
|
106
122
|
if anno_list is None:
|
|
107
123
|
anno_list = list()
|
|
@@ -21,8 +21,8 @@ from ..util.str import collision_rename
|
|
|
21
21
|
CallsetDefType: TypeAlias = (
|
|
22
22
|
pl.DataFrame
|
|
23
23
|
| pl.LazyFrame
|
|
24
|
-
|
|
|
25
|
-
pl.DataFrame | pl.LazyFrame
|
|
24
|
+
| Iterable[
|
|
25
|
+
pl.DataFrame | pl.LazyFrame | str
|
|
26
26
|
]
|
|
27
27
|
)
|
|
28
28
|
"""Alias for acceptable types."""
|
|
@@ -38,12 +38,17 @@ class MergeBase(ABC):
|
|
|
38
38
|
@abstractmethod
|
|
39
39
|
def __call__(
|
|
40
40
|
self,
|
|
41
|
-
callsets: Iterable[CallsetDefType]
|
|
41
|
+
callsets: Iterable[CallsetDefType],
|
|
42
|
+
retain_index: bool = False,
|
|
43
|
+
pre_filter: Optional[Iterable[pl.Expr]] = None,
|
|
42
44
|
) -> pl.LazyFrame:
|
|
43
45
|
"""
|
|
44
46
|
Intersect callsets.
|
|
45
47
|
|
|
46
48
|
:param callsets: Callsets to intersect.
|
|
49
|
+
:param retain_index: If `True`, do not drop an existing "_index" column if it exists.
|
|
50
|
+
:param pre_filter: If set, filter each table with these expressions. Filter is applied
|
|
51
|
+
last (after "_index" is set).
|
|
47
52
|
|
|
48
53
|
:return: A merged callset table.
|
|
49
54
|
"""
|
|
@@ -52,7 +57,8 @@ class MergeBase(ABC):
|
|
|
52
57
|
@staticmethod
|
|
53
58
|
def get_intersect_tuples(
|
|
54
59
|
callsets: Iterable[CallsetDefType],
|
|
55
|
-
retain_index: bool = False
|
|
60
|
+
retain_index: bool = False,
|
|
61
|
+
pre_filter: Optional[Iterable[pl.Expr]] = None,
|
|
56
62
|
) -> list[tuple[pl.LazyFrame, str, int]]:
|
|
57
63
|
"""
|
|
58
64
|
Transform input arguments to a list of tuples with set fields.
|
|
@@ -72,6 +78,8 @@ class MergeBase(ABC):
|
|
|
72
78
|
:param callsets: Callsets parameter. May be an iterable of DataFrames, LazyFrames, or
|
|
73
79
|
tuples of (DataFrame, name).
|
|
74
80
|
:param retain_index: If `True`, do not drop an existing "_index" column if it exists.
|
|
81
|
+
:param pre_filter: If set, filter each table with these expressions. Filter is applied
|
|
82
|
+
last (after "_index" is set).
|
|
75
83
|
|
|
76
84
|
:return: A list of tuples where each tuple element represents one input source.
|
|
77
85
|
"""
|
|
@@ -84,30 +92,43 @@ class MergeBase(ABC):
|
|
|
84
92
|
|
|
85
93
|
i = 0
|
|
86
94
|
|
|
95
|
+
pre_filter = list(pre_filter if pre_filter is not None else [])
|
|
96
|
+
|
|
87
97
|
if callsets is None:
|
|
88
98
|
raise ValueError('Missing callsets')
|
|
89
99
|
|
|
90
100
|
for callset in callsets:
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
# Get callset table and name
|
|
102
|
+
|
|
103
|
+
if isinstance(callset, (pl.LazyFrame, pl.DataFrame)):
|
|
104
|
+
callset_table = callset
|
|
105
|
+
callset_name = None
|
|
106
|
+
else:
|
|
107
|
+
try:
|
|
108
|
+
callset = tuple(callset)
|
|
109
|
+
except TypeError:
|
|
93
110
|
raise ValueError(
|
|
94
|
-
f'Callset at index {i}
|
|
111
|
+
f'Callset at index {i}: Callset must be a Polars table or an iterable '
|
|
112
|
+
f'of two elements (table, name): Received {type(callset)}'
|
|
95
113
|
)
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
if len(callset) == 1:
|
|
116
|
+
callset_table = callset[0]
|
|
117
|
+
callset_name = None
|
|
99
118
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
elif len(callset) == 2:
|
|
120
|
+
callset_table = callset[0]
|
|
121
|
+
callset_name = str(callset[1])
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
elif not isinstance(callset_table, pl.LazyFrame):
|
|
107
|
-
raise TypeError(
|
|
108
|
-
f'Callset at index {i} must be a DataFrame or LazyFrame, got {type(callset_table)}'
|
|
109
|
-
)
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError(f'Callset at index {i}: Input callsets must be a sequence of one or two elements: Received {len(callset)}')
|
|
110
125
|
|
|
126
|
+
if not isinstance(callset_table, (pl.LazyFrame, pl.DataFrame)):
|
|
127
|
+
raise ValueError(f'Callset at index {i}: First element Polars table: Received {type(callset_table)}')
|
|
128
|
+
|
|
129
|
+
callset_name = _get_name(callset_name, i, name_set)
|
|
130
|
+
|
|
131
|
+
# Set index if missing or forced
|
|
111
132
|
if not (retain_index and '_index' in callset_table.collect_schema().names()):
|
|
112
133
|
callset_table = (
|
|
113
134
|
callset_table
|
|
@@ -118,22 +139,27 @@ class MergeBase(ABC):
|
|
|
118
139
|
# Add missing IDs
|
|
119
140
|
callset_table = (
|
|
120
141
|
callset_table
|
|
142
|
+
.rename({'_index': '_mg_src_index'})
|
|
121
143
|
.with_columns(
|
|
122
144
|
pl.coalesce(
|
|
123
145
|
pl.col(r'^id$'), # Existing ID if present
|
|
124
|
-
pl.concat_str(pl.lit('var'), pl.col('
|
|
146
|
+
pl.concat_str(pl.lit('var'), pl.col('_mg_src_index')) # Fill missing/null
|
|
125
147
|
).cast(pl.String).alias('id')
|
|
126
148
|
)
|
|
127
149
|
.with_columns(
|
|
128
150
|
(
|
|
129
151
|
pl.when(pl.col('id').is_null())
|
|
130
|
-
.then(pl.concat_str(pl.lit('var'), pl.col('
|
|
152
|
+
.then(pl.concat_str(pl.lit('var'), pl.col('_mg_src_index')))
|
|
131
153
|
.otherwise(pl.col('id'))
|
|
132
154
|
).cast(pl.String).alias('id')
|
|
133
155
|
)
|
|
156
|
+
.filter(
|
|
157
|
+
*pre_filter
|
|
158
|
+
)
|
|
134
159
|
)
|
|
135
160
|
|
|
136
|
-
|
|
161
|
+
# Finalize
|
|
162
|
+
callset_tuple_list.append(tuple((callset_table, callset_name, i)))
|
|
137
163
|
name_set.add(callset_name)
|
|
138
164
|
|
|
139
165
|
i += 1
|
|
@@ -15,11 +15,13 @@ where multiple join choices are possible.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
|
+
'LeadStrategy',
|
|
18
19
|
'MergeCumulative',
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
from collections.abc import Iterable
|
|
22
23
|
from enum import Enum
|
|
24
|
+
from typing import Optional
|
|
23
25
|
|
|
24
26
|
import polars as pl
|
|
25
27
|
import polars.selectors as cs
|
|
@@ -72,12 +74,11 @@ def _init_cumulative(
|
|
|
72
74
|
:return: A table ready to be used as a cumulative table or appended to an existing one.
|
|
73
75
|
"""
|
|
74
76
|
mg_cols = ('_mg_src', '_mg_src_pos', '_mg_stat')
|
|
75
|
-
required_cols = set(required_cols) - set(mg_cols)
|
|
77
|
+
required_cols = (set(required_cols) - set(mg_cols)) | {'_mg_src_index'}
|
|
76
78
|
|
|
77
79
|
return (
|
|
78
80
|
df_next
|
|
79
81
|
.select(
|
|
80
|
-
pl.col('_index').alias('_mg_index'),
|
|
81
82
|
*required_cols
|
|
82
83
|
)
|
|
83
84
|
.with_columns( # Source columns
|
|
@@ -85,7 +86,7 @@ def _init_cumulative(
|
|
|
85
86
|
pl.struct(
|
|
86
87
|
pl.lit(src_index).cast(pl.Int32).alias('index'),
|
|
87
88
|
pl.lit(src_name).cast(pl.String).alias('id'),
|
|
88
|
-
pl.col('
|
|
89
|
+
pl.col('_mg_src_index').alias('var_index'),
|
|
89
90
|
pl.col('id').cast(pl.String).alias('var_id'),
|
|
90
91
|
)
|
|
91
92
|
).alias('_mg_src'),
|
|
@@ -99,11 +100,11 @@ def _init_cumulative(
|
|
|
99
100
|
# May be a Polars bug.
|
|
100
101
|
pl.col('_mg_src_pos').list.concat(pl.col('pos').cast(pl.Int64))
|
|
101
102
|
)
|
|
103
|
+
.drop('_mg_src_index')
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
def _get_match(
|
|
105
107
|
df_cumulative: pl.LazyFrame,
|
|
106
|
-
df_next: pl.LazyFrame,
|
|
107
108
|
df_join: pl.DataFrame,
|
|
108
109
|
src_index: int,
|
|
109
110
|
src_name: str,
|
|
@@ -111,9 +112,10 @@ def _get_match(
|
|
|
111
112
|
) -> pl.LazyFrame:
|
|
112
113
|
return ( # Update matching variants
|
|
113
114
|
df_cumulative
|
|
115
|
+
.with_row_index('_index')
|
|
114
116
|
.join(
|
|
115
117
|
df_join.lazy().select(pl.all().name.prefix('_mg_join_')),
|
|
116
|
-
left_on='
|
|
118
|
+
left_on='_index', right_on='_mg_join_index_a', how='inner'
|
|
117
119
|
)
|
|
118
120
|
.with_columns(
|
|
119
121
|
pl.col('_mg_src').list.concat(
|
|
@@ -132,7 +134,7 @@ def _get_match(
|
|
|
132
134
|
})
|
|
133
135
|
)
|
|
134
136
|
)
|
|
135
|
-
.drop('
|
|
137
|
+
.drop('_index', cs.starts_with('_mg_join_'))
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
|
|
@@ -161,7 +163,10 @@ class MergeCumulative(MergeBase):
|
|
|
161
163
|
def __call__(
|
|
162
164
|
self,
|
|
163
165
|
callsets: Iterable[CallsetDefType],
|
|
164
|
-
retain_index: bool = False
|
|
166
|
+
retain_index: bool = False,
|
|
167
|
+
pre_filter: Optional[Iterable[pl.Expr]] = None,
|
|
168
|
+
sort: bool = True,
|
|
169
|
+
add_id: bool = True,
|
|
165
170
|
) -> pl.LazyFrame:
|
|
166
171
|
"""
|
|
167
172
|
Intersect callsets.
|
|
@@ -169,10 +174,12 @@ class MergeCumulative(MergeBase):
|
|
|
169
174
|
:param callsets: Callsets to intersect.
|
|
170
175
|
:param retain_index: If `True`, do not drop an existing "_index" column in callset tables
|
|
171
176
|
if they exist.
|
|
177
|
+
:param pre_filter: If set, filter each table with these expressions. Filter is applied
|
|
178
|
+
last (after "_index" is set).
|
|
172
179
|
|
|
173
180
|
:return: A merged callset table.
|
|
174
181
|
"""
|
|
175
|
-
callsets = self.get_intersect_tuples(callsets, retain_index)
|
|
182
|
+
callsets = self.get_intersect_tuples(callsets, retain_index, pre_filter)
|
|
176
183
|
|
|
177
184
|
if len(callsets) == 0:
|
|
178
185
|
raise ValueError('No callsets to intersect.')
|
|
@@ -195,7 +202,7 @@ class MergeCumulative(MergeBase):
|
|
|
195
202
|
df_next, src_name, src_index = callsets[0]
|
|
196
203
|
|
|
197
204
|
merge_stat_cols = { # Empty join, gets names of columns this join will return
|
|
198
|
-
col: dtype for col, dtype in self.pairwise_join.join(df_next.head(0)
|
|
205
|
+
col: dtype for col, dtype in self.pairwise_join.join(df_next.head(0), df_next.head(0)).collect_schema().items()
|
|
199
206
|
if col not in {'index_a', 'index_b', 'id_a', 'id_b'}
|
|
200
207
|
}
|
|
201
208
|
|
|
@@ -216,16 +223,23 @@ class MergeCumulative(MergeBase):
|
|
|
216
223
|
# Intersect
|
|
217
224
|
df_join = (
|
|
218
225
|
(
|
|
219
|
-
self.pairwise_join.join(df_cumulative, df_next
|
|
226
|
+
self.pairwise_join.join(df_cumulative, df_next)
|
|
220
227
|
.unique('index_a', keep='first')
|
|
221
228
|
.unique('index_b', keep='first')
|
|
222
229
|
.sort('index_a', 'index_b')
|
|
223
230
|
)
|
|
224
231
|
.join(
|
|
225
|
-
|
|
232
|
+
(
|
|
233
|
+
df_next
|
|
234
|
+
.with_row_index('_index')
|
|
235
|
+
.select('_index', '_mg_src_index', 'pos', 'id')
|
|
236
|
+
),
|
|
226
237
|
left_on='index_b', right_on='_index',
|
|
227
238
|
how='left'
|
|
228
239
|
)
|
|
240
|
+
.with_columns(
|
|
241
|
+
pl.col('_mg_src_index').alias('index_b')
|
|
242
|
+
)
|
|
229
243
|
.collect()
|
|
230
244
|
)
|
|
231
245
|
|
|
@@ -235,36 +249,36 @@ class MergeCumulative(MergeBase):
|
|
|
235
249
|
# 3) New: New records in this callset
|
|
236
250
|
|
|
237
251
|
df_match = _get_match(
|
|
238
|
-
df_cumulative,
|
|
252
|
+
df_cumulative, df_join, src_index, src_name, merge_stat_cols
|
|
239
253
|
)
|
|
240
254
|
|
|
241
255
|
df_nomatch = ( # Gather records that were not updated
|
|
242
256
|
df_cumulative
|
|
257
|
+
.with_row_index('_index')
|
|
243
258
|
.join(
|
|
244
259
|
df_join.lazy(),
|
|
245
|
-
left_on='
|
|
260
|
+
left_on='_index', right_on='index_a', how='anti'
|
|
246
261
|
)
|
|
247
|
-
.drop('
|
|
262
|
+
.drop('_index')
|
|
248
263
|
)
|
|
249
264
|
|
|
250
|
-
df_new = (
|
|
265
|
+
df_new = ( # New records
|
|
251
266
|
_init_cumulative(
|
|
252
267
|
(
|
|
253
268
|
df_next
|
|
254
269
|
.join(
|
|
255
270
|
df_join.lazy(),
|
|
256
|
-
left_on='
|
|
271
|
+
left_on='_mg_src_index', right_on='index_b', how='anti'
|
|
257
272
|
)
|
|
258
273
|
),
|
|
259
274
|
src_index, src_name, required_cols, merge_stat_cols
|
|
260
275
|
)
|
|
261
|
-
.drop('_mg_index')
|
|
262
276
|
)
|
|
263
277
|
|
|
264
278
|
df_cumulative = (
|
|
265
279
|
pl.concat([df_match, df_nomatch, df_new])
|
|
266
280
|
.sort('chrom', 'pos', 'end')
|
|
267
|
-
.with_row_index('
|
|
281
|
+
.with_row_index('_mg_src_index')
|
|
268
282
|
.collect()
|
|
269
283
|
.lazy()
|
|
270
284
|
)
|
|
@@ -272,7 +286,7 @@ class MergeCumulative(MergeBase):
|
|
|
272
286
|
# Subset to merge columns (drop columns needed for join)
|
|
273
287
|
df_cumulative = (
|
|
274
288
|
df_cumulative
|
|
275
|
-
.select(cs.starts_with('_mg_').exclude('
|
|
289
|
+
.select(cs.starts_with('_mg_').exclude('_mg_src_index'))
|
|
276
290
|
.rename(lambda col: col.removeprefix('_'))
|
|
277
291
|
)
|
|
278
292
|
|
|
@@ -320,31 +334,43 @@ class MergeCumulative(MergeBase):
|
|
|
320
334
|
.filter(pl.col('mg_src_lead').struct.field('index') == src_index)
|
|
321
335
|
.select(
|
|
322
336
|
'_mg_index',
|
|
323
|
-
pl.col('mg_src_lead').struct.field('var_index').alias('
|
|
337
|
+
pl.col('mg_src_lead').struct.field('var_index').alias('_mg_src_index'),
|
|
324
338
|
)
|
|
325
339
|
),
|
|
326
|
-
|
|
340
|
+
on='_mg_src_index', how='inner'
|
|
327
341
|
)
|
|
328
|
-
.drop('
|
|
342
|
+
.drop('_mg_src_index')
|
|
329
343
|
)
|
|
330
344
|
|
|
331
345
|
for col in set(table_cols) - df_next_cols:
|
|
332
346
|
df_next = df_next.with_columns(pl.lit(None).cast(all_col_dict[col]).alias(col))
|
|
333
347
|
|
|
334
|
-
df_next =
|
|
335
|
-
|
|
348
|
+
df_next = (
|
|
349
|
+
df_next.with_columns(
|
|
350
|
+
pl.col('filter').fill_null([])
|
|
351
|
+
)
|
|
352
|
+
.select('_mg_index', *table_cols)
|
|
336
353
|
)
|
|
337
354
|
|
|
338
355
|
lead_list.append(df_next)
|
|
339
356
|
|
|
340
|
-
|
|
357
|
+
df_merge = (
|
|
341
358
|
pl.concat(lead_list)
|
|
342
359
|
.join(
|
|
343
360
|
df_cumulative.lazy(),
|
|
344
361
|
on='_mg_index', how='inner'
|
|
345
362
|
)
|
|
346
|
-
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
if add_id:
|
|
366
|
+
df_merge = df_merge.with_columns(id_version_expr())
|
|
367
|
+
|
|
368
|
+
if sort:
|
|
369
|
+
df_merge = df_merge.sort('chrom', 'pos', 'end', 'id')
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
df_merge
|
|
347
374
|
.drop(*(['_mg_index'] + ['filter'] if drop_filter else []))
|
|
348
375
|
.select(col_order)
|
|
349
|
-
.sort('chrom', 'pos', 'end', 'id')
|
|
350
376
|
)
|
|
@@ -9,7 +9,6 @@ __all__ = [
|
|
|
9
9
|
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
11
|
from collections.abc import Iterator, Iterable
|
|
12
|
-
from typing import Optional
|
|
13
12
|
|
|
14
13
|
import polars as pl
|
|
15
14
|
|
|
@@ -40,12 +39,15 @@ class PairwiseJoin(ABC):
|
|
|
40
39
|
def join_iter(
|
|
41
40
|
self,
|
|
42
41
|
df_a: pl.DataFrame | pl.LazyFrame,
|
|
43
|
-
df_b: pl.DataFrame | pl.LazyFrame
|
|
42
|
+
df_b: pl.DataFrame | pl.LazyFrame,
|
|
43
|
+
retain_index: bool = False,
|
|
44
44
|
) -> Iterator[pl.LazyFrame]:
|
|
45
45
|
"""Find all pairs of variants in two sources that meet a set of criteria.
|
|
46
46
|
|
|
47
47
|
:param df_a: Source dataframe.
|
|
48
48
|
:param df_b: Target dataframe.
|
|
49
|
+
:param retain_index: If True, do not drop an existing "_index" column in callset tables
|
|
50
|
+
if they exist.
|
|
49
51
|
|
|
50
52
|
:yields: A LazyFrame for each chunk.
|
|
51
53
|
"""
|
|
@@ -54,7 +56,8 @@ class PairwiseJoin(ABC):
|
|
|
54
56
|
def join(
|
|
55
57
|
self,
|
|
56
58
|
df_a: pl.DataFrame | pl.LazyFrame,
|
|
57
|
-
df_b: pl.DataFrame | pl.LazyFrame
|
|
59
|
+
df_b: pl.DataFrame | pl.LazyFrame,
|
|
60
|
+
retain_index: bool = False,
|
|
58
61
|
) -> pl.LazyFrame:
|
|
59
62
|
"""Find all pairs of variants in two sources that meet a set of criteria.
|
|
60
63
|
|
|
@@ -62,11 +65,13 @@ class PairwiseJoin(ABC):
|
|
|
62
65
|
|
|
63
66
|
:param df_a: Table A.
|
|
64
67
|
:param df_b: Table B.
|
|
68
|
+
:param retain_index: If True, do not drop an existing "_index" column in callset tables
|
|
69
|
+
if they exist.
|
|
65
70
|
|
|
66
71
|
:returns: A join table.
|
|
67
72
|
"""
|
|
68
73
|
return pl.concat(
|
|
69
|
-
self.join_iter(df_a, df_b)
|
|
74
|
+
self.join_iter(df_a, df_b, retain_index=retain_index)
|
|
70
75
|
)
|
|
71
76
|
|
|
72
77
|
@property
|
|
@@ -8,10 +8,14 @@ from collections.abc import (
|
|
|
8
8
|
Iterable,
|
|
9
9
|
Iterator,
|
|
10
10
|
Mapping,
|
|
11
|
+
MutableMapping,
|
|
11
12
|
)
|
|
12
13
|
import functools
|
|
13
14
|
import operator
|
|
14
|
-
from typing import
|
|
15
|
+
from typing import (
|
|
16
|
+
Any,
|
|
17
|
+
Optional,
|
|
18
|
+
)
|
|
15
19
|
from warnings import warn
|
|
16
20
|
|
|
17
21
|
import polars as pl
|
|
@@ -189,21 +193,26 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
189
193
|
def weight_expr(self):
|
|
190
194
|
return self.weight_strategy.expr
|
|
191
195
|
|
|
192
|
-
|
|
193
196
|
def join_iter(
|
|
194
197
|
self,
|
|
195
198
|
df_a: pl.DataFrame | pl.LazyFrame,
|
|
196
199
|
df_b: pl.DataFrame | pl.LazyFrame,
|
|
200
|
+
retain_index: bool = False,
|
|
197
201
|
) -> Iterator[pl.LazyFrame]:
|
|
198
202
|
"""Find all pairs of variants in two sources that meet a set of criteria.
|
|
199
203
|
|
|
200
204
|
:param df_a: Source dataframe.
|
|
201
205
|
:param df_b: Target dataframe.
|
|
206
|
+
:param retain_index: If True, do not drop an existing "_index" column in callset tables
|
|
207
|
+
if they exist.
|
|
202
208
|
|
|
203
209
|
:yields: A LazyFrame for each chunk.
|
|
204
210
|
"""
|
|
205
211
|
|
|
206
|
-
|
|
212
|
+
# Prepare tables
|
|
213
|
+
df_a, df_b = self._prepare_tables(df_a, df_b, warn_on_reserved=True, retain_index=retain_index)
|
|
214
|
+
|
|
215
|
+
if self.chunk_size == 0 or (self.is_equi_offset and self.chunk_size is None):
|
|
207
216
|
return self._join_iter_notchunked(df_a, df_b)
|
|
208
217
|
|
|
209
218
|
return self._join_iter_chunked(df_a, df_b)
|
|
@@ -215,22 +224,16 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
215
224
|
) -> Iterator[pl.LazyFrame]:
|
|
216
225
|
"""Find all pairs of variants in two sources that meet a set of criteria.
|
|
217
226
|
|
|
218
|
-
:param df_a: Source dataframe
|
|
219
|
-
:param df_b: Target dataframe
|
|
227
|
+
:param df_a: Source dataframe after `_prepare_tables()`.
|
|
228
|
+
:param df_b: Target dataframe after `_prepare_tables()`.
|
|
220
229
|
|
|
221
230
|
:yields: A LazyFrame for each chunk.
|
|
222
231
|
"""
|
|
223
|
-
|
|
224
|
-
# print('_join_iter_chunked(): Starting')
|
|
225
|
-
|
|
226
232
|
join_empty = True # Detects if no joins were written
|
|
227
233
|
|
|
228
234
|
chunk_range = self._get_chunk_range()
|
|
229
235
|
chunk_size = self.chunk_size if self.chunk_size is not None else DEFAULT_CHUNK_SIZE
|
|
230
236
|
|
|
231
|
-
# Prepare tables
|
|
232
|
-
df_a, df_b = self._prepare_tables(df_a, df_b, warn_on_reserved=True)
|
|
233
|
-
|
|
234
237
|
for chrom, last_index_a in (
|
|
235
238
|
df_a
|
|
236
239
|
.group_by('chrom_a')
|
|
@@ -308,18 +311,13 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
308
311
|
) -> Iterator[pl.LazyFrame]:
|
|
309
312
|
"""Find all pairs of variants in two sources that meet a set of criteria.
|
|
310
313
|
|
|
311
|
-
:param df_a: Source dataframe
|
|
312
|
-
:param df_b: Target dataframe
|
|
314
|
+
:param df_a: Source dataframe after `_prepare_tables()`.
|
|
315
|
+
:param df_b: Target dataframe after `_prepare_tables()`.
|
|
313
316
|
|
|
314
317
|
:yields: A LazyFrame for each chunk.
|
|
315
318
|
"""
|
|
316
|
-
# print('_join_iter_notchunked(): Starting')
|
|
317
|
-
|
|
318
319
|
join_empty = True # Detects if no joins were written
|
|
319
320
|
|
|
320
|
-
# Prepare tables
|
|
321
|
-
df_a, df_b = self._prepare_tables(df_a, df_b, warn_on_reserved=True)
|
|
322
|
-
|
|
323
321
|
chrom_list = sorted(
|
|
324
322
|
set(df_a.select('chrom_a').unique().collect().to_series().to_list())
|
|
325
323
|
| set(df_b.select('chrom_b').unique().collect().to_series().to_list())
|
|
@@ -415,7 +413,8 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
415
413
|
self,
|
|
416
414
|
df_a: pl.DataFrame | pl.LazyFrame,
|
|
417
415
|
df_b: pl.DataFrame | pl.LazyFrame,
|
|
418
|
-
|
|
416
|
+
retain_index: bool = False,
|
|
417
|
+
warn_on_reserved: bool = False,
|
|
419
418
|
) -> tuple[pl.LazyFrame, pl.LazyFrame]:
|
|
420
419
|
"""Prepares tables for join.
|
|
421
420
|
|
|
@@ -424,6 +423,8 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
424
423
|
|
|
425
424
|
:param df_a: Table A.
|
|
426
425
|
:param df_b: Table B.
|
|
426
|
+
:param retain_index: If True, do not drop an existing "_index" column in callset tables
|
|
427
|
+
if they exist.
|
|
427
428
|
:param warn_on_reserved: If True, generate a warning if reserved columns are found and
|
|
428
429
|
drop them. If false, raise an error.
|
|
429
430
|
|
|
@@ -460,25 +461,37 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
460
461
|
)
|
|
461
462
|
|
|
462
463
|
# Drop reserved columns
|
|
463
|
-
|
|
464
|
-
|
|
464
|
+
reserved_col_a = self.check_reserved_cols(columns_a)
|
|
465
|
+
reserved_col_b = self.check_reserved_cols(columns_b)
|
|
466
|
+
|
|
467
|
+
retain_index_a = '_index' in reserved_col_a and retain_index
|
|
468
|
+
retain_index_b = '_index' in reserved_col_b and retain_index
|
|
469
|
+
|
|
470
|
+
if retain_index:
|
|
471
|
+
reserved_col_a -= {'_index', }
|
|
472
|
+
reserved_col_b -= {'_index', }
|
|
473
|
+
|
|
474
|
+
if reserved_col_a:
|
|
475
|
+
reserved_col_a = sorted(reserved_col_a)
|
|
476
|
+
err_str = f'Reserved columns in table "A": {", ".join(reserved_col_a)}'
|
|
465
477
|
|
|
466
478
|
if warn_on_reserved:
|
|
467
479
|
warn(f'{err_str}: Dropping column(s)')
|
|
468
480
|
else:
|
|
469
481
|
raise ValueError(err_str)
|
|
470
482
|
|
|
471
|
-
df_a = df_a.drop(
|
|
483
|
+
df_a = df_a.drop(reserved_col_a, strict=False)
|
|
472
484
|
|
|
473
|
-
if
|
|
474
|
-
|
|
485
|
+
if reserved_col_b:
|
|
486
|
+
reserved_col_b = sorted(reserved_col_b)
|
|
487
|
+
err_str = f'Reserved columns in table "B": {", ".join(reserved_col_b)}'
|
|
475
488
|
|
|
476
489
|
if warn_on_reserved:
|
|
477
490
|
warn(f'{err_str}: Dropping column(s)')
|
|
478
491
|
else:
|
|
479
492
|
raise ValueError(err_str)
|
|
480
493
|
|
|
481
|
-
df_b = df_b.drop(
|
|
494
|
+
df_b = df_b.drop(reserved_col_b, strict=False)
|
|
482
495
|
|
|
483
496
|
# Cast columns
|
|
484
497
|
try:
|
|
@@ -526,15 +539,19 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
526
539
|
)
|
|
527
540
|
|
|
528
541
|
# Set index
|
|
529
|
-
|
|
530
|
-
df_a
|
|
531
|
-
|
|
532
|
-
|
|
542
|
+
if not retain_index_a:
|
|
543
|
+
df_a = (
|
|
544
|
+
df_a
|
|
545
|
+
.drop('_index', strict=False)
|
|
546
|
+
.with_row_index('_index')
|
|
547
|
+
)
|
|
533
548
|
|
|
534
|
-
|
|
535
|
-
df_b
|
|
536
|
-
|
|
537
|
-
|
|
549
|
+
if not retain_index_b:
|
|
550
|
+
df_b = (
|
|
551
|
+
df_b
|
|
552
|
+
.drop('_index', strict=False)
|
|
553
|
+
.with_row_index('_index')
|
|
554
|
+
)
|
|
538
555
|
|
|
539
556
|
# Set ID
|
|
540
557
|
if 'id' not in columns_a:
|
|
@@ -644,7 +661,7 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
644
661
|
def _append_join_cols(
|
|
645
662
|
self,
|
|
646
663
|
exprs: Iterable[pl.Expr | str] | pl.Expr | str,
|
|
647
|
-
join_expr_map:
|
|
664
|
+
join_expr_map: MutableMapping[str, Optional[pl.Expr]],
|
|
648
665
|
no_replace: bool = False,
|
|
649
666
|
) -> None:
|
|
650
667
|
"""Append expressions to the list of columns included in the join table.
|
|
@@ -660,7 +677,7 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
660
677
|
|
|
661
678
|
:param exprs: A join column or a list of join columns where each may be defined as a string
|
|
662
679
|
(known column name or a field already in the join table) or a Polars expression.
|
|
663
|
-
:param
|
|
680
|
+
:param join_expr_map: A dictionary to which the expressions will be added.
|
|
664
681
|
:param no_replace: Do not replace existing join column expressions if True.
|
|
665
682
|
"""
|
|
666
683
|
if isinstance(exprs, (pl.Expr, str)):
|
|
@@ -937,7 +954,7 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
937
954
|
@classmethod
|
|
938
955
|
def from_definiton(
|
|
939
956
|
cls,
|
|
940
|
-
overlap_def,
|
|
957
|
+
overlap_def: Mapping[str, Any] | Iterable[Mapping[str, Any]],
|
|
941
958
|
):
|
|
942
959
|
keys: Optional[set[str]] = None
|
|
943
960
|
|
|
@@ -959,12 +976,12 @@ class PairwiseOverlap(PairwiseJoin):
|
|
|
959
976
|
f'of stage definitions: {type(overlap_def)}'
|
|
960
977
|
)
|
|
961
978
|
|
|
962
|
-
overlap_def = {
|
|
979
|
+
overlap_def: Mapping[str, Any] | Iterable[Mapping[str, Any]] = {
|
|
963
980
|
'stages': stage_list,
|
|
964
981
|
}
|
|
965
982
|
|
|
966
983
|
# Parse arguments
|
|
967
|
-
parsed_def = {}
|
|
984
|
+
parsed_def: dict[str, Any] = {}
|
|
968
985
|
|
|
969
986
|
for key, value in overlap_def.items():
|
|
970
987
|
if key == 'stages':
|
|
@@ -38,6 +38,10 @@ class PairwiseOverlapStage():
|
|
|
38
38
|
:ivar match_ref: "REF" column must match between two variants.
|
|
39
39
|
:ivar match_alt: "ALT" column must match between two variants.
|
|
40
40
|
:ivar match_vartype: "VARTYPE" column must match between two variants.
|
|
41
|
+
:ivar match_filter_pass: If true, then matched variants must either pass filters ("filter"
|
|
42
|
+
column emtpy for both variants) or must fail filters ("filter" column non-empty for both
|
|
43
|
+
variants). This does not compare the contents of the "filter" column, just the presence or
|
|
44
|
+
absence of a filter.
|
|
41
45
|
:ivar match_prop_min: Minimum matched base proportion in alignment or None to not match.
|
|
42
46
|
"""
|
|
43
47
|
ro_min: Optional[float] = BoundedFloat(min_val=0.0, max_val=1.0)
|
|
@@ -82,6 +86,7 @@ class PairwiseOverlapStage():
|
|
|
82
86
|
self.match_ref = match_ref
|
|
83
87
|
self.match_alt = match_alt
|
|
84
88
|
self.match_vartype = match_vartype
|
|
89
|
+
self.match_filter_pass = match_filter_pass
|
|
85
90
|
self.seg_ro_min = seg_ro_min
|
|
86
91
|
self.match_prop_min = match_prop_min
|
|
87
92
|
|
|
@@ -185,6 +190,11 @@ class PairwiseOverlapStage():
|
|
|
185
190
|
pl.col('seg_ro') >= self.seg_ro_min
|
|
186
191
|
)
|
|
187
192
|
|
|
193
|
+
if self.match_filter_pass:
|
|
194
|
+
join_predicates_list.append(
|
|
195
|
+
(pl.col('filter_a').list.len() == 0) ^ (pl.col('filter_b').list.len() == 0)
|
|
196
|
+
)
|
|
197
|
+
|
|
188
198
|
if self.match_prop_min is not None and self.match_prop_min > 0.0:
|
|
189
199
|
join_filters_list.append(
|
|
190
200
|
pl.col('match_prop') >= self.match_prop_min
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|