catomatic 0.2.0__tar.gz → 0.2.1__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.
- {catomatic-0.2.0/src/catomatic.egg-info → catomatic-0.2.1}/PKG-INFO +1 -1
- {catomatic-0.2.0 → catomatic-0.2.1}/pyproject.toml +1 -1
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/BinaryCatalogue.py +84 -16
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/RegressionCatalogue.py +276 -180
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/cli.py +12 -11
- {catomatic-0.2.0 → catomatic-0.2.1/src/catomatic.egg-info}/PKG-INFO +1 -1
- {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/test_BuildBinaryCatalogue.py +25 -19
- {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/test_BuildRegressionCatalogue.py +85 -148
- {catomatic-0.2.0 → catomatic-0.2.1}/LICENSE +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/README.md +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/setup.cfg +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/PiezoTools.py +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/__init__.py +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/__main__.py +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/defence_module.py +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/SOURCES.txt +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/dependency_links.txt +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/entry_points.txt +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/not-zip-safe +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/requires.txt +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/top_level.txt +0 -0
- {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/__init__.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "catomatic"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "A tool for automatically building catalogues of antibiotic resistance-associated variants"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# If README.md is markdown, setuptools will pick it up; optionally:
|
|
@@ -27,7 +27,7 @@ class BinaryBuilder(PiezoExporter):
|
|
|
27
27
|
Required columns: ['UNIQUEID', 'MUTATION']
|
|
28
28
|
Optional columns: ['FRS']
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
frs (float, optional): The Fraction Read Support threshold used to construct the catalogues.
|
|
31
31
|
Lower FRS values allow for greater genotype heterogeneity.
|
|
32
32
|
|
|
33
33
|
seed (list) optional): A list of predefined GARC neutral mutations with associated phenotypes
|
|
@@ -140,10 +140,36 @@ class BinaryBuilder(PiezoExporter):
|
|
|
140
140
|
# If no more susceptible solos, classify all R and U solos in one, final sweep
|
|
141
141
|
self.classify(self.samples, self.mutations)
|
|
142
142
|
|
|
143
|
+
# add in rows for mutations without solos
|
|
144
|
+
# self.add_missing()
|
|
145
|
+
|
|
143
146
|
self.order_catalogue()
|
|
144
147
|
|
|
145
148
|
return self
|
|
146
149
|
|
|
150
|
+
def add_missing(self):
|
|
151
|
+
|
|
152
|
+
# add in remaining mutations
|
|
153
|
+
all_muts = set(self.mutations.MUTATION.dropna().unique())
|
|
154
|
+
classified_muts = set(self.catalogue.keys())
|
|
155
|
+
missing = all_muts - classified_muts
|
|
156
|
+
|
|
157
|
+
for mut in missing:
|
|
158
|
+
full_x = self.build_full_contingency(self.samples, self.mutations, mut)
|
|
159
|
+
|
|
160
|
+
proportion = self.calc_proportion(full_x)
|
|
161
|
+
ci = self.calc_confidence_interval(full_x)
|
|
162
|
+
|
|
163
|
+
data = {
|
|
164
|
+
"proportion": proportion,
|
|
165
|
+
"confidence": ci,
|
|
166
|
+
"full_contingency": full_x,
|
|
167
|
+
"solo_contingency": None, # explicitly absent
|
|
168
|
+
"note": "No solo evidence; added post hoc",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
self.add_mutation(mut, "U", data)
|
|
172
|
+
|
|
147
173
|
def classify(self, samples: pd.DataFrame, mutations: pd.DataFrame) -> None:
|
|
148
174
|
"""
|
|
149
175
|
Orchestrate one classification iteration over exposed 'solo' mutations.
|
|
@@ -196,22 +222,26 @@ class BinaryBuilder(PiezoExporter):
|
|
|
196
222
|
# Skip mutations with fewer than min_count samples
|
|
197
223
|
if solos[solos.MUTATION == mut].shape[0] < self.min_count:
|
|
198
224
|
return
|
|
199
|
-
# build a contingency table
|
|
200
|
-
x, ids = self.
|
|
225
|
+
# build a contingency table for solo mutations
|
|
226
|
+
x, ids = self.build_solo_contingency(solos, mut)
|
|
227
|
+
# build a contingency table for all observations
|
|
228
|
+
full_x = self.build_full_contingency(self.samples, self.mutations, mut)
|
|
201
229
|
# temporarily store mutation groups:
|
|
202
230
|
self.temp_ids = ids
|
|
203
231
|
|
|
204
232
|
# classify susceptible variants according to specified test mode
|
|
205
233
|
if self.test is None:
|
|
206
|
-
self.skeleton_build(mut, x)
|
|
234
|
+
self.skeleton_build(mut, x, full_x)
|
|
207
235
|
elif self.test == "Binomial":
|
|
208
|
-
self.binomial_build(mut, x)
|
|
236
|
+
self.binomial_build(mut, x, full_x)
|
|
209
237
|
elif self.test == "Fisher":
|
|
210
|
-
self.fishers_build(mut, x)
|
|
238
|
+
self.fishers_build(mut, x, full_x)
|
|
211
239
|
else:
|
|
212
240
|
raise ValueError(f"Unknown test mode: {self.test}")
|
|
213
241
|
|
|
214
|
-
def skeleton_build(
|
|
242
|
+
def skeleton_build(
|
|
243
|
+
self, mutation: str, x: Contingency, full_x: Contingency
|
|
244
|
+
) -> None:
|
|
215
245
|
"""
|
|
216
246
|
Record descriptive statistics and optionally mark susceptible solos.
|
|
217
247
|
Calls homogenous susceptible S.
|
|
@@ -227,7 +257,12 @@ class BinaryBuilder(PiezoExporter):
|
|
|
227
257
|
proportion = self.calc_proportion(x)
|
|
228
258
|
ci = self.calc_confidence_interval(x)
|
|
229
259
|
|
|
230
|
-
data = {
|
|
260
|
+
data = {
|
|
261
|
+
"proportion": proportion,
|
|
262
|
+
"confidence": ci,
|
|
263
|
+
"solo_contingency": x,
|
|
264
|
+
"contingency": full_x,
|
|
265
|
+
}
|
|
231
266
|
|
|
232
267
|
if self.run_iter:
|
|
233
268
|
# if iteratively classifing S variants
|
|
@@ -238,8 +273,12 @@ class BinaryBuilder(PiezoExporter):
|
|
|
238
273
|
# not phenotyping, just adding to catalogue
|
|
239
274
|
self.add_mutation(mutation, "U", data)
|
|
240
275
|
|
|
241
|
-
def binomial_build(
|
|
242
|
-
|
|
276
|
+
def binomial_build(
|
|
277
|
+
self, mutation: str, x: Contingency, full_x: Contingency
|
|
278
|
+
) -> None:
|
|
279
|
+
assert (
|
|
280
|
+
self.background is not None
|
|
281
|
+
), "background must be provided for Binomial test"
|
|
243
282
|
bg: float = float(self.background)
|
|
244
283
|
|
|
245
284
|
# p-value function for binomial
|
|
@@ -258,10 +297,11 @@ class BinaryBuilder(PiezoExporter):
|
|
|
258
297
|
def resistant_rule(proportion: float, p_calc: float, x) -> bool:
|
|
259
298
|
return bool(proportion > bg)
|
|
260
299
|
|
|
261
|
-
self.hypothesis_test(
|
|
262
|
-
|
|
300
|
+
self.hypothesis_test(
|
|
301
|
+
mutation, x, full_x, pvalue_fn, susceptible_rule, resistant_rule
|
|
302
|
+
)
|
|
263
303
|
|
|
264
|
-
def fishers_build(self, mutation: str, x: Contingency) -> None:
|
|
304
|
+
def fishers_build(self, mutation: str, x: Contingency, full_x: Contingency) -> None:
|
|
265
305
|
"""
|
|
266
306
|
Classify mutation using Fisher's exact test and directional inference.
|
|
267
307
|
|
|
@@ -291,12 +331,15 @@ class BinaryBuilder(PiezoExporter):
|
|
|
291
331
|
odds = self.calc_odds_ratio(x)
|
|
292
332
|
return odds > 1
|
|
293
333
|
|
|
294
|
-
self.hypothesis_test(
|
|
334
|
+
self.hypothesis_test(
|
|
335
|
+
mutation, x, full_x, pvalue_fn, susceptible_rule, resistant_rule
|
|
336
|
+
)
|
|
295
337
|
|
|
296
338
|
def hypothesis_test(
|
|
297
339
|
self,
|
|
298
340
|
mutation: str,
|
|
299
341
|
x: Contingency,
|
|
342
|
+
full_x: Contingency,
|
|
300
343
|
pvalue_fn: Callable[[Contingency], float],
|
|
301
344
|
susceptible_rule: Callable[[float, float, Contingency], bool],
|
|
302
345
|
resistant_rule: Callable[[float, float, Contingency], bool],
|
|
@@ -324,7 +367,8 @@ class BinaryBuilder(PiezoExporter):
|
|
|
324
367
|
"proportion": proportion,
|
|
325
368
|
"confidence": ci,
|
|
326
369
|
"p_value": p_calc,
|
|
327
|
-
"
|
|
370
|
+
"solo_contingency": x, # solo-based (classification)
|
|
371
|
+
"contingency": full_x, # (descriptive only)
|
|
328
372
|
}
|
|
329
373
|
|
|
330
374
|
# ITERATIVE MODE (we actively try to find susceptibles)
|
|
@@ -414,7 +458,7 @@ class BinaryBuilder(PiezoExporter):
|
|
|
414
458
|
return (lower, upper)
|
|
415
459
|
|
|
416
460
|
@staticmethod
|
|
417
|
-
def
|
|
461
|
+
def build_solo_contingency(
|
|
418
462
|
solos: pd.DataFrame, mutation: str
|
|
419
463
|
) -> Tuple[list[list[int]], list[str]]:
|
|
420
464
|
"""
|
|
@@ -438,6 +482,30 @@ class BinaryBuilder(PiezoExporter):
|
|
|
438
482
|
|
|
439
483
|
return [[R_count, S_count], [R_count_no_mut, S_count_no_mut]], ids
|
|
440
484
|
|
|
485
|
+
@staticmethod
|
|
486
|
+
def build_full_contingency(
|
|
487
|
+
samples: pd.DataFrame, mutations: pd.DataFrame, mutation: str
|
|
488
|
+
) -> list[list[int]]:
|
|
489
|
+
"""
|
|
490
|
+
Full contingency using ALL samples (sample-level, not row-level).
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
# determine which samples have the mutation
|
|
494
|
+
mut_present = mutations[mutations.MUTATION == mutation][
|
|
495
|
+
"UNIQUEID"
|
|
496
|
+
].drop_duplicates()
|
|
497
|
+
|
|
498
|
+
df = samples.copy()
|
|
499
|
+
df["has_mut"] = df["UNIQUEID"].isin(mut_present)
|
|
500
|
+
|
|
501
|
+
R_with = ((df.PHENOTYPE == "R") & (df.has_mut)).sum()
|
|
502
|
+
S_with = ((df.PHENOTYPE == "S") & (df.has_mut)).sum()
|
|
503
|
+
|
|
504
|
+
R_without = ((df.PHENOTYPE == "R") & (~df.has_mut)).sum()
|
|
505
|
+
S_without = ((df.PHENOTYPE == "S") & (~df.has_mut)).sum()
|
|
506
|
+
|
|
507
|
+
return [[int(R_with), int(S_with)], [int(R_without), int(S_without)]]
|
|
508
|
+
|
|
441
509
|
@staticmethod
|
|
442
510
|
def calc_odds_ratio(x: Contingency) -> float:
|
|
443
511
|
"""
|