ancestryaudit 0.1.0__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.
- ancestryaudit-0.1.0/LICENSE +21 -0
- ancestryaudit-0.1.0/PKG-INFO +278 -0
- ancestryaudit-0.1.0/README.md +243 -0
- ancestryaudit-0.1.0/ancestryaudit/__init__.py +412 -0
- ancestryaudit-0.1.0/ancestryaudit/audit.py +98 -0
- ancestryaudit-0.1.0/ancestryaudit/correction.py +133 -0
- ancestryaudit-0.1.0/ancestryaudit/filter.py +122 -0
- ancestryaudit-0.1.0/ancestryaudit/report.py +93 -0
- ancestryaudit-0.1.0/ancestryaudit/visualize.py +208 -0
- ancestryaudit-0.1.0/ancestryaudit.egg-info/PKG-INFO +278 -0
- ancestryaudit-0.1.0/ancestryaudit.egg-info/SOURCES.txt +15 -0
- ancestryaudit-0.1.0/ancestryaudit.egg-info/dependency_links.txt +1 -0
- ancestryaudit-0.1.0/ancestryaudit.egg-info/requires.txt +6 -0
- ancestryaudit-0.1.0/ancestryaudit.egg-info/top_level.txt +1 -0
- ancestryaudit-0.1.0/pyproject.toml +3 -0
- ancestryaudit-0.1.0/setup.cfg +4 -0
- ancestryaudit-0.1.0/setup.py +41 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dana Yergaliyeva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ancestryaudit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bias detection and correction framework for genomic cancer AI. Detects ancestry-linked performance gaps in CNV-based cancer classifiers and applies supervised fine-tuning correction.
|
|
5
|
+
Home-page: https://github.com/DanYerga/ancestryaudit
|
|
6
|
+
Author: Dana Yergaliyeva
|
|
7
|
+
Author-email: dyergaliyeva.08@gmail.com
|
|
8
|
+
Keywords: bioinformatics,cancer genomics,copy number variation,ancestry bias,fairness,machine learning,TCGA
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.21
|
|
19
|
+
Requires-Dist: pandas>=1.3
|
|
20
|
+
Requires-Dist: scikit-learn>=1.0
|
|
21
|
+
Requires-Dist: scipy>=1.7
|
|
22
|
+
Requires-Dist: shap>=0.41
|
|
23
|
+
Requires-Dist: matplotlib>=3.4
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
[](https://doi.org/10.5281/zenodo.21290874)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://www.python.org/)
|
|
39
|
+
|
|
40
|
+
# AncestryAudit
|
|
41
|
+
|
|
42
|
+
**Bias detection and correction framework for genomic cancer AI.**
|
|
43
|
+
|
|
44
|
+
Detects ancestry-linked performance gaps in copy number variation (CNV)-based
|
|
45
|
+
cancer classifiers, applies supervised fine-tuning correction, and generates
|
|
46
|
+
structured audit reports.
|
|
47
|
+
|
|
48
|
+
Developed from research on ancestry bias in TCGA-LIHC/STAD classification
|
|
49
|
+
(Yergaliyeva, 2026).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install ancestryaudit
|
|
57
|
+
# or from source:
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Input Format
|
|
64
|
+
|
|
65
|
+
AncestryAudit works on any CNV feature matrix:
|
|
66
|
+
|
|
67
|
+
| Format | Shape | Notes |
|
|
68
|
+
|--------|-------|-------|
|
|
69
|
+
| `np.ndarray` | `(n_samples, n_genes)` | Continuous copy-number values |
|
|
70
|
+
| `pd.DataFrame` | `(n_samples, n_genes)` | Column names = gene identifiers |
|
|
71
|
+
|
|
72
|
+
**Column values:** continuous copy number (e.g. TCGA ABSOLUTE pipeline output,
|
|
73
|
+
where 2.0 = normal diploid, >2 = amplification, <2 = deletion).
|
|
74
|
+
|
|
75
|
+
**Labels:** binary integer (0 or 1), one per sample.
|
|
76
|
+
|
|
77
|
+
**Models:** any scikit-learn compatible estimator with `fit` / `predict` interface.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from ancestryaudit import AncestryAuditFramework
|
|
85
|
+
from sklearn.linear_model import LogisticRegression
|
|
86
|
+
|
|
87
|
+
framework = AncestryAuditFramework()
|
|
88
|
+
|
|
89
|
+
# Step 1: Detect ancestry-linked performance gap
|
|
90
|
+
report = framework.audit(
|
|
91
|
+
LogisticRegression(max_iter=1000),
|
|
92
|
+
X_western, y_western, # source (training) population
|
|
93
|
+
X_asian, y_asian # target (evaluation) population
|
|
94
|
+
)
|
|
95
|
+
print(f"Gap: {report.gap_pp:.2f}pp, p={report.p_value:.4f}")
|
|
96
|
+
print(f"Recommendation: {report.recommendation}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Output:
|
|
100
|
+
```
|
|
101
|
+
Gap: +2.39pp, p=0.0069
|
|
102
|
+
Recommendation: correction_required
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Full Pipeline
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from ancestryaudit import AncestryAuditFramework
|
|
111
|
+
from sklearn.linear_model import LogisticRegression
|
|
112
|
+
|
|
113
|
+
framework = AncestryAuditFramework(
|
|
114
|
+
random_state=42,
|
|
115
|
+
n_bootstrap=1000,
|
|
116
|
+
threshold_pp=2.0, # minimum gap to trigger correction_required
|
|
117
|
+
threshold_p=0.05 # maximum p-value to trigger correction_required
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# ββ Step 1: Filter population-stratification noise (optional) ββββββββββββββ
|
|
121
|
+
X_western_filtered, kept_genes, filter_log = framework.filter_stratification_noise(
|
|
122
|
+
X_western_df, # pd.DataFrame with gene names as columns
|
|
123
|
+
gene_list # list of gene name strings
|
|
124
|
+
)
|
|
125
|
+
print(f"Removed {filter_log['n_removed']} junk genes, kept {filter_log['n_kept']}")
|
|
126
|
+
|
|
127
|
+
# ββ Step 2: Audit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
128
|
+
audit_report = framework.audit(
|
|
129
|
+
LogisticRegression(max_iter=1000),
|
|
130
|
+
X_western_filtered, y_western,
|
|
131
|
+
X_asian_filtered, y_asian
|
|
132
|
+
)
|
|
133
|
+
print(audit_report)
|
|
134
|
+
# AuditReport(gap=+2.39pp, p=0.0069, d=1.52, 95%CI=[0.80, 4.10],
|
|
135
|
+
# recommendation='correction_required')
|
|
136
|
+
|
|
137
|
+
# ββ Step 3: Correct ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
138
|
+
if audit_report.recommendation == "correction_required":
|
|
139
|
+
corrected_model, correction_report = framework.correct(
|
|
140
|
+
LogisticRegression(max_iter=1000),
|
|
141
|
+
X_western_filtered, y_western,
|
|
142
|
+
X_asian_labeled, y_asian_labeled, # labeled Asian samples
|
|
143
|
+
n_samples=75 # how many to include
|
|
144
|
+
)
|
|
145
|
+
print(correction_report)
|
|
146
|
+
# CorrectionReport(delta=+3.51pp, p=0.0012, n_used=75, all_positive=True)
|
|
147
|
+
|
|
148
|
+
# ββ Step 4: Validate βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
149
|
+
validation_report = framework.validate(
|
|
150
|
+
corrected_model,
|
|
151
|
+
X_asian_holdout, y_asian_holdout # never seen during correction
|
|
152
|
+
)
|
|
153
|
+
print(validation_report)
|
|
154
|
+
# ValidationReport(pre_gap=+2.39pp, post_gap=-1.12pp, improvement=+3.51pp)
|
|
155
|
+
|
|
156
|
+
# ββ Step 5: Report βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
157
|
+
report_dict = framework.generate_report("my_audit_report.json")
|
|
158
|
+
framework.summary()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## API Reference
|
|
164
|
+
|
|
165
|
+
### `AncestryAuditFramework`
|
|
166
|
+
|
|
167
|
+
| Method | Description | Returns |
|
|
168
|
+
|--------|-------------|---------|
|
|
169
|
+
| `audit(model, X_source, y_source, X_target, y_target)` | Detect gap | `AuditReport` |
|
|
170
|
+
| `filter_stratification_noise(X, gene_list)` | Remove OR/pseudogene columns | `(X_filtered, kept_genes, filter_log)` |
|
|
171
|
+
| `correct(model, X_source, y_source, X_target_labeled, y_target_labeled, n_samples)` | Fine-tune correction | `(corrected_model, CorrectionReport)` |
|
|
172
|
+
| `validate(corrected_model, X_holdout, y_holdout)` | Post-correction audit | `ValidationReport` |
|
|
173
|
+
| `generate_report(save_path)` | Full JSON report | `dict` |
|
|
174
|
+
| `summary()` | Print pipeline summary | `str` |
|
|
175
|
+
|
|
176
|
+
### `AuditReport` fields
|
|
177
|
+
|
|
178
|
+
| Field | Type | Description |
|
|
179
|
+
|-------|------|-------------|
|
|
180
|
+
| `gap_pp` | float | Accuracy gap in percentage points (positive = source better) |
|
|
181
|
+
| `p_value` | float | Two-sided p-value from bootstrap t-test |
|
|
182
|
+
| `cohen_d` | float | Effect size |
|
|
183
|
+
| `ci_95` | tuple | 95% bootstrap CI on gap_pp |
|
|
184
|
+
| `source_accuracy` | float | Model accuracy on held-out source data |
|
|
185
|
+
| `target_accuracy` | float | Model accuracy on target data |
|
|
186
|
+
| `n_source` | int | Source sample count |
|
|
187
|
+
| `n_target` | int | Target sample count |
|
|
188
|
+
| `recommendation` | str | `"correction_required"` or `"no_action"` |
|
|
189
|
+
|
|
190
|
+
### `CorrectionReport` fields
|
|
191
|
+
|
|
192
|
+
| Field | Type | Description |
|
|
193
|
+
|-------|------|-------------|
|
|
194
|
+
| `delta_pp` | float | Mean accuracy improvement on target holdout (pp) |
|
|
195
|
+
| `p_value` | float | Two-sided t-test on per-seed deltas vs 0 |
|
|
196
|
+
| `n_used` | int | Target samples used (min of n_samples and available) |
|
|
197
|
+
| `seed_robustness` | dict | mean, sd, min, max, n_positive across 10 seeds |
|
|
198
|
+
| `all_positive` | bool | True if all seeds showed positive correction |
|
|
199
|
+
| `baseline_accuracy` | float | Source-only accuracy on full target |
|
|
200
|
+
| `corrected_accuracy` | float | Estimated corrected accuracy on full target |
|
|
201
|
+
|
|
202
|
+
### `ValidationReport` fields
|
|
203
|
+
|
|
204
|
+
| Field | Type | Description |
|
|
205
|
+
|-------|------|-------------|
|
|
206
|
+
| `pre_gap` | float | Performance gap before correction (pp) |
|
|
207
|
+
| `post_gap` | float | Performance gap after correction (pp) |
|
|
208
|
+
| `correction_magnitude` | float | pre_gap - post_gap |
|
|
209
|
+
| `improvement_pp` | float | Accuracy improvement on target (pp) |
|
|
210
|
+
| `pre_accuracy_target` | float | Target accuracy before correction |
|
|
211
|
+
| `post_accuracy_target` | float | Target accuracy after correction |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Filtering Details
|
|
216
|
+
|
|
217
|
+
`filter_stratification_noise` removes three gene categories that are known
|
|
218
|
+
to reflect population-level genetic drift rather than cancer biology:
|
|
219
|
+
|
|
220
|
+
- **Olfactory receptor genes** (`OR*`) β CNV in these clusters varies by
|
|
221
|
+
ancestral migration history, not cancer type
|
|
222
|
+
- **Pseudogenes** (`*P`, `*P1`, `*P2`, β¦) β non-functional, high
|
|
223
|
+
population-stratification signal
|
|
224
|
+
- **Uncharacterized loci** (names containing `.`) β clone-based placeholder
|
|
225
|
+
identifiers with no interpretable biological information
|
|
226
|
+
|
|
227
|
+
**Required Methods disclosure:** The feature space was defined using all
|
|
228
|
+
samples prior to train/test split, which constitutes a bounded form of
|
|
229
|
+
data snooping. No label information was used in this step (Kaufman et al., 2012).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Expected Results
|
|
234
|
+
|
|
235
|
+
> **Disclaimer:** Audit results depend on the model, train/test split,
|
|
236
|
+
> and preprocessing pipeline provided. Results will differ from published
|
|
237
|
+
> paper figures, which used a 7-algorithm ensemble with specific PCA
|
|
238
|
+
> preprocessing. The library is designed for arbitrary input β
|
|
239
|
+
> directional consistency (not numerical identity) with paper results
|
|
240
|
+
> is the correct validation criterion.
|
|
241
|
+
|
|
242
|
+
**Validated reproduction test (Yergaliyeva, 2026):**
|
|
243
|
+
|
|
244
|
+
When provided with the exact paper train/test split (White n=338 train,
|
|
245
|
+
n=113 test; Asian n=242 evaluation) and gene-aligned PCA features,
|
|
246
|
+
the library reproduces paper results with 0.003pp numerical precision:
|
|
247
|
+
|
|
248
|
+
| Metric | Paper | Library |
|
|
249
|
+
|--------|-------|---------|
|
|
250
|
+
| Mean PGI | +2.39pp | +2.393pp |
|
|
251
|
+
| Algorithms positive | 7/7 | 7/7 |
|
|
252
|
+
| Direction | positive | positive |
|
|
253
|
+
|
|
254
|
+
All 6 API methods (import, audit, correct, validate, report, filter)
|
|
255
|
+
pass independent correctness tests on synthetic CNV data.
|
|
256
|
+
|
|
257
|
+
> **Note on train/test splits:** Provide the full dataset and let the
|
|
258
|
+
> framework handle splitting internally. Passing only the training
|
|
259
|
+
> portion causes the framework to re-split a subset, producing different
|
|
260
|
+
> model boundaries and non-comparable gap values.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Citation
|
|
265
|
+
|
|
266
|
+
If you use AncestryAudit in research, please cite:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
Yergaliyeva, D. (2026). Ancestry-linked bias in genomic cancer AI:
|
|
270
|
+
Transfer learning correction for East Asian populations.
|
|
271
|
+
[Manuscript in preparation]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT License. Copyright (c) 2026 Dana Yergaliyeva.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
[](https://doi.org/10.5281/zenodo.21290874)
|
|
2
|
+
[](LICENSE)
|
|
3
|
+
[](https://www.python.org/)
|
|
4
|
+
|
|
5
|
+
# AncestryAudit
|
|
6
|
+
|
|
7
|
+
**Bias detection and correction framework for genomic cancer AI.**
|
|
8
|
+
|
|
9
|
+
Detects ancestry-linked performance gaps in copy number variation (CNV)-based
|
|
10
|
+
cancer classifiers, applies supervised fine-tuning correction, and generates
|
|
11
|
+
structured audit reports.
|
|
12
|
+
|
|
13
|
+
Developed from research on ancestry bias in TCGA-LIHC/STAD classification
|
|
14
|
+
(Yergaliyeva, 2026).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install ancestryaudit
|
|
22
|
+
# or from source:
|
|
23
|
+
pip install -e .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Input Format
|
|
29
|
+
|
|
30
|
+
AncestryAudit works on any CNV feature matrix:
|
|
31
|
+
|
|
32
|
+
| Format | Shape | Notes |
|
|
33
|
+
|--------|-------|-------|
|
|
34
|
+
| `np.ndarray` | `(n_samples, n_genes)` | Continuous copy-number values |
|
|
35
|
+
| `pd.DataFrame` | `(n_samples, n_genes)` | Column names = gene identifiers |
|
|
36
|
+
|
|
37
|
+
**Column values:** continuous copy number (e.g. TCGA ABSOLUTE pipeline output,
|
|
38
|
+
where 2.0 = normal diploid, >2 = amplification, <2 = deletion).
|
|
39
|
+
|
|
40
|
+
**Labels:** binary integer (0 or 1), one per sample.
|
|
41
|
+
|
|
42
|
+
**Models:** any scikit-learn compatible estimator with `fit` / `predict` interface.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from ancestryaudit import AncestryAuditFramework
|
|
50
|
+
from sklearn.linear_model import LogisticRegression
|
|
51
|
+
|
|
52
|
+
framework = AncestryAuditFramework()
|
|
53
|
+
|
|
54
|
+
# Step 1: Detect ancestry-linked performance gap
|
|
55
|
+
report = framework.audit(
|
|
56
|
+
LogisticRegression(max_iter=1000),
|
|
57
|
+
X_western, y_western, # source (training) population
|
|
58
|
+
X_asian, y_asian # target (evaluation) population
|
|
59
|
+
)
|
|
60
|
+
print(f"Gap: {report.gap_pp:.2f}pp, p={report.p_value:.4f}")
|
|
61
|
+
print(f"Recommendation: {report.recommendation}")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Output:
|
|
65
|
+
```
|
|
66
|
+
Gap: +2.39pp, p=0.0069
|
|
67
|
+
Recommendation: correction_required
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Full Pipeline
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from ancestryaudit import AncestryAuditFramework
|
|
76
|
+
from sklearn.linear_model import LogisticRegression
|
|
77
|
+
|
|
78
|
+
framework = AncestryAuditFramework(
|
|
79
|
+
random_state=42,
|
|
80
|
+
n_bootstrap=1000,
|
|
81
|
+
threshold_pp=2.0, # minimum gap to trigger correction_required
|
|
82
|
+
threshold_p=0.05 # maximum p-value to trigger correction_required
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# ββ Step 1: Filter population-stratification noise (optional) ββββββββββββββ
|
|
86
|
+
X_western_filtered, kept_genes, filter_log = framework.filter_stratification_noise(
|
|
87
|
+
X_western_df, # pd.DataFrame with gene names as columns
|
|
88
|
+
gene_list # list of gene name strings
|
|
89
|
+
)
|
|
90
|
+
print(f"Removed {filter_log['n_removed']} junk genes, kept {filter_log['n_kept']}")
|
|
91
|
+
|
|
92
|
+
# ββ Step 2: Audit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
93
|
+
audit_report = framework.audit(
|
|
94
|
+
LogisticRegression(max_iter=1000),
|
|
95
|
+
X_western_filtered, y_western,
|
|
96
|
+
X_asian_filtered, y_asian
|
|
97
|
+
)
|
|
98
|
+
print(audit_report)
|
|
99
|
+
# AuditReport(gap=+2.39pp, p=0.0069, d=1.52, 95%CI=[0.80, 4.10],
|
|
100
|
+
# recommendation='correction_required')
|
|
101
|
+
|
|
102
|
+
# ββ Step 3: Correct ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
103
|
+
if audit_report.recommendation == "correction_required":
|
|
104
|
+
corrected_model, correction_report = framework.correct(
|
|
105
|
+
LogisticRegression(max_iter=1000),
|
|
106
|
+
X_western_filtered, y_western,
|
|
107
|
+
X_asian_labeled, y_asian_labeled, # labeled Asian samples
|
|
108
|
+
n_samples=75 # how many to include
|
|
109
|
+
)
|
|
110
|
+
print(correction_report)
|
|
111
|
+
# CorrectionReport(delta=+3.51pp, p=0.0012, n_used=75, all_positive=True)
|
|
112
|
+
|
|
113
|
+
# ββ Step 4: Validate βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
114
|
+
validation_report = framework.validate(
|
|
115
|
+
corrected_model,
|
|
116
|
+
X_asian_holdout, y_asian_holdout # never seen during correction
|
|
117
|
+
)
|
|
118
|
+
print(validation_report)
|
|
119
|
+
# ValidationReport(pre_gap=+2.39pp, post_gap=-1.12pp, improvement=+3.51pp)
|
|
120
|
+
|
|
121
|
+
# ββ Step 5: Report βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
122
|
+
report_dict = framework.generate_report("my_audit_report.json")
|
|
123
|
+
framework.summary()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## API Reference
|
|
129
|
+
|
|
130
|
+
### `AncestryAuditFramework`
|
|
131
|
+
|
|
132
|
+
| Method | Description | Returns |
|
|
133
|
+
|--------|-------------|---------|
|
|
134
|
+
| `audit(model, X_source, y_source, X_target, y_target)` | Detect gap | `AuditReport` |
|
|
135
|
+
| `filter_stratification_noise(X, gene_list)` | Remove OR/pseudogene columns | `(X_filtered, kept_genes, filter_log)` |
|
|
136
|
+
| `correct(model, X_source, y_source, X_target_labeled, y_target_labeled, n_samples)` | Fine-tune correction | `(corrected_model, CorrectionReport)` |
|
|
137
|
+
| `validate(corrected_model, X_holdout, y_holdout)` | Post-correction audit | `ValidationReport` |
|
|
138
|
+
| `generate_report(save_path)` | Full JSON report | `dict` |
|
|
139
|
+
| `summary()` | Print pipeline summary | `str` |
|
|
140
|
+
|
|
141
|
+
### `AuditReport` fields
|
|
142
|
+
|
|
143
|
+
| Field | Type | Description |
|
|
144
|
+
|-------|------|-------------|
|
|
145
|
+
| `gap_pp` | float | Accuracy gap in percentage points (positive = source better) |
|
|
146
|
+
| `p_value` | float | Two-sided p-value from bootstrap t-test |
|
|
147
|
+
| `cohen_d` | float | Effect size |
|
|
148
|
+
| `ci_95` | tuple | 95% bootstrap CI on gap_pp |
|
|
149
|
+
| `source_accuracy` | float | Model accuracy on held-out source data |
|
|
150
|
+
| `target_accuracy` | float | Model accuracy on target data |
|
|
151
|
+
| `n_source` | int | Source sample count |
|
|
152
|
+
| `n_target` | int | Target sample count |
|
|
153
|
+
| `recommendation` | str | `"correction_required"` or `"no_action"` |
|
|
154
|
+
|
|
155
|
+
### `CorrectionReport` fields
|
|
156
|
+
|
|
157
|
+
| Field | Type | Description |
|
|
158
|
+
|-------|------|-------------|
|
|
159
|
+
| `delta_pp` | float | Mean accuracy improvement on target holdout (pp) |
|
|
160
|
+
| `p_value` | float | Two-sided t-test on per-seed deltas vs 0 |
|
|
161
|
+
| `n_used` | int | Target samples used (min of n_samples and available) |
|
|
162
|
+
| `seed_robustness` | dict | mean, sd, min, max, n_positive across 10 seeds |
|
|
163
|
+
| `all_positive` | bool | True if all seeds showed positive correction |
|
|
164
|
+
| `baseline_accuracy` | float | Source-only accuracy on full target |
|
|
165
|
+
| `corrected_accuracy` | float | Estimated corrected accuracy on full target |
|
|
166
|
+
|
|
167
|
+
### `ValidationReport` fields
|
|
168
|
+
|
|
169
|
+
| Field | Type | Description |
|
|
170
|
+
|-------|------|-------------|
|
|
171
|
+
| `pre_gap` | float | Performance gap before correction (pp) |
|
|
172
|
+
| `post_gap` | float | Performance gap after correction (pp) |
|
|
173
|
+
| `correction_magnitude` | float | pre_gap - post_gap |
|
|
174
|
+
| `improvement_pp` | float | Accuracy improvement on target (pp) |
|
|
175
|
+
| `pre_accuracy_target` | float | Target accuracy before correction |
|
|
176
|
+
| `post_accuracy_target` | float | Target accuracy after correction |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Filtering Details
|
|
181
|
+
|
|
182
|
+
`filter_stratification_noise` removes three gene categories that are known
|
|
183
|
+
to reflect population-level genetic drift rather than cancer biology:
|
|
184
|
+
|
|
185
|
+
- **Olfactory receptor genes** (`OR*`) β CNV in these clusters varies by
|
|
186
|
+
ancestral migration history, not cancer type
|
|
187
|
+
- **Pseudogenes** (`*P`, `*P1`, `*P2`, β¦) β non-functional, high
|
|
188
|
+
population-stratification signal
|
|
189
|
+
- **Uncharacterized loci** (names containing `.`) β clone-based placeholder
|
|
190
|
+
identifiers with no interpretable biological information
|
|
191
|
+
|
|
192
|
+
**Required Methods disclosure:** The feature space was defined using all
|
|
193
|
+
samples prior to train/test split, which constitutes a bounded form of
|
|
194
|
+
data snooping. No label information was used in this step (Kaufman et al., 2012).
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Expected Results
|
|
199
|
+
|
|
200
|
+
> **Disclaimer:** Audit results depend on the model, train/test split,
|
|
201
|
+
> and preprocessing pipeline provided. Results will differ from published
|
|
202
|
+
> paper figures, which used a 7-algorithm ensemble with specific PCA
|
|
203
|
+
> preprocessing. The library is designed for arbitrary input β
|
|
204
|
+
> directional consistency (not numerical identity) with paper results
|
|
205
|
+
> is the correct validation criterion.
|
|
206
|
+
|
|
207
|
+
**Validated reproduction test (Yergaliyeva, 2026):**
|
|
208
|
+
|
|
209
|
+
When provided with the exact paper train/test split (White n=338 train,
|
|
210
|
+
n=113 test; Asian n=242 evaluation) and gene-aligned PCA features,
|
|
211
|
+
the library reproduces paper results with 0.003pp numerical precision:
|
|
212
|
+
|
|
213
|
+
| Metric | Paper | Library |
|
|
214
|
+
|--------|-------|---------|
|
|
215
|
+
| Mean PGI | +2.39pp | +2.393pp |
|
|
216
|
+
| Algorithms positive | 7/7 | 7/7 |
|
|
217
|
+
| Direction | positive | positive |
|
|
218
|
+
|
|
219
|
+
All 6 API methods (import, audit, correct, validate, report, filter)
|
|
220
|
+
pass independent correctness tests on synthetic CNV data.
|
|
221
|
+
|
|
222
|
+
> **Note on train/test splits:** Provide the full dataset and let the
|
|
223
|
+
> framework handle splitting internally. Passing only the training
|
|
224
|
+
> portion causes the framework to re-split a subset, producing different
|
|
225
|
+
> model boundaries and non-comparable gap values.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Citation
|
|
230
|
+
|
|
231
|
+
If you use AncestryAudit in research, please cite:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
Yergaliyeva, D. (2026). Ancestry-linked bias in genomic cancer AI:
|
|
235
|
+
Transfer learning correction for East Asian populations.
|
|
236
|
+
[Manuscript in preparation]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT License. Copyright (c) 2026 Dana Yergaliyeva.
|