PerRI — Personalized Reference Intervals
Code accompanying the paper: Personalized clinical reference intervals for routine precision medical care
Built on University of Washington Medicine (UWM) data. All default hyperparameters, units, and reference ranges are calibrated to UWM laboratory standards.
Note on units and reference ranges: Default hyperparameters assume UWM laboratory units and reference ranges (UW Lab Test Guide). If your institution uses different units or reference ranges, update
bayesian_hyperparameters.csvandmarker_intra_patient_std.csvto reflect own lab's reference ranges.
What does it do?
Standard clinical reference intervals are derived from a healthy reference population and applied uniformly to all patients. PerRI takes a different approach: for any laboratory marker that follows homeostatic dynamics, it fits a Bayesian model to an individual's longitudinal lab measurements to estimate that patient's personal physiological setpoint as a normal distribution N(μ, σ).
The model starts from a population-level prior derived from the standard reference range for the marker, then refines its estimate with each new observation. At any time point, the personalized reference interval is the 95% credible interval: μ ± 1.96σ. The result is a reference interval specific to the individual — narrower for patients with stable physiology, and appropriately wider for those with greater intra-individual variability.
Inputs
The model takes an ordered sequence of measurement values and timestamps for a single patient and marker. Five hyperparameters control the search space and temporal weighting:
| Parameter | Description |
|---|---|
log_lambda_ |
Log of the temporal decay rate. Higher values give more weight to recent measurements over older ones. Optimized per marker, procedure mentioned in the paper |
min_mu, max_mu |
Search grid bounds for the setpoint μ. Bundled defaults are calibrated to UWM. perri.bounds.resolve_grid_bounds calculates site-specific bounds using each marker's policy in perri/marker_config.py: clinical RI by default, empirical 0.5th/99.5th percentiles for A1C, and an empirical upper side for HDL. |
min_sigma, max_sigma |
Search grid bounds for within-person SD σ. |
Pre-optimized values for all 43 supported markers are in perri/data/bayesian_hyperparameters.csv, stratified by sex (ALL, M, F). Supported markers:
['A1C', 'ALB', 'ALK', 'ALT', 'AST', 'BIL', 'BILD', 'BUN', 'CA', 'CHOL', 'CL', 'CO2', 'CRE', 'FER', 'GLU', 'HB', 'HCT', 'HDL', 'HSCRP', 'IGAP', 'K', 'LD', 'LDL', 'LYMPH', 'MCH', 'MCHC', 'MCV', 'MG', 'MONOC', 'NA', 'NONHDL', 'P', 'PLT', 'PROINR', 'PROPAT', 'RBC', 'RDWCV', 'TNEUT', 'TP', 'TRIG', 'TSH', 'VITDT', 'WBC']
Some markers were found to have better performance after log transformation:
['FER', 'GLU', 'HSCRP', 'TRIG', 'TSH']
For these markers, min_mu/max_mu in bayesian_hyperparameters.csv are bundled in log-space, and fit_patient()/fit_batch() log-transform measurements before fitting and back-transform mu/sigma to raw units after by default.
Measurement Selection
By default, measurements within 90 days of any neighbour are filtered out before fitting to avoid clusters of repeat draws (e.g. serial phlebotomy, acute illness) biasing the setpoint estimate. This can be disabled by passing filter_isolated_measurements=False to fit_patient() or fit_batch(), and the gap threshold can be adjusted via min_gap_days.
Scoring
We report RMSE, KS statistic, and KS quintile range. To enable comparison across markers with different scales, RMSE is also reported as RMSE_Scaled, which is RMSE normalized by the median intra-patient standard deviation.
The theoretical lower bound for RMSE Scaled is 1. These are pre-calculated from the UW cohort and stored in perri/data/marker_intra_patient_std.csv.
Requirements
Python 3.9+.
To install the package in editable mode with all dependencies from pyproject.toml:
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
Running the Examples
Sample data (synthetic, 3 patients × 43 markers × 12 visits) is bundled at perri/data/sample_data.csv.
Single-patient fit:
python perri/example_single.py --test_code HB --patient_id 3
Outputs the estimated setpoint, within-person SD, personalized 95% reference interval, and saves a plot to the perri/ directory.
Batch fit + cohort evaluation:
python perri/example_batch.py --test_code HB
Outputs RMSE, KS statistic, and KS quintile range across all patients for the specified marker.
Optional flags for both scripts:
| Flag | Default | Description |
|---|---|---|
--test_code |
HB |
Lab marker (see supported list above) |
--patient_id |
3 |
Patient ID — single-patient script only |
--sex |
ALL |
ALL, M, or F |
Custom Hyperparameters
To override the optimized defaults, pass a params dict to fit_patient() or fit_batch():
import perri as pri
# Start from the defaults and tweak one value
params = pri.get_default_params("HB")
params["log_lambda_"] = 0.5 # stronger recency weighting
result = pri.fit_patient(
values=patient_df["result_value"].values,
timestamps=patient_df["result_date"].values,
params=params,
)
Or specify all five parameters from scratch:
result = pri.fit_patient(
values=patient_df["result_value"].values,
timestamps=patient_df["result_date"].values,
params={
"log_lambda_": 0.5,
"min_mu": 5.0,
"max_mu": 20.0,
"min_sigma": 0.01,
"max_sigma": 2.0,
},
)
When params is provided, test_code is not required for parameter loading (but can still be passed for display purposes).
Interactive Streamlit App
Requires pip install streamlit plotly.
pip install -e ".[app]"
streamlit run perri/app.py
Metadata
Release files for perri 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| perri-0.5.0.tar.gz | 48.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| perri-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.7 kB
Release files / perri-0.5.0.tar.gz
| Download URL | perri-0.5.0.tar.gz |
|---|---|
| Size | 48.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6c12bcaa4f24a36d248510339ddbd72b91c788d860c20198a246f43e76d55102
|
|
BLAKE2b-256 checksum How to use checksums |
64f00ab8467e4731f3cb951d100c6a4e1b2bddfbba5cf4c16d5b51d2d5eb3a3d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / perri-0.5.0-py3-none-any.whl
| Download URL | perri-0.5.0-py3-none-any.whl |
|---|---|
| Size | 47.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0ce933dad1218b6af8144c7debd55e64e2163c9dcb0a1723a5906ed40842f392
|
|
BLAKE2b-256 checksum How to use checksums |
67825e9f99d8165a7433722523f310eda1a807091edb7013eeebd7cfebdba283
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for perri 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.