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.
Files changed (22) hide show
  1. {catomatic-0.2.0/src/catomatic.egg-info → catomatic-0.2.1}/PKG-INFO +1 -1
  2. {catomatic-0.2.0 → catomatic-0.2.1}/pyproject.toml +1 -1
  3. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/BinaryCatalogue.py +84 -16
  4. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/RegressionCatalogue.py +276 -180
  5. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/cli.py +12 -11
  6. {catomatic-0.2.0 → catomatic-0.2.1/src/catomatic.egg-info}/PKG-INFO +1 -1
  7. {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/test_BuildBinaryCatalogue.py +25 -19
  8. {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/test_BuildRegressionCatalogue.py +85 -148
  9. {catomatic-0.2.0 → catomatic-0.2.1}/LICENSE +0 -0
  10. {catomatic-0.2.0 → catomatic-0.2.1}/README.md +0 -0
  11. {catomatic-0.2.0 → catomatic-0.2.1}/setup.cfg +0 -0
  12. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/PiezoTools.py +0 -0
  13. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/__init__.py +0 -0
  14. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/__main__.py +0 -0
  15. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic/defence_module.py +0 -0
  16. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/SOURCES.txt +0 -0
  17. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/dependency_links.txt +0 -0
  18. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/entry_points.txt +0 -0
  19. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/not-zip-safe +0 -0
  20. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/requires.txt +0 -0
  21. {catomatic-0.2.0 → catomatic-0.2.1}/src/catomatic.egg-info/top_level.txt +0 -0
  22. {catomatic-0.2.0 → catomatic-0.2.1}/src/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: catomatic
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A tool for automatically building catalogues of antibiotic resistance-associated variants
5
5
  Author: Dylan Adlard
6
6
  Author-email: Philip W Fowler <philip.fowler@ndm.ox.ac.uk>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "catomatic"
7
- version = "0.2.0"
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
- FRS (float, optional): The Fraction Read Support threshold used to construct the catalogues.
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.build_contingency(solos, mut)
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(self, mutation: str, x: Contingency) -> None:
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 = {"proportion": proportion, "confidence": ci, "contingency": x}
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(self, mutation: str, x: Contingency) -> None:
242
- assert self.background is not None, "background must be provided for Binomial test"
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(mutation, x, pvalue_fn, susceptible_rule, resistant_rule)
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(mutation, x, pvalue_fn, susceptible_rule, resistant_rule)
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
- "contingency": x,
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 build_contingency(
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
  """