catomatic 0.2.0__tar.gz → 0.2.3__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 → catomatic-0.2.3}/LICENSE +0 -0
  2. {catomatic-0.2.0/src/catomatic.egg-info → catomatic-0.2.3}/PKG-INFO +23 -16
  3. {catomatic-0.2.0 → catomatic-0.2.3}/README.md +22 -15
  4. {catomatic-0.2.0 → catomatic-0.2.3}/pyproject.toml +1 -1
  5. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/BinaryCatalogue.py +84 -16
  6. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/PiezoTools.py +0 -0
  7. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/RegressionCatalogue.py +335 -216
  8. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/__init__.py +0 -0
  9. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/__main__.py +0 -0
  10. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/cli.py +13 -12
  11. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic/defence_module.py +0 -0
  12. {catomatic-0.2.0 → catomatic-0.2.3/src/catomatic.egg-info}/PKG-INFO +23 -16
  13. {catomatic-0.2.0 → catomatic-0.2.3}/src/tests/__init__.py +0 -0
  14. {catomatic-0.2.0 → catomatic-0.2.3}/src/tests/test_BuildBinaryCatalogue.py +25 -19
  15. {catomatic-0.2.0 → catomatic-0.2.3}/src/tests/test_BuildRegressionCatalogue.py +88 -163
  16. {catomatic-0.2.0 → catomatic-0.2.3}/setup.cfg +0 -0
  17. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/SOURCES.txt +0 -0
  18. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/dependency_links.txt +0 -0
  19. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/entry_points.txt +0 -0
  20. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/not-zip-safe +0 -0
  21. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/requires.txt +0 -0
  22. {catomatic-0.2.0 → catomatic-0.2.3}/src/catomatic.egg-info/top_level.txt +0 -0
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: catomatic
3
- Version: 0.2.0
3
+ Version: 0.2.3
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>
@@ -48,15 +48,15 @@ The first is used in [https://doi.org/10.1101/2025.01.30.635633](https://doi.org
48
48
 
49
49
  This method relies on the logic that mutations that do not cause resistance can co-occur with those that do. If a mutation in isolation (solo) does not cause resistance, then it is not contributing to the phenotype when present in a mixture either.
50
50
 
51
- Mutations that occur in isolation across specified genes are traversed in sequence, and if their proportion of drug-susceptibility (vs. resistance) passes the specified statistical test, they are characterized as benign (susceptible) and removed from the dataset. This step repeats iteratively until no more solo benign mutations are found.
51
+ Mutations that occur in isolation are traversed in sequence. When they meet the susceptible criteria for the selected test, they are characterized as benign (`S`) and removed from the dataset. This step repeats iteratively until no more solo benign mutations are found.
52
52
 
53
53
  Remaining mutations are classified based on their resistance rates and statistical test results. Those that don’t meet thresholds are labeled as `U`.
54
54
 
55
55
  The classification approach supports:
56
56
 
57
- - **No test**: assumes homogeneous susceptibility is sufficient for S
58
- - **Binomial test**: against a specified background resistance rate
59
- - **Fisher's test**: using a contingency background
57
+ - **No test**: calls a mutation `S` during iterative unlocking when all its solo observations are susceptible; remaining solo mutations are `U`
58
+ - **Binomial test**: tests the solo resistance rate against a specified background resistance rate
59
+ - **Fisher's test**: compares the mutation solo contingency table with the no-mutation solo background
60
60
 
61
61
  ### Optional Interventions
62
62
 
@@ -68,9 +68,9 @@ Because the method uses GARC1 grammar, rules like `{rpoB@*_fs:R}` can be supplie
68
68
  - Be additive (lower priority than specific entries)
69
69
  - Replace matching mutations (requires `replace=True` and `wildcards` supplied)
70
70
 
71
- The catalogue can be returned as a dictionary or a Piezo-compatible `pandas.DataFrame`.
71
+ The built catalogue is available as a dictionary through the `catalogue.catalogue` attribute. It can also be converted to a Piezo-compatible `pandas.DataFrame` with `build_piezo()`.
72
72
 
73
- Contingency tables, proportions, p-values, and Wilson confidence intervals are stored in the `EVIDENCE` field.
73
+ Evidence is stored with each catalogue entry and exported in the `EVIDENCE` field. It includes proportions, Wilson confidence intervals, solo contingency tables used for classification, full sample-level contingency tables for description, and p-values when a statistical test is used. Sample IDs can be included with `record_ids=True`.
74
74
 
75
75
  ### Example Workflow using Fisher's:
76
76
 
@@ -125,7 +125,7 @@ from catomatic.BinaryCatalogue import BinaryBuilder
125
125
  catalogue = BinaryBuilder(samples=samples_df, mutations=mutations_df).build()
126
126
 
127
127
  # View dictionary version
128
- cat_dict = catalogue.return_catalogue()
128
+ cat_dict = catalogue.catalogue
129
129
 
130
130
  # Convert to Piezo-compatible format
131
131
  catalogue_df = catalogue.build_piezo(
@@ -149,7 +149,7 @@ catalogue.to_piezo(
149
149
 
150
150
  ### CLI
151
151
 
152
- After installation, the simplest way to run the catomatic catalogue builder is via the command line interface using the `binary` subcommand. You must use either `--to_piezo` or `--to_json` to specify the output format. Additional metadata is required when using `--to_piezo`.
152
+ After installation, the simplest way to run the catomatic catalogue builder is via the command line interface using the `binary` subcommand. Use `--to_piezo` or `--to_json` to export the catalogue. If neither is supplied, no file is exported. Additional metadata is required when using `--to_piezo`.
153
153
 
154
154
  #### Export to JSON
155
155
 
@@ -178,6 +178,8 @@ catomatic binary \
178
178
 
179
179
  ### CLI Parameters
180
180
 
181
+ The Binary CLI does not expose the Python `build()` options `tails` or `min_count`. The output flags are not mutually exclusive; if both are supplied, both exporters write to the same `--outfile`, so use only one.
182
+
181
183
  | Parameter | Type | Description |
182
184
  | ------------------ | ------- | ---------------------------------------------------------------------------------------------- |
183
185
  | `--samples` | `str` | Path to the samples (phenotypes) file. Required. |
@@ -190,11 +192,16 @@ catomatic binary \
190
192
  | `--version` | `str` | Catalogue version. Required with `--to_piezo`. |
191
193
  | `--drug` | `str` | Name of the drug. Required with `--to_piezo`. |
192
194
  | `--wildcards` | `str` | Path to JSON file containing wildcard mutation definitions. Required with `--to_piezo`. |
193
- | `--test` | `str` | Type of statistical test to apply. One of: `None`, `Binomial`, or `Fisher`. Optional. |
194
- | `--background` | `float` | Background mutation rate (0–1). Required if `--test Binomial` is used. |
195
- | `--p` | `float` | P-value threshold for statistical significance. Optional. Defaults to `0.95`. |
196
- | `--tails` | `str` | Tail type for statistical test. One of: `one`, `two`. Optional. Defaults to `two`. |
197
- | `--strict_unlock` | `flag` | If set, disables classification of susceptible (`S`) mutations unless statistically confident. |
195
+ | `--test` | `str` | Type of statistical test to apply: `Binomial` or `Fisher`. Optional; if omitted, no statistical test is used. |
196
+ | `--background` | `float` | Background resistance rate (0–1). Required if `--test Binomial` is used. |
197
+ | `--p` | `float` | Confidence level used for statistical testing. Optional. Defaults to `0.95` (a 0.05 significance threshold). |
198
+ | `--strict_unlock` | `flag` | For two-tailed tests, require statistical significance to classify homogeneous susceptible mutations as `S` during iterative unlocking. |
199
+ | `--frs` | `float` | Optional fraction-read-support threshold. Requires an `FRS` column in the mutations file. |
200
+ | `--seed` | `str[]` | Optional list of mutations to seed as susceptible before building. |
201
+ | `--record_ids` | `flag` | Include sample IDs in evidence records. |
202
+ | `--grammar` | `str` | Grammar used for Piezo export. Default: `GARC1`. |
203
+ | `--values` | `str` | Prediction values for Piezo export. Default: `RUS`. |
204
+ | `--for_piezo` | `flag` | Add placeholder rows for Piezo export. Default: `False` in the CLI. |
198
205
 
199
206
  ## Running catomatic's Regression Builder
200
207
 
@@ -310,8 +317,8 @@ catomatic regression \
310
317
 
311
318
  ### Notes
312
319
 
313
- - When using post-hoc rule updates via .update(), you must provide wildcards and set replace=True if you intend to override existing entries.
314
- - For Piezo export, placeholder entries are inserted automatically if needed to satisfy parser requirements (R, S, and U must be represented).
320
+ - For `BinaryBuilder`, use `update_catalogue()` to apply post-hoc rules. Supply `wildcards` and set `replace=True` to replace entries matched by an aggregate rule. Specific entries can be added or replaced without `replace=True`.
321
+ - For Piezo export, placeholder entries are inserted when `for_piezo=True` and needed to satisfy parser requirements (R, S, and U must be represented). The Python export methods default to `for_piezo=True`; the CLI flag is off by default.
315
322
  - The EVIDENCE column includes contingency tables, proportions, confidence intervals, and p-values, and may optionally include sample IDs if `record_ids=True`.
316
323
  - To build a catalogue with the regression builder, as currently implemented, requires an ecoff as it will compare the predited effected against the background to supply an R/S/U label
317
324
  - To only calculate predicted effects, this can be done in Python by calling RegressionBuilder.predict_effects()
@@ -23,15 +23,15 @@ The first is used in [https://doi.org/10.1101/2025.01.30.635633](https://doi.org
23
23
 
24
24
  This method relies on the logic that mutations that do not cause resistance can co-occur with those that do. If a mutation in isolation (solo) does not cause resistance, then it is not contributing to the phenotype when present in a mixture either.
25
25
 
26
- Mutations that occur in isolation across specified genes are traversed in sequence, and if their proportion of drug-susceptibility (vs. resistance) passes the specified statistical test, they are characterized as benign (susceptible) and removed from the dataset. This step repeats iteratively until no more solo benign mutations are found.
26
+ Mutations that occur in isolation are traversed in sequence. When they meet the susceptible criteria for the selected test, they are characterized as benign (`S`) and removed from the dataset. This step repeats iteratively until no more solo benign mutations are found.
27
27
 
28
28
  Remaining mutations are classified based on their resistance rates and statistical test results. Those that don’t meet thresholds are labeled as `U`.
29
29
 
30
30
  The classification approach supports:
31
31
 
32
- - **No test**: assumes homogeneous susceptibility is sufficient for S
33
- - **Binomial test**: against a specified background resistance rate
34
- - **Fisher's test**: using a contingency background
32
+ - **No test**: calls a mutation `S` during iterative unlocking when all its solo observations are susceptible; remaining solo mutations are `U`
33
+ - **Binomial test**: tests the solo resistance rate against a specified background resistance rate
34
+ - **Fisher's test**: compares the mutation solo contingency table with the no-mutation solo background
35
35
 
36
36
  ### Optional Interventions
37
37
 
@@ -43,9 +43,9 @@ Because the method uses GARC1 grammar, rules like `{rpoB@*_fs:R}` can be supplie
43
43
  - Be additive (lower priority than specific entries)
44
44
  - Replace matching mutations (requires `replace=True` and `wildcards` supplied)
45
45
 
46
- The catalogue can be returned as a dictionary or a Piezo-compatible `pandas.DataFrame`.
46
+ The built catalogue is available as a dictionary through the `catalogue.catalogue` attribute. It can also be converted to a Piezo-compatible `pandas.DataFrame` with `build_piezo()`.
47
47
 
48
- Contingency tables, proportions, p-values, and Wilson confidence intervals are stored in the `EVIDENCE` field.
48
+ Evidence is stored with each catalogue entry and exported in the `EVIDENCE` field. It includes proportions, Wilson confidence intervals, solo contingency tables used for classification, full sample-level contingency tables for description, and p-values when a statistical test is used. Sample IDs can be included with `record_ids=True`.
49
49
 
50
50
  ### Example Workflow using Fisher's:
51
51
 
@@ -100,7 +100,7 @@ from catomatic.BinaryCatalogue import BinaryBuilder
100
100
  catalogue = BinaryBuilder(samples=samples_df, mutations=mutations_df).build()
101
101
 
102
102
  # View dictionary version
103
- cat_dict = catalogue.return_catalogue()
103
+ cat_dict = catalogue.catalogue
104
104
 
105
105
  # Convert to Piezo-compatible format
106
106
  catalogue_df = catalogue.build_piezo(
@@ -124,7 +124,7 @@ catalogue.to_piezo(
124
124
 
125
125
  ### CLI
126
126
 
127
- After installation, the simplest way to run the catomatic catalogue builder is via the command line interface using the `binary` subcommand. You must use either `--to_piezo` or `--to_json` to specify the output format. Additional metadata is required when using `--to_piezo`.
127
+ After installation, the simplest way to run the catomatic catalogue builder is via the command line interface using the `binary` subcommand. Use `--to_piezo` or `--to_json` to export the catalogue. If neither is supplied, no file is exported. Additional metadata is required when using `--to_piezo`.
128
128
 
129
129
  #### Export to JSON
130
130
 
@@ -153,6 +153,8 @@ catomatic binary \
153
153
 
154
154
  ### CLI Parameters
155
155
 
156
+ The Binary CLI does not expose the Python `build()` options `tails` or `min_count`. The output flags are not mutually exclusive; if both are supplied, both exporters write to the same `--outfile`, so use only one.
157
+
156
158
  | Parameter | Type | Description |
157
159
  | ------------------ | ------- | ---------------------------------------------------------------------------------------------- |
158
160
  | `--samples` | `str` | Path to the samples (phenotypes) file. Required. |
@@ -165,11 +167,16 @@ catomatic binary \
165
167
  | `--version` | `str` | Catalogue version. Required with `--to_piezo`. |
166
168
  | `--drug` | `str` | Name of the drug. Required with `--to_piezo`. |
167
169
  | `--wildcards` | `str` | Path to JSON file containing wildcard mutation definitions. Required with `--to_piezo`. |
168
- | `--test` | `str` | Type of statistical test to apply. One of: `None`, `Binomial`, or `Fisher`. Optional. |
169
- | `--background` | `float` | Background mutation rate (0–1). Required if `--test Binomial` is used. |
170
- | `--p` | `float` | P-value threshold for statistical significance. Optional. Defaults to `0.95`. |
171
- | `--tails` | `str` | Tail type for statistical test. One of: `one`, `two`. Optional. Defaults to `two`. |
172
- | `--strict_unlock` | `flag` | If set, disables classification of susceptible (`S`) mutations unless statistically confident. |
170
+ | `--test` | `str` | Type of statistical test to apply: `Binomial` or `Fisher`. Optional; if omitted, no statistical test is used. |
171
+ | `--background` | `float` | Background resistance rate (0–1). Required if `--test Binomial` is used. |
172
+ | `--p` | `float` | Confidence level used for statistical testing. Optional. Defaults to `0.95` (a 0.05 significance threshold). |
173
+ | `--strict_unlock` | `flag` | For two-tailed tests, require statistical significance to classify homogeneous susceptible mutations as `S` during iterative unlocking. |
174
+ | `--frs` | `float` | Optional fraction-read-support threshold. Requires an `FRS` column in the mutations file. |
175
+ | `--seed` | `str[]` | Optional list of mutations to seed as susceptible before building. |
176
+ | `--record_ids` | `flag` | Include sample IDs in evidence records. |
177
+ | `--grammar` | `str` | Grammar used for Piezo export. Default: `GARC1`. |
178
+ | `--values` | `str` | Prediction values for Piezo export. Default: `RUS`. |
179
+ | `--for_piezo` | `flag` | Add placeholder rows for Piezo export. Default: `False` in the CLI. |
173
180
 
174
181
  ## Running catomatic's Regression Builder
175
182
 
@@ -285,8 +292,8 @@ catomatic regression \
285
292
 
286
293
  ### Notes
287
294
 
288
- - When using post-hoc rule updates via .update(), you must provide wildcards and set replace=True if you intend to override existing entries.
289
- - For Piezo export, placeholder entries are inserted automatically if needed to satisfy parser requirements (R, S, and U must be represented).
295
+ - For `BinaryBuilder`, use `update_catalogue()` to apply post-hoc rules. Supply `wildcards` and set `replace=True` to replace entries matched by an aggregate rule. Specific entries can be added or replaced without `replace=True`.
296
+ - For Piezo export, placeholder entries are inserted when `for_piezo=True` and needed to satisfy parser requirements (R, S, and U must be represented). The Python export methods default to `for_piezo=True`; the CLI flag is off by default.
290
297
  - The EVIDENCE column includes contingency tables, proportions, confidence intervals, and p-values, and may optionally include sample IDs if `record_ids=True`.
291
298
  - To build a catalogue with the regression builder, as currently implemented, requires an ecoff as it will compare the predited effected against the background to supply an R/S/U label
292
299
  - To only calculate predicted effects, this can be done in Python by calling RegressionBuilder.predict_effects()
@@ -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.3"
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
  """