The median substitution coefficient is stable. But what lies beneath the median?
A four-study investigation into the structure, reliability limits, and open problems
of per-drug substitution coefficients in the Kyiv pharmacy market.
Pairwise correlation of median substitution coefficients: SP1 (99 markets) vs SP3 (144) vs SP4 (243),
across three exclusive coverage ranges. Pearson r β₯ 0.997 in all 9 comparisons β zero degradation by coverage level.
- The median is rock-solid: r β₯ 0.9969 across all 9 pairwise comparisons, mean deviation < 2%, no selection bias β coefficients are a property of the market, not the sample
- But per-market distributions are wide: IQR ~14 pp, dominant bin covers only ~37% of markets β one number is not enough for per-pharmacy recommendations
- 71.5% of drugs are UNIMODAL β one stable coefficient, production-ready, median 50 markets needed to confirm
- 23.3% are SPREAD β wide uniform distribution with no natural peaks; attempting GMM segmentation yields r = 0.52, and this instability is fundamental: internal split r = 0.82, never β₯ 0.95 in 20 trials
Each study was motivated by the finding of the previous one:
Study 01: Median coefficient is stable (r β₯ 0.997) across datasets
β
βββΊ But IQR is large (~14 pp). One number isn't enough for per-pharmacy use.
β
Study 02: Sequential algorithm classifies each drug's distribution type.
71.5% UNIMODAL / 23.3% SPREAD / 0.8% PEAKS / 4.4% BORDERLINE.
Median 50 markets needed per drug.
β
βββΊ Now we know the type. Can we compute per-segment coefficients via GMM?
β
Study 03: UNIMODAL cross-validates at r = 0.994 β
MULTIMODAL_SPREAD cross-validates at r = 0.52 β unstable regardless of method β
β
βββΊ Is the instability a method problem, or something deeper?
β
Study 03b: Internal SP3 50/50 splits yield r = 0.82. Never β₯ 0.95 in 20 trials.
SPREAD instability is fundamental β not a method problem.
These drugs have no natural segmentation point. Different modeling required.
Pearson r stays β₯ 0.997 regardless of how strictly drugs are filtered by coverage level. Drugs at the 70β80% coverage threshold are just as stable as drugs at β₯90%.
Selection bias test: the high stability isn't because we're only looking at "easy" drugs. Coverage level has zero correlation with coefficient variability (r β 0).
But the per-market distribution around that stable median is wide. The chart above shows examples across coverage tiers β the spread within each drug motivates everything that follows.
A Sequential Analyzer simulates the production scenario: markets arrive one by one per drug, and after each batch of 5 new markets, a Bootstrap Dip Test checks whether the distribution type has stabilized. The algorithm stops automatically as soon as confidence β₯ 80% AND IQR stability β₯ 80% are met simultaneously β saving compute on markets that would not change the result.
| Class | Count | Share | What it means |
|---|---|---|---|
| UNIMODAL | 276 | 71.5% | Median is sufficient β one coefficient per drug |
| MULTIMODAL_SPREAD | 90 | 23.3% | Wide spread β customers behave differently across pharmacies |
| MULTIMODAL_PEAKS | 3 | 0.8% | Two distinct behavioral peaks β GMM finds them cleanly |
| BORDERLINE | 17 | 4.4% | Distribution is objectively ambiguous β cannot classify |
95.6% of 386 drugs classified. Typical drug stabilizes in 26β50 markets (69.5% of confident cases). A production deployment of this algorithm would stop data collection automatically rather than running to a fixed market count.
For UNIMODAL drugs: fit a single median. For MULTIMODAL_SPREAD and PEAKS: use Gaussian Mixture Models to identify clusters of pharmacies with similar substitution behavior and compute a separate coefficient per cluster. Cross-validate by comparing the pipeline output on SP3 (144 markets) vs SP1 (99 different markets).
The class split is sharp:
| Class | N | Pearson r (SP3 vs SP1) | Status |
|---|---|---|---|
| UNIMODAL | 231 | 0.994 | β Production-ready |
| MULTIMODAL_SPREAD | 61 | 0.516 | β Unstable |
| BORDERLINE | 12 | 0.240 | β Not classifiable |
UNIMODAL is solved. SPREAD is not β and Study 03b investigates why.
Four segmentation methods tested: single median, 2-segment threshold, 3-segment threshold, current GMM. None broke r = 0.94 cross-study. The problem is not the method.
Internal validation: 20 random 50/50 splits of SP3 markets per drug. Mean r = 0.82, never β₯ 0.95 in any trial. The SPREAD drug's substitution coefficient is sensitive to which specific pharmacies ended up in the sample β because these drugs have no natural clustering point: SHARE_INTERNAL is uniformly spread across [0, 1] with no dominant peak or segment.
| Class | Result | Path forward |
|---|---|---|
| UNIMODAL (71.5%) | β Solved | Median coefficient β reliable, production-ready |
| MULTIMODAL_PEAKS (0.8%) | β Solved | GMM identifies peaks cleanly |
| BORDERLINE (4.4%) | Insufficient data β collect more markets | |
| MULTIMODAL_SPREAD (23.3%) | β Open | Fundamentally unstable β requires per-pharmacy observable features as segmentation criteria, not per-drug distribution clustering |
substitution_coefficient_validation/
β
βββ study_01_cross_study_validation/ # Median stability across 3 datasets
β βββ scripts/ # 01_cross_study_comparison.py, 02_distribution_profile.py
β βββ docs/ # Research context (full methodology)
β βββ results/charts Β· tables Β· reports Β· conclusions/
β
βββ study_02_sample_size_sufficiency/ # Sequential Analyzer: min markets per drug
β βββ scripts/ # 00_prepare β 01_sequential_analyzer β 02_classify β 03_reports
β βββ core_parameters/ # All algorithm thresholds
β βββ determining_markets_algorithm/ # Production-ready algorithm implementation
β βββ docs/ # Context, parameters guide, glossary
β βββ results/charts Β· reports Β· conclusions/
β
βββ study_03_pharmacy_categorization/ # GMM per-segment coefficient pipeline
β βββ methods_research/ # 4 methods explored: unimodal / peaks / spread / borderline
β βββ pipeline/ # Final pipeline: classify β compute β merge β report β cross-validate
β βββ docs/ # Research context and work plan
β βββ results/cross_validation/ # SP3 vs SP1 charts and reports
β
βββ study_03_b/ # SPREAD instability investigation
β βββ scripts/ # 01_compute β 02_cross_validate β 03_within_split
β βββ results/comparison Β· conclusions/
β
βββ config/ # Shared paths across studies
Requirements: Python 3.11+, input coefficient files from the canonical pipeline
pip install -r requirements.txtDataset routing is configured via config/paths.py for inter-study dependencies and pipeline/config/paths.py for Study 03. Set SOURCE_DATASET in the respective config before running.
# Study 01
python study_01_cross_study_validation/scripts/01_cross_study_comparison.py
python study_01_cross_study_validation/scripts/02_distribution_profile.py
# Study 02 (run in order; step 01 takes ~20 min)
python study_02_sample_size_sufficiency/scripts/00_data_preparation.py
python study_02_sample_size_sufficiency/scripts/01_sequential_analyzer.py
python study_02_sample_size_sufficiency/scripts/02_classification.py
python study_02_sample_size_sufficiency/scripts/03_reports.py
# Study 03 pipeline (configure SOURCE_DATASET in pipeline/config/paths.py first)
python study_03_pharmacy_categorization/pipeline/scripts/01_classify.py
python study_03_pharmacy_categorization/pipeline/scripts/02_compute_coefficients.py
python study_03_pharmacy_categorization/pipeline/scripts/03_merge_library.py
python study_03_pharmacy_categorization/pipeline/scripts/04_reports.py
python study_03_pharmacy_categorization/pipeline/scripts/05_cross_validation.py
# Study 03b
python study_03_b/scripts/01_compute.py
python study_03_b/scripts/02_cross_validate.py
python study_03_b/scripts/03_within_split.pyPer-market coefficient files and drug-level result tables are excluded. Published results contain aggregate statistics only: correlation matrices, distribution charts, confidence summaries. No drug names are paired with quantitative metrics in any published file. No personally identifiable information is present at any stage.
See SECURITY.md for the full data privacy statement.
Β© 2026 Radyslav Lomanov. All Rights Reserved.
See LICENSE for full terms.
Radyslav Lomanov β Data Scientist, Pharmaceutical Market Research
| Website | lomanov.dev |
| lomanov.mail@gmail.com | |
| Telegram | @radyslav_datascience |
| GitHub | radyslav-datascience |
Related projects:
- cross_pharm_market_analysis β canonical pipeline that computed the coefficients validated here
- pharm_market_data_sufficiency_detection β dataset-level stress test (this project goes one level deeper, per-drug)










