Skip to content
ML Academy

project-003 · Analytics and statistics · insurance

Cost-sharing and doctor visits: pricing plan tiers from a randomized insurance experiment

A health insurer is designing plan tiers with different coinsurance rates. How many outpatient doctor visits per member-year should it expect under 25%, 50% and 95% coinsurance compared with free care, and how uncertain are those numbers? Use randomized evidence instead of the insurer's own claims, where members choose their plans.

Licence
public domain (as stated by statsmodels)
Reference run
executed

At a glance

  • Data

    RAND Health Insurance Experiment subset (statsmodels.datasets.randhie) (bundled-with-library) · 20,190 person-years; 10 columns (modelling file) or 45 columns (source file); under 1 MB

    Details on data
  • Baseline

    Raw visit rates per eligible person-year by coinsurance level and their ratios to free care, with naive Poisson intervals.

    Details on baseline
  • Validation

    The causal contrast comes from randomization; models are compared on fit and calibration: GroupKFold by zper (5 folds) for held-out log-likelihood and the predicted…

    Details on validation
  • Metric

    Rate ratios with 95% clustered intervals; overdispersion estimate; held-out log-likelihood; predicted versus observed zero share and upper-tail share by arm.

    Details on metric

Success criterion

  1. Visit rate ratios for each coinsurance level versus free care, with 95% intervals that account for overdispersion and repeated years per person (person-clustered).
  2. The chosen count model reproduces the observed share of zero-visit person-years within 2 percentage points on person-grouped held-out folds.
  3. A pricing table of expected visits per 1,000 member-years by tier with intervals, and a statement of how far the experimental population and era limit its transfer to today's members.

Context

The RAND Health Insurance Experiment randomized people to plans with different cost sharing; statsmodels ships a subset used by Cameron and Trivedi. Because plan assignment was randomized, the comparison across coinsurance levels is not confounded by plan choice, unlike an insurer's own claims. Pre-randomization health measures can still improve precision. Check the RAND documentation for the randomization unit: the bundled subset includes a person id and site but no family id, which limits how errors can be clustered.

Observed when loading (statsmodels 0.15.0): 20,190 person-years; 31% have zero visits; the variance of mdvis is about 7 times its mean; raw mean visits are not monotone in coinsurance (the 100% group is above the 95% group), which needs explanation before modelling.

Data

Dataset
RAND Health Insurance Experiment subset (statsmodels.datasets.randhie) (bundled-with-library)
Licence
public domain (as stated by statsmodels)
Size
20,190 person-years; 10 columns (modelling file) or 45 columns (source file); under 1 MB
Verification
download verified

Access

Ships with statsmodels; no download, no account. sm.datasets.randhie.load_pandas().data gives the 10-column modelling version. The 45-column source file (with plan, site, coinsurance level, year, person id, age, income, eligible time) is installed at Path(sm.datasets.randhie.__file__).parent / "src" / "randhie.csv", with variable labels in src/randdesc.txt.

Grain

One row per person per study year (zper x year, years 1-5); 5,912 persons across 6 sites and 18 plans (observed when loading). Persons contribute up to five correlated rows.

ColumnMeaning
mdvisNumber of face-to-face outpatient visits to an MD in the year (outcome)
coinsCoinsurance rate in percent: 0, 25, 50, 95 or 100 (source file)
lncoinsln(coinsurance + 1) (modelling file)
idp1 if individual deductible plan
lpi / fmdeLog participation incentive; function of the maximum dollar expenditure offer
physlm / disea / hlthg / hlthf / hlthpBaseline physical limitation, chronic disease measure, self-rated health (excellent omitted)
zper / site / plan / yearPerson id (leading digit is site), site, plan number, study year (source file)
timeFraction of the year the person was eligible (source file); exposure for rates

EDA and data-quality rules

  • disea is documented as a count of chronic diseases but takes non-integer values (mean about 11, max about 59 when loaded); confirm its definition in the source description before treating it as a count.

  • Use time (eligible fraction of the year) as exposure; person-years with very small exposure are listed and handled consistently (offset, not deletion).

  • Tabulate coinsurance by site and plan; explain the non-monotone raw means (composition by site, idp plans, age) before modelling.

  • Check that baseline covariates are balanced across coinsurance levels; imbalance points to the randomization unit or to subsetting.

  • Outcome distribution: zero share and upper tail by arm; decide on the count family from these, not by habit.

Baseline

Raw visit rates per eligible person-year by coinsurance level and their ratios to free care, with naive Poisson intervals. These are unbiased under randomization but their intervals are too narrow because of overdispersion and repeated years per person.

Features

Treatment: coinsurance as a categorical variable (0 reference, 25, 50, 95, 100), not the log-linear lncoins term, so the shape is estimated rather than assumed; idp as a separate plan feature. Precision covariates (pre-randomization only): age, sex, child, log income, family size, baseline health (physlm, disea, self-rated health, mental health index), site, study year. Exposure: log(time) as an offset.

Candidate methods

  • Poisson GLM with log link, exposure offset and person-clustered (sandwich) errors

    Consistent for rate ratios even under overdispersion when the mean is right; the robust errors fix the intervals.

  • Negative binomial regression

    Models overdispersion explicitly and gives a full predictive distribution, needed for the zero-share check and pricing bands.

  • Two-part (hurdle) model: logit for any visit, truncated count for visits given any

    Cost sharing may act mainly on the decision to seek care; the two parts show where the effect sits.

Validation design

The causal contrast comes from randomization; models are compared on fit and calibration:

  • GroupKFold by zper (5 folds) for held-out log-likelihood and the predicted versus observed zero share.
  • Agreement check: adjusted and unadjusted rate ratios should be close; a large gap suggests imbalance worth reporting.
  • Posterior-predictive style check: simulated visit distributions per arm against observed histograms.

Leakage checks

Do not adjust for anything measured after randomization that the plan could affect: expenditures (meddol, outpdol), admissions or other utilisation. They are outcomes, and adjusting for them removes part of the effect.

Bias and representativeness

Attrition or non-participation that differs by plan cannot be checked in this subset; the subset itself is described as differing slightly from the published figures; the unit of randomization may be the family, which is not identified here.

Robustness checks

Estimates by site and by study year; excluding partial-year rows; clustering by person versus by site (only 6 sites, so site-clustered errors are unreliable and are shown only as a stress test).

Technical metrics

Rate ratios with 95% clustered intervals; overdispersion estimate; held-out log-likelihood; predicted versus observed zero share and upper-tail share by arm.

Business interpretation

Expected visits per 1,000 member-years per tier with intervals; with a hypothetical cost per visit, the expected outpatient visit cost difference per tier. Labelled as a transfer from historical experimental data.

Error slices

  • Children versus adults.

  • Baseline self-rated health (poor/fair versus good/excellent).

  • Income tertiles: cost sharing may bind more for low-income members.

  • Study year 1 versus later years.

Implementation steps

  • Load both files, join labels, and reproduce the 10-column modelling variables from the source file to confirm alignment.

  • Run the EDA rules; write a short note on disea, exposure and the non-monotone raw means.

  • Fit the baseline rates, then the three candidates with the same covariates and offset; extract rate ratios with clustered errors.

  • Compare candidates with person-grouped CV and the zero-share check; choose one for pricing.

  • Build the pricing table with uncertainty (parametric bootstrap or clustered bootstrap by person).

  • Write the memo: estimates, chosen model, transfer limits.

Reproduction

Learner-built; data ship with statsmodels, so the run is offline. Pin statsmodels via python/uv.lock (the bundled data could change between versions), record the shape and a hash of the loaded frames, and fix bootstrap seeds.

Limitations

  • Historical experimental population and prices; utilisation patterns and care delivery have changed.

  • Subset of the original data without family identifiers, so family-level clustering and some original analyses cannot be reproduced.

  • Visits only; expenditure and health outcomes are not modelled here.

Production and monitoring

Not a deployed model: the output is a documented pricing assumption. Track actual versus expected visit frequency by tier in current claims every quarter, and revise the assumption when the ratio drifts outside agreed bands.

Interview questions

  • Why not estimate the coinsurance effect by regressing visits on plan in our own claims data?

  • Poisson or negative binomial when the estimand is a rate ratio? What changes and what does not?

  • How do you handle members observed for only part of a year?

Postmortem and alternatives

A hierarchical model with site effects would separate site differences from plan effects more cleanly than fixed site terms. If the business question were total spending, a two-part cost model would be needed, because a few hospital stays dominate expenditure while visits are frequent and small.

Reference implementation

Status: executed — outputs below are from the recorded run

cd python && uv run python -m reference.r11_cost_sharing_two_part.run

r11 results - cost sharing and doctor visits

Recorded run of cd python && uv run python -m reference.r11_cost_sharing_two_part.run (full mode). Every number below is copied from the files in outputs/ written by that run (metrics.json, baseline_rates.csv, rate_ratios.csv, agreement_adjusted_vs_unadjusted.csv, interval_comparison.csv, cv_summary.csv, count_distribution_check.csv, model_fit_slices.csv, pricing_table.csv, hurdle_parts.csv, hurdle_decomposition.csv, slice_effects.csv, robustness_by_site.csv, robustness_by_year.csv, balance.csv). The data are the RAND Health Insurance Experiment subset bundled with statsmodels; the dollar cost per visit used in pricing_table.csv (100 USD) is hypothetical and only illustrates the translation to cost.

ItemValue
Run date2026-10-12
SeedsCV folds 20261012 (+ repeat index); bootstrap spawned from 20261013 (200 person-level resamples)
Python / numpy / pandas / scipy / statsmodels3.12.13 / 2.5.3 / 3.0.6 / 1.18.1 / 0.15.0
Runtime of the recorded run91.9 s inside run.py (95.0 s wall); 10 joblib workers, one BLAS thread each; Apple M5, 10 cores, 16 GB
Smoke mode--smoke: 1 CV repeat, 8 bootstrap draws, 28.6 s inside run.py (31.9 s wall)
Data20,190 person-years, 5,912 persons, 6 sites, 18 plans; years 1 to 5 have 5,638 / 5,575 / 5,548 / 1,715 / 1,714 rows; no missing values in the used columns; source file SHA-256 fe64f3c8...5c541
Rows per person3 rows for 3,743 persons, 5 for 1,584, 1 for 269, 2 for 246, 4 for 70; every person stays in one plan cell
Convergenceall 15 CV fits per model converged; full-data fits converged; bootstrap failed draws: 0 / 0 / 0 (Poisson / NB / hurdle)
Alignment checksmodelling frame equals the source file on mdvis, idp, lpi, fmde, physlm, disea, health dummies, and lncoins = ln(coins + 1); the source column logc does not equal ln(coins + 1) for the individual-deductible rows (see Finding 1)

Findings

  1. What the data look like. 31.2% of person-years have zero visits, mean visits are 2.86, the variance is 7.09 times the mean and the maximum is 77. Exposure is nearly complete (mean 0.999 of a year); 45 person-years are below one year and 21 below half a year (smallest 0.077), so the offset matters for a handful of rows, not for the headline. disea is documented as a count but 91.1% of its values are non-integer (0 to 58.6, mean 11.2); it is used as a continuous severity index. The six populated coins x idp cells are free care (6,822 person-years), 25% (4,065), 50% (1,401), 95% (2,653), individual deductible with coins = 0 (4,175) and individual deductible with coins = 100 (1,074, site 1 only). In the source file logc equals ln(96) for the individual-deductible plan with coins = 0 and 0 for the plans with coins = 100, which is not what the coins column implies; I did not resolve this against the RAND documentation (the bundled randdesc.txt only labels the columns), so those two cells are named by what the files contain and kept out of the four pricing tiers.

  2. The raw means are not monotone, and the reason is composition. Mean visits fall from 3.56 (free) to 2.79 (25%), 2.56 (50%) and 2.11 (95%) in the non-deductible cells, but the coins = 100 deductible cell sits at 2.68, above 95%. That cell exists only at site 1, and 50% plans are offered only at sites 1, 3, 4, 5 and 6, so cell means mix sites. With site in the model the ratio for the coins = 100 deductible cell drops from 0.755 (unadjusted) to 0.571 and the 50% ratio moves from 0.720 to 0.653 (agreement_adjusted_vs_unadjusted.csv). Adding the precision covariates and year then changes no 25/50/95 ratio by more than 0.025 (for example 50%: 0.653 to 0.676). Site is therefore the covariate that matters; the rest only tighten intervals.

  3. Headline rate ratios (visit rate versus free care, adjusted, person-clustered 95% intervals).

    plan cellPoissonNB2raw ratio with naive interval
    25% coinsurance0.769 [0.705, 0.840]0.733 [0.674, 0.798]0.784 [0.767, 0.802]
    50% coinsurance0.676 [0.594, 0.768]0.660 [0.585, 0.746]0.720 [0.695, 0.746]
    95% coinsurance0.637 [0.567, 0.716]0.602 [0.536, 0.676]0.594 [0.577, 0.611]
    individual deductible, coins = 00.710 [0.649, 0.776]0.678 [0.623, 0.739]0.681 [0.666, 0.697]
    individual deductible, coins = 100 (site 1)0.587 [0.496, 0.694]0.585 [0.492, 0.696]0.755 [0.726, 0.785]

    The NB2 ratios are 2 to 6 percent lower than the Poisson ones. Poisson with a log link is consistent for the rate ratio whenever the mean model is right, whatever the variance; NB2 weights observations differently, so a gap between them is a hint that the mean model is not exactly right (the income-tertile misfit in Finding 6 points the same way), not a sampling-error statement. I did not investigate it further.

  4. The naive intervals are far too narrow: 3.6 to 4.2 times in log-width for the unadjusted ratios (baseline_rates.csv). For the three tiers the standard error of the log rate ratio is 0.011 / 0.018 / 0.015 under naive Poisson, 0.028 / 0.045 / 0.037 under the Pearson-scaled quasi-Poisson, 0.029 / 0.043 / 0.039 under HC1 robust errors that treat rows as independent, and 0.045 / 0.066 / 0.059 when clustered by person (interval_comparison.csv). Overdispersion explains roughly two thirds of the gap in log terms (Pearson chi-square / df = 5.93 for the Poisson fit, NB2 alpha = 1.21 with interval 1.15 to 1.27), repeated years per person the rest. Clustering on the inferred family key (2,528 families; the bundled subset has no family id, so this is a heuristic built from consecutive person ids, plan and family size) widens the standard errors further to 0.052 / 0.077 / 0.070, and clustering on the 6 sites (a stress test with too few clusters to trust) gives 0.069 / 0.086 / 0.050. If the randomization unit was the family, as the project record suggests it may have been, the person-clustered intervals shown above are somewhat too narrow; the family-clustered version of the 95% tier is [0.556, 0.730] against [0.567, 0.716].

  5. Only the count model that separates "any visit" reproduces the zero share. On person-grouped held-out folds (5 folds x 3 repeats) the predicted zero share is 0.101 for adjusted Poisson, 0.319 for adjusted NB2 and 0.313 for the adjusted hurdle model, against 0.312 observed. The held-out log-likelihood per person-year is -3.023 (Poisson), -2.131 (NB2) and -2.107 (hurdle). Paired differences with a person-level standard error: NB2 over Poisson +0.893 per person-year (z = 20.0), hurdle over NB2 +0.024 (z = 11.1); adding the covariates to NB2 over arm-only NB2 +0.051 (z = 12.3). The spread across the three repeats is below 0.001. Under the rule fixed before the run (within 2 pp pooled, then best log-likelihood) the chosen model is the hurdle. Success criterion 2 (pooled zero share within 2 percentage points) is met: gap +0.008 pp for the hurdle, +0.63 pp for NB2.

  6. The hurdle's near-zero gap is partly mechanical; the slices are the real test. The logit part contains one dummy per plan cell, so it reproduces each cell's zero share almost by construction (worst plan cell, c50: -0.07 pp). NB2 misses 95% coinsurance by -6.5 pp (arm-only NB2 by -6.7 pp). The informative checks are slices the model did not condition on as separate parameters. By income tertile the hurdle's held-out zero share is off by -3.1 pp (low), -1.2 pp (middle) and +4.4 pp (high), and observed mean visits are 3.29 against 3.06 predicted in the high tertile and 2.53 against 2.69 in the low one. So the criterion holds pooled and by plan cell, and does not hold within the two outer income tertiles. Log income enters only linearly in both parts; I did not try a more flexible income term. By child/adult, baseline health, and study year the gaps are within 0.05 pp (fair or poor health: +0.04 pp for the hurdle, -2.6 pp for NB2).

  7. Where the effect sits: mostly in whether anyone visits. Hurdle parts (odds ratio for any visit; rate ratio for visits given any): 25%: 0.603 [0.533, 0.683] and 0.786 [0.709, 0.873]; 50%: 0.487 [0.406, 0.586] and 0.735 [0.633, 0.853]; 95%: 0.330 [0.287, 0.378] and 0.798 [0.693, 0.918]. Standardized to the whole sample, the probability of at least one visit is 0.784 under free care, 0.696 (25%), 0.654 (50%) and 0.570 (95%), a ratio of 0.73 at 95%; mean visits given any visit go from 4.49 to 3.78, 3.60 and 3.81 (ratio 0.85 at 95%). The conditional ratio is flat from 50% to 95% while the any-visit ratio keeps falling.

  8. Pricing table (chosen hurdle model, expected visits per 1,000 member-years, exposure = 1 year, whole-sample covariate mix, person-level cluster bootstrap with 200 draws, 95% percentile intervals).

    plan cellvisits per 1,000 member-years95% intervalratio to free care95% interval
    free care3,613[3,449, 3,782]1.000
    25% coinsurance2,723[2,546, 2,928]0.754[0.701, 0.823]
    50% coinsurance2,450[2,150, 2,718]0.678[0.592, 0.772]
    95% coinsurance2,288[2,060, 2,573]0.633[0.566, 0.720]
    individual deductible, coins = 02,550[2,350, 2,735]0.706[0.657, 0.770]
    individual deductible, coins = 100 (site 1)2,107[1,816, 2,455]0.583[0.494, 0.694]

    With the hypothetical 100 USD per visit, expected outpatient visit cost per member-year falls from 361 USD to 272, 245 and 229 USD for the three tiers (differences of -89, -116 and -133 USD). The intervals of the 25%, 50% and 95% tiers overlap; I did not compute paired differences between tiers, so the ordering among them is suggestive, while the difference of each tier from free care is well established.

  9. Model choice moves the pricing table by a few percent, not by much more. Free-care visits per 1,000 member-years are 3,583 (Poisson), 3,709 (NB2) and 3,613 (hurdle); the 25% tier 2,756, 2,720 and 2,723; the 95% tier 2,282, 2,232 and 2,288. The ratio to free care for the 95% tier is 0.637, 0.602 and 0.633. Differences between models are smaller than the bootstrap interval half-widths (about 250 visits at 95%).

  10. Heterogeneity. Joint Wald tests that the three coinsurance ratios do not vary by slice give p = 0.37 (child versus adult), 0.080 (baseline health), 0.091 (income tertile) and 0.72 (study year 1 versus 2 to 5). Nothing is significant at 5%, and the two borderline ones are exactly where the data are thin: 1,862 person-years have fair or poor baseline health and the 95% ratio there is 0.942 [0.685, 1.294] against 0.599 [0.530, 0.677] for good or excellent health. The 25% ratio is 0.793, 0.674 and 0.824 in the low, middle and high income tertiles: no gradient in the direction "cost sharing binds more for low-income members". With four slice families and several ratios each, I read the differences as noise rather than a pattern.

  11. Robustness. By site (arm-site cells that exist) the 25% ratio ranges from 0.585 (site 6) to 0.907 (site 2), the 95% ratio from 0.524 (site 6) to 0.715 (site 5); site 4's 50% ratio is 0.978 [0.729, 1.312] and site 5's is 0.409 [0.215, 0.777], i.e. single sites are noisy and their intervals are wide. By study year the 25% ratio runs from 0.696 to 0.811, no trend. Dropping the 45 partial-year rows changes the ratios by at most 0.011 (50%: 0.665 against 0.676). Restricting to the four sites that offer all four tiers (3 to 6, 11,692 person-years) gives 0.694 / 0.681 / 0.612, close to the pooled 0.769 / 0.676 / 0.637 for 50% and 95% but lower for 25%.

  12. Balance is acceptable within site except for family size. The largest within-site standardized mean difference against free care is -0.21 (family size, individual-deductible cell with coins = 0); the other values above 0.1 are family size in the 50% cell (-0.18) and in the coins = 100 cell (-0.12) and "poor" self-rated health in the coins = 100 cell (-0.10). Pooled over sites the chronic-disease index differs by up to 0.32 SD between cells, which shrinks to 0.08 within site: this is site composition, not a randomization failure. I cannot tell from these files whether the family-size differences reflect randomization at the family level or subsetting.

  13. Leakage demonstration. Adding same-year log outpatient expenditure (outpdol, an outcome of the plan) to the Poisson model moves the rate ratios from 0.769 / 0.676 / 0.637 to 0.948 / 0.908 / 0.967, with the 25% and 95% intervals now covering 1. It is shown only to size the damage; the headline models do not include it.

Success criteria of project-003

#CriterionResult
1Rate ratios for each coinsurance level versus free care with 95% intervals that allow for overdispersion and person clusteringMet: Poisson-robust and NB2 ratios with person-clustered intervals for all five non-free cells (Finding 3)
2Chosen count model reproduces the observed zero share within 2 percentage points on person-grouped held-out foldsMet pooled (+0.008 pp) and in every plan cell (worst -0.07 pp). Not met within the income tertiles (+4.4 pp high, -3.1 pp low), which the criterion did not ask for but which is reported (Finding 6)
3Pricing table with intervals per tier and a statement of how far the experimental population and era limit transferTable: Finding 8. Transfer limits: next section

What this does not prove

  • Transfer to today's members. The experiment is historical (the bundled documentation gives no dates; the RAND literature places it in the 1970s and early 1980s) and covers six sites; prices, care delivery (telehealth, urgent care, specialist access) and benefit design have changed. The pricing table says what visit rates were under those plans then, not what a current member will do under a 25%, 50% or 95% plan. The effect of a deductible plan, copayments, or out-of-pocket maxima is not identified by coinsurance level alone.
  • The randomization unit is not verified. The bundled subset has no family id, so I clustered by person and, as a check, by an inferred family key and by site. If plans were assigned to families, the person-clustered intervals are slightly too narrow (Finding 4).
  • Visits only. Spending, admissions, health outcomes and welfare effects are not modelled. A visit-rate reduction says nothing about whether the visits avoided were low or high value.
  • Subset and attrition. The bundled subset is not the published analysis file; participation and attrition by plan cannot be checked (rows per person are 1 to 5, with 1,715 and 1,714 rows in years 4 and 5), and I assume the subset is representative of the full experiment.
  • The model class is limited. All three candidates use a log-linear mean with linear income, age and health terms. The income-tertile misfit (Finding 6) shows that this is not exact; the hurdle's logit also uses log(time) as a covariate rather than an offset, which is a modelling choice, not an identity.
  • Intervals are conditional on the model form. The bootstrap resamples persons and refits the same model class; it does not include model-selection uncertainty (the chosen model won on the full sample, not inside each draw). Percentile endpoints from 200 draws are noisy at the second digit.
  • Pricing standardizes to this sample. "Per 1,000 member-years" uses the covariate mix of the experiment, one full year of eligibility and no interaction between plan and covariates; a different membership mix gives different totals even if the ratios hold.
  • Slice and robustness results are not tuned and not corrected for multiplicity. Single-site and single-year estimates have wide intervals and are shown to show instability, not to rank sites.
  • The 100 USD cost per visit is invented for illustration.

Reproduce

cd python && uv run python -m reference.r11_cost_sharing_two_part.run          # full run, about 90 seconds
cd python && uv run python -m reference.r11_cost_sharing_two_part.run --smoke  # about 30 seconds, writes to a temp dir
cd python && uv run pytest tests/test_r11_cost_sharing_two_part.py             # 12 tests

Builder-authored reference. It is not learner evidence and is never credited to your portfolio.

Rubric3 criteria

CriterionMeetsExceeds
Estimand and designStates the rate ratio versus free care as the estimand and explains why randomization identifies it.Discusses randomization unit, attrition and subset caveats and their effect on intervals.
Count modellingUses exposure, handles overdispersion and clusters by person.Compares Poisson-robust, negative binomial and hurdle models with grouped CV and zero-share checks.
Business translationPricing table with intervals per tier.Shows sensitivity of tier pricing to model choice and to the transfer assumption.

References5