agglovar 0.0.1.dev12__tar.gz → 0.0.1.dev13__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.
Files changed (59) hide show
  1. {agglovar-0.0.1.dev12/src/agglovar.egg-info → agglovar-0.0.1.dev13}/PKG-INFO +2 -1
  2. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/pyproject.toml +1 -0
  3. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/__init__.py +4 -2
  4. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/align/score.py +2 -2
  5. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/bed/col.py +1 -4
  6. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/bed/join.py +32 -0
  7. agglovar-0.0.1.dev13/src/agglovar/expr/__init__.py +7 -0
  8. agglovar-0.0.1.dev13/src/agglovar/expr/variant.py +61 -0
  9. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/base.py +12 -11
  10. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/cumulative.py +20 -17
  11. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/overlap/_const.py +3 -2
  12. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/overlap/_overlap.py +158 -103
  13. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/overlap/_stage.py +27 -41
  14. agglovar-0.0.1.dev13/src/agglovar/schema.py +38 -0
  15. agglovar-0.0.1.dev13/src/agglovar/vcf/__init__.py +18 -0
  16. agglovar-0.0.1.dev13/src/agglovar/vcf/_vcf_const.py +72 -0
  17. agglovar-0.0.1.dev13/src/agglovar/vcf/_vcf_header.py +909 -0
  18. agglovar-0.0.1.dev13/src/agglovar/vcf/_vcf_read.py +664 -0
  19. agglovar-0.0.1.dev13/src/agglovar/vcf/_vcf_write.py +52 -0
  20. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13/src/agglovar.egg-info}/PKG-INFO +2 -1
  21. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar.egg-info/SOURCES.txt +8 -1
  22. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar.egg-info/requires.txt +1 -0
  23. agglovar-0.0.1.dev12/src/agglovar/schema.py +0 -30
  24. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/LICENSE +0 -0
  25. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/README.md +0 -0
  26. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/setup.cfg +0 -0
  27. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/align/__init__.py +0 -0
  28. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/align/op.py +0 -0
  29. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/bed/__init__.py +0 -0
  30. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/bed/intersect.py +0 -0
  31. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/bed/merge.py +0 -0
  32. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/dev/__init__.py +0 -0
  33. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/dev/imports.py +0 -0
  34. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/fa.py +0 -0
  35. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/io.py +0 -0
  36. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/kmer/__init__.py +0 -0
  37. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/kmer/plot.py +0 -0
  38. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/kmer/util.py +0 -0
  39. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/__init__.py +0 -0
  40. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/config/__init__.py +0 -0
  41. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/config/parser.py +0 -0
  42. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/config/stage.py +0 -0
  43. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/config/strategy.py +0 -0
  44. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/om/__init__.py +0 -0
  45. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/om/_executor_base.py +0 -0
  46. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/merge/om/_executor_nr.py +0 -0
  47. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/meta/__init__.py +0 -0
  48. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/meta/decorators.py +0 -0
  49. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/meta/descriptors.py +0 -0
  50. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/__init__.py +0 -0
  51. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/base.py +0 -0
  52. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/overlap/__init__.py +0 -0
  53. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/pairwise/weights.py +0 -0
  54. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/seqmatch.py +0 -0
  55. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/util/__init__.py +0 -0
  56. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/util/str.py +0 -0
  57. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar/util/var.py +0 -0
  58. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar.egg-info/dependency_links.txt +0 -0
  59. {agglovar-0.0.1.dev12 → agglovar-0.0.1.dev13}/src/agglovar.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agglovar
3
- Version: 0.0.1.dev12
3
+ Version: 0.0.1.dev13
4
4
  Summary: Toolkit for fast genomic variant transformations and intersects
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.12
@@ -12,6 +12,7 @@ Requires-Dist: intervaltree>=3.1.0
12
12
  Requires-Dist: numpy>=2.3.0
13
13
  Requires-Dist: ply>=3.11
14
14
  Requires-Dist: polars[numpy,pandas,pyarrow]>=1.35.2
15
+ Requires-Dist: pysam>=0.23.3
15
16
  Provides-Extra: plot
16
17
  Requires-Dist: matplotlib>=3.10.3; extra == "plot"
17
18
  Provides-Extra: all
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "numpy>=2.3.0",
18
18
  "ply>=3.11",
19
19
  "polars[numpy,pandas,pyarrow]>=1.35.2",
20
+ "pysam>=0.23.3",
20
21
  ]
21
22
 
22
23
  [agglovar.urls]
@@ -1,6 +1,6 @@
1
1
  """Agglovar: A toolkit for fast genomic variant transformations and intersects."""
2
2
 
3
- __version__ = '0.0.1.dev12'
3
+ __version__ = '0.0.1.dev13'
4
4
 
5
5
  __all__ = [
6
6
  'align',
@@ -13,6 +13,7 @@ __all__ = [
13
13
  'meta',
14
14
  'schema',
15
15
  'seqmatch',
16
+ 'vcf',
16
17
  'util',
17
18
  ]
18
19
 
@@ -26,4 +27,5 @@ from . import merge
26
27
  from . import meta
27
28
  from . import schema
28
29
  from . import seqmatch
29
- from . import util
30
+ from . import vcf
31
+ from . import util
@@ -128,7 +128,7 @@ class ScoreModel(ABC):
128
128
  self,
129
129
  op_arr: np.ndarray,
130
130
  ) -> float:
131
- """A vectorized implementation of summing scores for affine models.
131
+ """Sum scores for affine models using a vectorized implementation.
132
132
 
133
133
  :param op_arr: Array of alignment operations (op_code: first column, op_len: second column).
134
134
 
@@ -285,7 +285,7 @@ class AffineScoreModel(ScoreModel):
285
285
  self,
286
286
  op_arr: np.ndarray,
287
287
  ) -> float:
288
- """A vectorized implementation of summing scores for affine models.
288
+ """Sum scores for affine models using a A vectorized implementation.
289
289
 
290
290
  :param op_arr: Array of alignment operations (op_code: first column, op_len: second column).
291
291
 
@@ -123,7 +123,6 @@ def get_coord_cols(
123
123
 
124
124
  :return: An object with column names.
125
125
  """
126
-
127
126
  if isinstance(col_names, CoordCol):
128
127
  return col_names
129
128
 
@@ -137,7 +136,7 @@ def get_coord_cols(
137
136
  else:
138
137
  col_names_tuple = tuple((
139
138
  col.strip() if col is not None else None
140
- for col in col_names
139
+ for col in col_names
141
140
  ))
142
141
 
143
142
  if len(col_names_tuple) != 3:
@@ -181,7 +180,6 @@ def make_unique_col(
181
180
 
182
181
  :return: Unique column name.
183
182
  """
184
-
185
183
  new_col = col
186
184
  i = 0
187
185
 
@@ -189,5 +187,4 @@ def make_unique_col(
189
187
  i += 1
190
188
  new_col = f'{col}_{i}'
191
189
 
192
-
193
190
  return new_col
@@ -192,6 +192,8 @@ def _join_chunks(
192
192
  col_a = join_resources.col_a
193
193
  col_b = join_resources.col_b
194
194
 
195
+ yield_count = 0
196
+
195
197
  for chrom, last_index_a in (
196
198
  df_a
197
199
  .group_by(col_a.chrom)
@@ -275,8 +277,38 @@ def _join_chunks(
275
277
 
276
278
  start_index_b = end_index_b
277
279
 
280
+ yield_count += 1
281
+
278
282
  start_index_a = end_index_a
279
283
 
284
+ if yield_count == 0:
285
+ # Iterator should always return at least one chunk, even if empty. Prevents pl.concat from failing.
286
+ yield (
287
+ df_a.filter(False)
288
+ .join(
289
+ df_b.filter(False),
290
+ left_on=col_a.chrom,
291
+ right_on=col_b.chrom,
292
+ how='inner',
293
+ )
294
+ .filter(
295
+ pl.col(col_b.pos) - distance <= pl.col(col_a.end),
296
+ pl.col(col_b.end) + distance >= pl.col(col_a.pos),
297
+ )
298
+ .select(
299
+ pl.col('_index_a').alias('index_a'),
300
+ pl.col('_index_b').alias('index_b'),
301
+ pl.col(col_a.chrom).alias('chrom'),
302
+ pl.max_horizontal(col_a.pos, col_b.pos).alias('pos'),
303
+ pl.min_horizontal(col_a.end, col_b.end).alias('end'),
304
+ )
305
+ .with_columns(
306
+ pl.min_horizontal('pos', 'end').alias('pos'),
307
+ pl.max_horizontal('pos', 'end').alias('end'),
308
+ (pl.col('pos') - pl.col('end')).alias('distance')
309
+ )
310
+ ).collect().lazy()
311
+
280
312
 
281
313
  def pairwise_join_tree(
282
314
  df_a: pl.LazyFrame | pl.DataFrame,
@@ -0,0 +1,7 @@
1
+ """Commonly used expressions."""
2
+
3
+ __all__ = [
4
+ 'variant',
5
+ ]
6
+
7
+ from . import variant
@@ -0,0 +1,61 @@
1
+ """Polars expressions used by variant calling routines."""
2
+
3
+ __all__ = [
4
+ 'id_snv_expr',
5
+ 'id_nonsnv_expr',
6
+ 'sort_cols',
7
+ 'id_expr_noalias',
8
+ 'id_expr',
9
+ ]
10
+
11
+ import polars as pl
12
+
13
+ id_snv_expr = (
14
+ pl.concat_str(
15
+ pl.col('chrom'),
16
+ pl.lit('-'),
17
+ pl.col('pos') + 1,
18
+ pl.lit('-SNV-'),
19
+ pl.col('ref').str.to_uppercase(),
20
+ pl.col('alt').str.to_uppercase(),
21
+ )
22
+ )
23
+ """Expression for generating SNV IDs."""
24
+
25
+
26
+ id_nonsnv_expr = (
27
+ pl.concat_str(
28
+ pl.col('chrom'),
29
+ pl.lit('-'),
30
+ (pl.col('pos') + 1).cast(pl.String),
31
+ pl.lit('-'),
32
+ pl.col('vartype').str.to_uppercase(),
33
+ pl.lit('-'),
34
+ pl.col('varlen').cast(pl.String)
35
+ )
36
+ )
37
+ """Expression for generating non-SNV IDs."""
38
+
39
+
40
+ sort_cols = (
41
+ pl.col('chrom'),
42
+ pl.col('pos'),
43
+ pl.col('end'),
44
+ pl.col('filter').list.len(),
45
+ pl.coalesce(
46
+ pl.col('^id$'),
47
+ pl.lit(None),
48
+ )
49
+ )
50
+ """Sort columns for variant tables."""
51
+
52
+
53
+ id_expr_noalias = (
54
+ pl.when(pl.col('vartype').str.to_uppercase() == 'SNV')
55
+ .then(id_snv_expr)
56
+ .otherwise(id_nonsnv_expr)
57
+ )
58
+ """Expression for generating variant IDs."""
59
+
60
+ id_expr = id_expr_noalias.alias('id')
61
+ """Expression for generating variant IDs including alias "id" for the result (ideal for `with_column()`)."""
@@ -58,7 +58,7 @@ class MergeBase(ABC):
58
58
  def get_intersect_tuples(
59
59
  callsets: Iterable[CallsetDefType],
60
60
  retain_index: bool = False,
61
- pre_filter: Optional[Iterable[pl.Expr]] = None,
61
+ pre_filter: Optional[Iterable[pl.Expr] | pl.Expr] = None,
62
62
  ) -> list[tuple[pl.LazyFrame, str, int]]:
63
63
  """
64
64
  Transform input arguments to a list of tuples with set fields.
@@ -92,7 +92,10 @@ class MergeBase(ABC):
92
92
 
93
93
  i = 0
94
94
 
95
- pre_filter = list(pre_filter if pre_filter is not None else [])
95
+ if isinstance(pre_filter, pl.Expr):
96
+ pre_filter = [pre_filter]
97
+ else:
98
+ pre_filter = list(pre_filter if pre_filter is not None else [])
96
99
 
97
100
  if callsets is None:
98
101
  raise ValueError('Missing callsets')
@@ -143,15 +146,13 @@ class MergeBase(ABC):
143
146
  .with_columns(
144
147
  pl.coalesce(
145
148
  pl.col(r'^id$'), # Existing ID if present
146
- pl.concat_str(pl.lit('var'), pl.col('_mg_src_index')) # Fill missing/null
147
- ).cast(pl.String).alias('id')
148
- )
149
- .with_columns(
150
- (
151
- pl.when(pl.col('id').is_null())
152
- .then(pl.concat_str(pl.lit('var'), pl.col('_mg_src_index')))
153
- .otherwise(pl.col('id'))
154
- ).cast(pl.String).alias('id')
149
+ pl.lit(None)
150
+ )
151
+ .cast(pl.String)
152
+ .fill_null(
153
+ pl.concat_str(pl.lit('var'), pl.col('_mg_src_index'))
154
+ )
155
+ .alias('id')
155
156
  )
156
157
  .filter(
157
158
  *pre_filter
@@ -114,7 +114,12 @@ def _get_match(
114
114
  df_cumulative
115
115
  .with_row_index('_index')
116
116
  .join(
117
- df_join.lazy().select(pl.all().name.prefix('_mg_join_')),
117
+ (
118
+ df_join.lazy()
119
+ .select(
120
+ pl.all().name.prefix('_mg_join_')
121
+ )
122
+ ),
118
123
  left_on='_index', right_on='_mg_join_index_a', how='inner'
119
124
  )
120
125
  .with_columns(
@@ -164,7 +169,7 @@ class MergeCumulative(MergeBase):
164
169
  self,
165
170
  callsets: Iterable[CallsetDefType],
166
171
  retain_index: bool = False,
167
- pre_filter: Optional[Iterable[pl.Expr]] = None,
172
+ pre_filter: Optional[Iterable[pl.Expr] | pl.Expr] = None,
168
173
  sort: bool = True,
169
174
  add_id: bool = True,
170
175
  ) -> pl.LazyFrame:
@@ -196,21 +201,20 @@ class MergeCumulative(MergeBase):
196
201
  if col not in all_col_dict:
197
202
  all_col_dict[col] = dtype
198
203
 
199
- required_cols = [col for col in all_col_dict.keys() if col in required_cols] # To list in order found in tables
200
-
201
- # Initialize cumulative table
202
- df_next, src_name, src_index = callsets[0]
203
-
204
- merge_stat_cols = { # Empty join, gets names of columns this join will return
205
- col: dtype for col, dtype in self.pairwise_join.join(df_next.head(0), df_next.head(0)).collect_schema().items()
206
- if col not in {'index_a', 'index_b', 'id_a', 'id_b'}
207
- }
204
+ # Build reqired_cols from expected columns (includes optional columns), preserve order found in tables
205
+ required_cols = [col for col in all_col_dict.keys() if col in set(self.pairwise_join.expected_cols)]
208
206
 
209
207
  # Cumulative merge
210
208
  df_cumulative = None
209
+ merge_stat_cols = None
211
210
 
212
211
  for df_next, src_name, src_index in callsets:
213
212
  if df_cumulative is None:
213
+ merge_stat_cols = { # Empty join, gets names of columns this join will return
214
+ col: dtype for col, dtype in self.pairwise_join.join(df_next.head(0), df_next.head(0)).collect_schema().items()
215
+ if col not in {'index_a', 'index_b', 'id_a', 'id_b'}
216
+ }
217
+
214
218
  df_cumulative = (
215
219
  _init_cumulative(df_next, src_index, src_name, required_cols, merge_stat_cols)
216
220
  .sort('chrom', 'pos', 'end', 'id')
@@ -224,6 +228,7 @@ class MergeCumulative(MergeBase):
224
228
  df_join = (
225
229
  (
226
230
  self.pairwise_join.join(df_cumulative, df_next)
231
+ .sort('weight', descending=True)
227
232
  .unique('index_a', keep='first')
228
233
  .unique('index_b', keep='first')
229
234
  .sort('index_a', 'index_b')
@@ -266,6 +271,9 @@ class MergeCumulative(MergeBase):
266
271
  _init_cumulative(
267
272
  (
268
273
  df_next
274
+ # .with_columns(
275
+ # pl.col('index_b').alias('_mg_src_index')
276
+ # )
269
277
  .join(
270
278
  df_join.lazy(),
271
279
  left_on='_mg_src_index', right_on='index_b', how='anti'
@@ -276,13 +284,8 @@ class MergeCumulative(MergeBase):
276
284
  )
277
285
 
278
286
  df_cumulative = (
279
- pl.concat([
280
- df_match.drop('_mg_src_index', strict=False),
281
- df_nomatch.drop('_mg_src_index', strict=False),
282
- df_new.drop('_mg_src_index', strict=False),
283
- ])
287
+ pl.concat([df_match, df_nomatch, df_new])
284
288
  .sort('chrom', 'pos', 'end')
285
- .with_row_index('_mg_src_index')
286
289
  .collect()
287
290
  .lazy()
288
291
  )
@@ -33,8 +33,9 @@ AUTOGEN_COLS: frozenset[str] = frozenset({
33
33
 
34
34
  DEFAULT_CHUNK_SIZE: int = 1_500
35
35
  """
36
- Default size to chunk tables before joining. A value of 10,000 works well to balance combinatorial
37
- explosion without exploding the number of chunks to merge when overlapping large variant tables.
36
+ Default number of df_a variants per chunk. Chunking controls peak memory and determines the batch
37
+ size presented to the sequence-alignment thread pool larger values amortize thread-pool overhead
38
+ better but use more memory. Tune alongside ``n_threads`` based on available RAM and CPU count.
38
39
  """
39
40
 
40
41
  EXPR_OVERLAP_RO = (