zipstrain 0.2.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.
- zipstrain/__init__.py +7 -0
- zipstrain/cli.py +808 -0
- zipstrain/compare.py +377 -0
- zipstrain/database.py +871 -0
- zipstrain/profile.py +221 -0
- zipstrain/task_manager.py +1978 -0
- zipstrain/utils.py +451 -0
- zipstrain/visualize.py +586 -0
- zipstrain-0.2.4.dist-info/METADATA +27 -0
- zipstrain-0.2.4.dist-info/RECORD +12 -0
- zipstrain-0.2.4.dist-info/WHEEL +4 -0
- zipstrain-0.2.4.dist-info/entry_points.txt +3 -0
zipstrain/compare.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""zipstrain.compare
|
|
2
|
+
========================
|
|
3
|
+
This module provides all comparison functions for zipstrain.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
class PolarsANIExpressions:
|
|
10
|
+
"""
|
|
11
|
+
Any kind of ANI calculation based on two profiles should be implemented as a method of this class.
|
|
12
|
+
In defining this method, the following rules should be followed:
|
|
13
|
+
|
|
14
|
+
- The method returns a Polars expression (pl.Expr).
|
|
15
|
+
|
|
16
|
+
- When applied to a row, the method returns a zero if that position is a SNV. Otherwise it should return a number greater than zero.
|
|
17
|
+
|
|
18
|
+
- A, T, C, G columns in the first profile are named "A", "T", "C", "G" and in the second profile they are named "A_2", "T_2", "C_2", "G_2".
|
|
19
|
+
|
|
20
|
+
1. popani: Population ANI based on the shared alleles between two profiles.
|
|
21
|
+
2. conani: Consensus ANI based on the consensus alleles between two profiles.
|
|
22
|
+
3. cosani_<threshold>: Generalized cosine similarity ANI where threshold is a float value between 0 and 1. Once the similarity is below the threshold, it is considered a SNV.
|
|
23
|
+
"""
|
|
24
|
+
MPILE_1_BASES = ["A", "T", "C", "G"]
|
|
25
|
+
MPILE_2_BASES = ["A_2", "T_2", "C_2", "G_2"]
|
|
26
|
+
|
|
27
|
+
def popani(self):
|
|
28
|
+
return pl.col("A")*pl.col("A_2") + pl.col("C")*pl.col("C_2") + pl.col("G")*pl.col("G_2") + pl.col("T")*pl.col("T_2")
|
|
29
|
+
|
|
30
|
+
def conani(self):
|
|
31
|
+
max_base_1=pl.max_horizontal(*[pl.col(base) for base in self.MPILE_1_BASES])
|
|
32
|
+
max_base_2=pl.max_horizontal(*[pl.col(base) for base in self.MPILE_2_BASES])
|
|
33
|
+
return pl.when((pl.col("A")==max_base_1) & (pl.col("A_2")==max_base_2) |
|
|
34
|
+
(pl.col("T")==max_base_1) & (pl.col("T_2")==max_base_2) |
|
|
35
|
+
(pl.col("C")==max_base_1) & (pl.col("C_2")==max_base_2) |
|
|
36
|
+
(pl.col("G")==max_base_1) & (pl.col("G_2")==max_base_2)).then(1).otherwise(0)
|
|
37
|
+
|
|
38
|
+
def generalized_cos_ani(self,threshold:float=0.4):
|
|
39
|
+
dot_product = pl.col("A")*pl.col("A_2") + pl.col("C")*pl.col("C_2") + pl.col("G")*pl.col("G_2") + pl.col("T")*pl.col("T_2")
|
|
40
|
+
magnitude_1 = (pl.col("A")**2 + pl.col("C")**2 + pl.col("G")**2 + pl.col("T")**2)**0.5
|
|
41
|
+
magnitude_2 = (pl.col("A_2")**2 + pl.col("C_2")**2 + pl.col("G_2")**2 + pl.col("T_2")**2)**0.5
|
|
42
|
+
cos_sim = dot_product / (magnitude_1 * magnitude_2)
|
|
43
|
+
return pl.when(cos_sim >= threshold).then(1).otherwise(0)
|
|
44
|
+
|
|
45
|
+
def __getattribute__(self, name):
|
|
46
|
+
if name.startswith("cosani_"):
|
|
47
|
+
try:
|
|
48
|
+
threshold = float(name.split("_")[1])
|
|
49
|
+
except ValueError:
|
|
50
|
+
raise AttributeError(f"Invalid threshold in method name: {name}")
|
|
51
|
+
return lambda: self.generalized_cos_ani(threshold)
|
|
52
|
+
else:
|
|
53
|
+
return super().__getattribute__(name)
|
|
54
|
+
|
|
55
|
+
def coverage_filter(mpile_frame:pl.LazyFrame, min_cov:int,engine:str)-> pl.LazyFrame:
|
|
56
|
+
"""
|
|
57
|
+
Filter the mpile lazyframe based on minimum coverage at each loci.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
mpile_frame (pl.LazyFrame): The input LazyFrame containing coverage data.
|
|
61
|
+
min_cov (int): The minimum coverage threshold.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
pl.LazyFrame: Filtered LazyFrame with positions having coverage >= min_cov.
|
|
65
|
+
"""
|
|
66
|
+
mpile_frame = mpile_frame.with_columns(
|
|
67
|
+
(pl.col("A") + pl.col("C") + pl.col("G") + pl.col("T")).alias("cov")
|
|
68
|
+
)
|
|
69
|
+
return mpile_frame.filter(pl.col("cov") >= min_cov).collect(engine=engine).lazy()
|
|
70
|
+
|
|
71
|
+
def adjust_for_sequence_errors(mpile_frame:pl.LazyFrame, null_model:pl.LazyFrame) -> pl.LazyFrame:
|
|
72
|
+
"""
|
|
73
|
+
Adjust the mpile frame for sequence errors based on the null model.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
mpile_frame (pl.LazyFrame): The input LazyFrame containing coverage data.
|
|
77
|
+
null_model (pl.LazyFrame): The null model LazyFrame containing error counts.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
pl.LazyFrame: Adjusted LazyFrame with sequence errors accounted for.
|
|
81
|
+
"""
|
|
82
|
+
return mpile_frame.join(null_model, on="cov", how="left").with_columns([
|
|
83
|
+
pl.when(pl.col(base) >= pl.col("max_error_count"))
|
|
84
|
+
.then(pl.col(base))
|
|
85
|
+
.otherwise(0)
|
|
86
|
+
.alias(base)
|
|
87
|
+
for base in ["A", "T", "C", "G"]
|
|
88
|
+
]).drop("max_error_count")
|
|
89
|
+
|
|
90
|
+
def get_shared_locs(mpile_contig_1:pl.LazyFrame, mpile_contig_2:pl.LazyFrame,ani_method:str="popani") -> pl.LazyFrame:
|
|
91
|
+
"""
|
|
92
|
+
Returns a lazyframe with ATCG information for shared scaffolds and positions between two mpileup files.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
mpile_contig_1 (pl.LazyFrame): The first mpileup LazyFrame.
|
|
96
|
+
mpile_contig_2 (pl.LazyFrame): The second mpileup LazyFrame.
|
|
97
|
+
ani_method (str): The ANI calculation method to use. Default is "popani".
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
pl.LazyFrame: Merged LazyFrame containing shared scaffolds and positions with ATCG information.
|
|
101
|
+
"""
|
|
102
|
+
ani_expr=getattr(PolarsANIExpressions(), ani_method)()
|
|
103
|
+
|
|
104
|
+
mpile_contig= mpile_contig_1.join(
|
|
105
|
+
mpile_contig_2,
|
|
106
|
+
on=["chrom", "pos"],
|
|
107
|
+
how="inner",
|
|
108
|
+
suffix="_2" # To distinguish lf2 columns
|
|
109
|
+
).with_columns(
|
|
110
|
+
ani_expr.alias("surr")
|
|
111
|
+
).select(
|
|
112
|
+
pl.col("surr"),
|
|
113
|
+
scaffold=pl.col("chrom"),
|
|
114
|
+
pos=pl.col("pos"),
|
|
115
|
+
gene=pl.col("gene")
|
|
116
|
+
)
|
|
117
|
+
return mpile_contig
|
|
118
|
+
|
|
119
|
+
def add_contiguity_info(mpile_contig:pl.LazyFrame) -> pl.LazyFrame:
|
|
120
|
+
""" Adds group id information to the lazy frame. If on the same scaffold and not popANI, then they are in the same group.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
pl.LazyFrame: Updated LazyFrame with group id information added.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
mpile_contig= mpile_contig.sort(["scaffold", "pos"])
|
|
130
|
+
mpile_contig = mpile_contig.with_columns([
|
|
131
|
+
(pl.col("scaffold").shift(1).fill_null(pl.col("scaffold")).alias("prev_scaffold")),
|
|
132
|
+
])
|
|
133
|
+
mpile_contig = mpile_contig.with_columns([
|
|
134
|
+
(((pl.col("scaffold") != pl.col("prev_scaffold")) | (pl.col("surr") == 0))).cum_sum().alias("group_id")
|
|
135
|
+
])
|
|
136
|
+
return mpile_contig
|
|
137
|
+
|
|
138
|
+
def add_genome_info(mpile_contig:pl.LazyFrame, scaffold_to_genome:pl.LazyFrame) -> pl.LazyFrame:
|
|
139
|
+
"""
|
|
140
|
+
Adds genome information to the mpileup LazyFrame based on scaffold to genome mapping.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
144
|
+
scaffold_to_genome (pl.LazyFrame): The LazyFrame mapping scaffolds to genomes.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
pl.LazyFrame: Updated LazyFrame with genome information added.
|
|
148
|
+
"""
|
|
149
|
+
return mpile_contig.join(
|
|
150
|
+
scaffold_to_genome, on="scaffold", how="left"
|
|
151
|
+
).fill_null("NA")
|
|
152
|
+
|
|
153
|
+
def calculate_pop_ani(mpile_contig:pl.LazyFrame) -> pl.LazyFrame:
|
|
154
|
+
"""
|
|
155
|
+
Calculates the population ANI (Average Nucleotide Identity) for the given mpileup LazyFrame.
|
|
156
|
+
NOTE: Remember that this function should be applied to the merged mpileup using get_shared_locs.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
pl.LazyFrame: Updated LazyFrame with population ANI information added.
|
|
163
|
+
"""
|
|
164
|
+
return mpile_contig.group_by("genome").agg(
|
|
165
|
+
total_positions=pl.len(),
|
|
166
|
+
share_allele_pos=(pl.col("surr") > 0 ).sum()
|
|
167
|
+
).with_columns(
|
|
168
|
+
genome_pop_ani=pl.col("share_allele_pos")/pl.col("total_positions")*100,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def get_longest_consecutive_blocks(mpile_contig:pl.LazyFrame) -> pl.LazyFrame:
|
|
172
|
+
"""
|
|
173
|
+
Calculates the longest consecutive blocks for each genome in the mpileup LazyFrame for any genome.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
pl.LazyFrame: Updated LazyFrame with longest consecutive blocks information added.
|
|
180
|
+
"""
|
|
181
|
+
block_lengths = (
|
|
182
|
+
mpile_contig.group_by(["genome", "scaffold", "group_id"])
|
|
183
|
+
.agg(pl.len().alias("length"))
|
|
184
|
+
)
|
|
185
|
+
return block_lengths.group_by("genome").agg(pl.col("length").max().alias("max_consecutive_length"))
|
|
186
|
+
|
|
187
|
+
def get_gene_ani(mpile_contig:pl.LazyFrame, min_gene_compare_len:int) -> pl.LazyFrame:
|
|
188
|
+
"""
|
|
189
|
+
Calculates gene ANI (Average Nucleotide Identity) for each gene in each genome.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
193
|
+
min_gene_compare_len (int): Minimum length of the gene to consider for comparison.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
pl.LazyFrame: Updated LazyFrame with gene ANI information added.
|
|
197
|
+
"""
|
|
198
|
+
return mpile_contig.group_by(["genome", "gene"]).agg(
|
|
199
|
+
total_positions=pl.len(),
|
|
200
|
+
share_allele_pos=(pl.col("surr") > 0).sum()
|
|
201
|
+
).filter(pl.col("total_positions") >= min_gene_compare_len).with_columns(
|
|
202
|
+
identical=(pl.col("share_allele_pos") == pl.col("total_positions")),
|
|
203
|
+
).filter(pl.col("gene") != "NA").group_by("genome").agg(
|
|
204
|
+
shared_genes_count=pl.len(),
|
|
205
|
+
identical_gene_count=pl.col("identical").sum()
|
|
206
|
+
).with_columns(perc_id_genes=pl.col("identical_gene_count") / pl.col("shared_genes_count") * 100)
|
|
207
|
+
|
|
208
|
+
def get_unique_scaffolds(mpile_contig:pl.LazyFrame,batch_size:int=10000) -> set:
|
|
209
|
+
"""
|
|
210
|
+
Retrieves unique scaffolds from the mpileup LazyFrame.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
mpile_contig (pl.LazyFrame): The input LazyFrame containing mpileup data.
|
|
214
|
+
batch_size (int): The number of rows to process in each batch. Default is 10000.
|
|
215
|
+
Returns:
|
|
216
|
+
set: A set of unique scaffold names.
|
|
217
|
+
"""
|
|
218
|
+
scaffolds = set()
|
|
219
|
+
start_index = 0
|
|
220
|
+
while True:
|
|
221
|
+
batch = mpile_contig.slice(start_index, batch_size).select("chrom").collect()
|
|
222
|
+
if batch.height == 0:
|
|
223
|
+
break
|
|
224
|
+
scaffolds.update(batch["chrom"].to_list())
|
|
225
|
+
start_index += batch_size
|
|
226
|
+
return scaffolds
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def compare_genomes(mpile_contig_1:pl.LazyFrame,
|
|
230
|
+
mpile_contig_2:pl.LazyFrame,
|
|
231
|
+
null_model:pl.LazyFrame,
|
|
232
|
+
scaffold_to_genome:pl.LazyFrame,
|
|
233
|
+
min_cov:int=5,
|
|
234
|
+
min_gene_compare_len:int=100,
|
|
235
|
+
memory_mode:str="heavy",
|
|
236
|
+
chrom_batch_size:int=10000,
|
|
237
|
+
shared_scaffolds:list=None,
|
|
238
|
+
scaffold_scope:list=None,
|
|
239
|
+
engine="streaming",
|
|
240
|
+
ani_method:str="popani"
|
|
241
|
+
)-> pl.LazyFrame:
|
|
242
|
+
"""
|
|
243
|
+
Compares two profiles and generates genome-level comparison statistics.
|
|
244
|
+
The final output is a Polars LazyFrame with genome comparison statisticsin the following columns:
|
|
245
|
+
|
|
246
|
+
- genome: The genome identifier.
|
|
247
|
+
|
|
248
|
+
- total_positions: Total number of positions compared.
|
|
249
|
+
|
|
250
|
+
- share_allele_pos: Number of positions with shared alleles.
|
|
251
|
+
|
|
252
|
+
- genome_pop_ani: Population ANI percentage.
|
|
253
|
+
|
|
254
|
+
- max_consecutive_length: Length of the longest consecutive block of shared alleles.
|
|
255
|
+
|
|
256
|
+
- shared_genes_count: Number of genes compared.
|
|
257
|
+
|
|
258
|
+
- identical_gene_count: Number of identical genes.
|
|
259
|
+
|
|
260
|
+
- perc_id_genes: Percentage of identical genes.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
mpile_contig_1 (pl.LazyFrame): The first profile as a LazyFrame.
|
|
264
|
+
mpile_contig_2 (pl.LazyFrame): The second profile as a LazyFrame.
|
|
265
|
+
null_model (pl.LazyFrame): The null model LazyFrame that contains the thresholds for sequence error adjustment.
|
|
266
|
+
scaffold_to_genome (pl.LazyFrame): A mapping LazyFrame from scaffolds to genomes.
|
|
267
|
+
min_cov (int): Minimum coverage threshold for filtering positions. Default is 5.
|
|
268
|
+
min_gene_compare_len (int): Minimum length of genes that needs to be covered to consider for comparison. Default is 100.
|
|
269
|
+
memory_mode (str): Memory mode for processing. Options are "heavy" or "light". Default is "heavy".
|
|
270
|
+
chrom_batch_size (int): Batch size for processing scaffolds in light memory mode. Default
|
|
271
|
+
shared_scaffolds (list): List of shared scaffolds between the two profiles. Required for light memory mode.
|
|
272
|
+
scaffold_scope (list): List of scaffolds to limit the comparison to. Default is None.
|
|
273
|
+
engine (str): The Polars engine to use for computation. Default is "streaming".
|
|
274
|
+
ani_method (str): The ANI calculation method to use. Default is "popani".
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
pl.LazyFrame: A LazyFrame containing genome-level comparison statistics.
|
|
278
|
+
"""
|
|
279
|
+
if memory_mode == "heavy":
|
|
280
|
+
if scaffold_scope is not None:
|
|
281
|
+
mpile_contig_1 = mpile_contig_1.filter(pl.col("chrom").is_in(scaffold_scope)).collect(engine=engine).lazy()
|
|
282
|
+
mpile_contig_2 = mpile_contig_2.filter(pl.col("chrom").is_in(scaffold_scope)).collect(engine=engine).lazy()
|
|
283
|
+
lf1=coverage_filter(mpile_contig_1, min_cov,engine=engine)
|
|
284
|
+
lf1=adjust_for_sequence_errors(lf1, null_model)
|
|
285
|
+
lf2=coverage_filter(mpile_contig_2, min_cov,engine=engine)
|
|
286
|
+
lf2=adjust_for_sequence_errors(lf2, null_model)
|
|
287
|
+
### Now we need to only keep (scaffold, pos) that are in both lf1 and lf2
|
|
288
|
+
lf = get_shared_locs(lf1, lf2, ani_method=ani_method)
|
|
289
|
+
## Add Contiguity Information
|
|
290
|
+
lf = add_contiguity_info(lf)
|
|
291
|
+
## Let's add genome information for all scaffolds and positions
|
|
292
|
+
lf = add_genome_info(lf, scaffold_to_genome)
|
|
293
|
+
## Let's calculate popANI
|
|
294
|
+
genome_comp= calculate_pop_ani(lf)
|
|
295
|
+
## Calculate longest consecutive blocks
|
|
296
|
+
max_consecutive_per_genome = get_longest_consecutive_blocks(lf)
|
|
297
|
+
## Calculate gene ani for each gene in each genome
|
|
298
|
+
gene= get_gene_ani(lf, min_gene_compare_len)
|
|
299
|
+
genome_comp=genome_comp.join(max_consecutive_per_genome, on="genome", how="left")
|
|
300
|
+
genome_comp=genome_comp.join(gene, on="genome", how="left")
|
|
301
|
+
|
|
302
|
+
elif memory_mode == "light":
|
|
303
|
+
shared_scaffolds_batches = [shared_scaffolds[i:i + chrom_batch_size] for i in range(0, len(shared_scaffolds), chrom_batch_size)]
|
|
304
|
+
lf_list=[]
|
|
305
|
+
for scaffold in shared_scaffolds_batches:
|
|
306
|
+
lf1= coverage_filter(mpile_contig_1.filter(pl.col("chrom").is_in(scaffold)), min_cov)
|
|
307
|
+
lf1=adjust_for_sequence_errors(lf1, null_model)
|
|
308
|
+
lf2= coverage_filter(mpile_contig_2.filter(pl.col("chrom").is_in(scaffold)), min_cov)
|
|
309
|
+
lf2=adjust_for_sequence_errors(lf2, null_model)
|
|
310
|
+
### Now we need to only keep (scaffold, pos) that are in both lf1 and lf2
|
|
311
|
+
lf = get_shared_locs(lf1, lf2, ani_method=ani_method)
|
|
312
|
+
## Lets add contiguity information
|
|
313
|
+
lf= add_contiguity_info(lf)
|
|
314
|
+
lf_list.append(lf)
|
|
315
|
+
lf= pl.concat(lf_list)
|
|
316
|
+
lf= add_genome_info(lf, scaffold_to_genome)
|
|
317
|
+
genome_comp= calculate_pop_ani(lf)
|
|
318
|
+
max_consecutive_per_genome = get_longest_consecutive_blocks(lf)
|
|
319
|
+
gene= get_gene_ani(lf, min_gene_compare_len)
|
|
320
|
+
genome_comp=genome_comp.join(max_consecutive_per_genome, on="genome", how="left")
|
|
321
|
+
genome_comp=genome_comp.join(gene, on="genome", how="left")
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError("Invalid memory_mode. Choose either 'heavy' or 'light'.")
|
|
324
|
+
return genome_comp
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def compare_genes(mpile_contig_1:pl.LazyFrame,
|
|
329
|
+
mpile_contig_2:pl.LazyFrame,
|
|
330
|
+
null_model:pl.LazyFrame,
|
|
331
|
+
scaffold_to_genome:pl.LazyFrame,
|
|
332
|
+
min_cov:int=5,
|
|
333
|
+
min_gene_compare_len:int=100,
|
|
334
|
+
engine="streaming",
|
|
335
|
+
ani_method:str="popani"
|
|
336
|
+
)-> pl.LazyFrame:
|
|
337
|
+
"""
|
|
338
|
+
Compares two profiles and generates gene-level comparison statistics.
|
|
339
|
+
The final output is a Polars LazyFrame with gene comparison statistics in the following columns:
|
|
340
|
+
- genome: The genome identifier.
|
|
341
|
+
- gene: The gene identifier.
|
|
342
|
+
- total_positions: Total number of positions compared in the gene.
|
|
343
|
+
- share_allele_pos: Number of positions with shared alleles in the gene.
|
|
344
|
+
- ani: Average Nucleotide Identity (ANI) percentage for the gene.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
mpile_contig_1 (pl.LazyFrame): The first profile as a LazyFrame.
|
|
348
|
+
mpile_contig_2 (pl.LazyFrame): The second profile as a LazyFrame.
|
|
349
|
+
null_model (pl.LazyFrame): The null model LazyFrame that contains the thresholds for sequence error adjustment.
|
|
350
|
+
scaffold_to_genome (pl.LazyFrame): A mapping LazyFrame from scaffolds to genomes.
|
|
351
|
+
min_cov (int): Minimum coverage threshold for filtering positions. Default is 5.
|
|
352
|
+
min_gene_compare_len (int): Minimum length of genes that needs to be covered to consider for comparison. Default is 100.
|
|
353
|
+
engine (str): The Polars engine to use for computation. Default is "streaming".
|
|
354
|
+
ani_method (str): The ANI calculation method to use. Default is "popani".
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
pl.LazyFrame: A LazyFrame containing gene-level comparison statistics.
|
|
358
|
+
"""
|
|
359
|
+
lf1=coverage_filter(mpile_contig_1, min_cov,engine=engine)
|
|
360
|
+
lf1=adjust_for_sequence_errors(lf1, null_model)
|
|
361
|
+
lf2=coverage_filter(mpile_contig_2, min_cov,engine=engine)
|
|
362
|
+
lf2=adjust_for_sequence_errors(lf2, null_model)
|
|
363
|
+
### Now we need to only keep (scaffold, pos) that are in both lf1 and lf2
|
|
364
|
+
lf = get_shared_locs(lf1, lf2, ani_method=ani_method)
|
|
365
|
+
## Let's add genome information for all scaffolds and positions
|
|
366
|
+
lf = add_genome_info(lf, scaffold_to_genome)
|
|
367
|
+
## Let's calculate gene ani for each gene in each genome
|
|
368
|
+
gene_comp = lf.group_by(["genome", "gene"]).agg(
|
|
369
|
+
total_positions=pl.len(),
|
|
370
|
+
share_allele_pos=(pl.col("surr") > 0).sum()
|
|
371
|
+
).filter(pl.col("total_positions") >= min_gene_compare_len).with_columns(
|
|
372
|
+
ani=pl.col("share_allele_pos") / pl.col("total_positions") * 100,
|
|
373
|
+
)
|
|
374
|
+
return gene_comp
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|