|
1 | | -# Feature Parity: R HarmonizR v1.10.0 vs HarmonizePy v0.3.0 |
| 1 | +# Feature Parity: R HarmonizR v1.10.0 vs HarmonizePy v0.3.1 |
2 | 2 |
|
3 | 3 | References: R source at `ref/HarmonizR/R/` (13 files). R sva v3.60.0, limma, HarmonizR v1.10.0 via Bioconductor 3.23. |
4 | 4 |
|
5 | 5 | --- |
6 | 6 |
|
7 | | -## WARNING: Fixture Limitations Remain |
| 7 | +## Current Validation Boundaries |
8 | 8 |
|
9 | | -185 existing concordance tests use synthetic data with clean structural missingness only. Per-cell NaN testing is now covered by 5 new `percell_nan_*` fixtures and verified against the murine_medulloblastoma real dataset. However: |
| 9 | +The parity surface is broader than the original structural-missingness fixture suite. In addition to the dense and structural-missingness fixtures, the repository now includes dedicated checks for per-cell NaN handling, medium `jaccard` and `seriation` sort+block cases, chain-rescue `unique_removal` toggles, and the combined stress case (`sort + block + ur + per-cell NaN`). |
10 | 10 |
|
11 | | -- Jaccard/Seriation sort only verified on one 3-batch fixture each |
12 | | -- Unique-removal chain-rescue scenario not tested |
13 | | -- Combined stress (sort+block+ur+per-cell NaN) not tested |
| 11 | +Remaining limits are narrower: |
| 12 | + |
| 13 | +- Jaccard and seriation are fixture-backed and unit-tested, but exact algorithm matching to R is intentionally out of scope. |
| 14 | +- Combined-stress validation is now fixture-backed, but exact NaN-position parity on every feature is still not used as a release gate because of the documented mixed-NaN edge case. |
| 15 | +- Python source coverage is now measured directly in-repo and is currently 94% for `src/harmonizepy`. |
14 | 16 |
|
15 | 17 | --- |
16 | 18 |
|
@@ -104,7 +106,7 @@ R uses `plyr::rbind.fill` (align by row name). Python uses pre-allocated array + |
104 | 106 |
|
105 | 107 | ## 4. Unique-Removal Algorithm: MINOR DIVERGENCE |
106 | 108 |
|
107 | | -R updates affiliation list in-place enabling chain rescues. Python pre-computes non-unique set from original list; rescued singletons don't become new targets. Only verified on highmiss dataset. Chain-rescue scenario not tested. |
| 109 | +R updates affiliation list in-place enabling chain rescues. Python pre-computes the non-unique target set from the original list, so rescued singletons do not become new targets in the same pass. This difference is now covered by dedicated `chain_rescue` fixtures for both `ur=True` and `ur=False`. |
108 | 110 |
|
109 | 111 | --- |
110 | 112 |
|
@@ -133,19 +135,19 @@ Python retains more features in all cases. The trade-off: retained features are |
133 | 135 |
|
134 | 136 | ## 7. Verification Status |
135 | 137 |
|
136 | | -| Scenario | ComBat 1-4 | limma | Notes | |
137 | | -| -------------------------------- | ------------------------- | ------------------------- | ---------------------------------------------- | |
138 | | -| Dense synthetic | PASS (rtol 2e-5 to 5e-4) | PASS (rtol 1e-9) | 185 fixture tests | |
139 | | -| Structural missingness | PASS (rtol 5e-4) | PASS (rtol 1e-8) | 185 fixture tests | |
140 | | -| Blocking (block=2,4) | PASS (rtol 5e-4) | PASS (rtol 1e-8) | 185 fixture tests | |
141 | | -| Sparsity sort + block | PASS (rtol 5e-4) | PASS (rtol 1e-8) | 185 fixture tests | |
142 | | -| Jaccard sort + block | PASS (rtol 5e-4) | -- | One 3-batch fixture | |
143 | | -| Seriation sort + block | PASS (rtol 5e-4) | -- | One 3-batch fixture | |
144 | | -| Per-cell NaN synthetic | PASS (max_rel 0.0000) | PASS (max_rel 0.0000) | NaN positions match | |
145 | | -| **Murine unblocked** | **PASS (max_rel 0.0003)** | **PASS (max_rel 2e-10)** | **Real data, NaN positions match** | |
146 | | -| **Murine blocked (block=2, direct recheck)** | **PASS (ComBat mode 1: max_rel 6e-06)** | **--** | **Current benchmark summaries used an invalid R wrapper. Full blocked summary must be regenerated.** | |
147 | | -| Unique-removal chain rescue | NOT TESTED | -- | No fixture | |
148 | | -| Combined stress | NOT TESTED | NOT TESTED | No fixture | |
| 138 | +| Scenario | ComBat 1-4 | limma | Notes | |
| 139 | +| --- | --- | --- | --- | |
| 140 | +| Dense synthetic | PASS (rtol 2e-5 to 5e-4) | PASS (rtol 1e-9) | baseline fixture suite | |
| 141 | +| Structural missingness | PASS (rtol 5e-4) | PASS (rtol 1e-8) | baseline fixture suite | |
| 142 | +| Blocking (block=2,4) | PASS (rtol 5e-4) | PASS (rtol 1e-8) | fixture-backed | |
| 143 | +| Sparsity sort + block | PASS (rtol 5e-4) | PASS (rtol 1e-8) | fixture-backed | |
| 144 | +| Jaccard sort + block | PASS (rtol 5e-4) | -- | medium block=2 fixture plus broad unit coverage | |
| 145 | +| Seriation sort + block | PASS (rtol 5e-4) | -- | medium block=2 fixture plus broad unit coverage | |
| 146 | +| Per-cell NaN synthetic | PASS (max_rel 0.0000) | PASS (max_rel 0.0000) | NaN positions match | |
| 147 | +| Murine unblocked | PASS (max_rel 0.0003) | PASS (max_rel 2e-10) | real data, NaN positions match | |
| 148 | +| Murine blocked (block=2) | PASS (ComBat mode 1: max_rel 6e-06) | -- | benchmark wrapper bug fixed; blocked summary should still be regenerated | |
| 149 | +| Unique-removal chain rescue | PASS (rtol 1e-4) | -- | dedicated `ur=True` and `ur=False` fixtures | |
| 150 | +| Combined stress | PASS on value concordance for NaN-matching features | -- | dedicated fixture; minority NaN-position mismatch allowed | |
149 | 151 |
|
150 | 152 | The catastrophic blocked benchmark rows previously reported in markdown and JSON artifacts were not real algorithmic divergences. The benchmark R wrapper passed `block` as an integer, HarmonizR rejected it, and the R side silently ran unblocked while Python still ran blocked. A corrected direct rerun on murine `ComBat` mode 1 with `block=2` gives `max_rel 5.73e-06`, `p95_rel 5.48e-15`, and `nan_match 1.0`. Treat existing benchmark-summary rows with `Block = 2` as invalid until they are regenerated with the fixed wrapper. |
151 | 153 |
|
|
0 commit comments