An R package for measuring and characterising polypharmacy from UK Clinical Practice Research Datalink (CPRD) Aurum primary-care data.
polypharmacyR takes raw CPRD Aurum tables (drug issues, patients, practices, problems, referrals) and turns them into a structured analytical pipeline: continuous treatment episodes, daily concurrent-drug counts, polypharmacy episodes, and downstream summaries covering clinical appropriateness, economic burden, demographic patterns, geographic variation, and seasonal trends.
Developed in collaboration with the School of Medicine at Keele University.
- Source code: https://github.com/timadeg/polypharmacyR
- Issues: https://github.com/timadeg/polypharmacyR/issues
- What the package does
- Installation
- Quick start
- The analysis pipeline
- Expected input data
- Methodology notes
- Shiny demo app
- Development and testing
- Contributing
- Citation
- Licence
- Acknowledgements
CPRD Aurum is a longitudinal database of anonymised primary-care records from EMIS Web-using GP practices across the UK. Each prescription is recorded as a row in the DrugIssue table, with patient, drug code, issue date, and quantity. To study polypharmacy you have to turn those individual prescription events into something analytically tractable: continuous treatment exposures, days when a patient was on multiple drugs at once, and grouped episodes of sustained concurrent drug use. polypharmacyR does that and then layers contextual analyses on top.
The package gives you:
- Treatment episode detection with configurable grace periods between consecutive prescriptions
- Daily concurrent-drug counts built up from per-episode exposure intervals
- Polypharmacy episode identification at a user-defined threshold (default 5+ concurrent drugs)
- Clinical appropriateness assessment by linking episodes to active problems in the
Problemtable - Cost analysis using
estnhscostfrom drug issues - Demographic stratification by age band and gender, with elderly-specific breakdowns
- Practice and regional variation analysis across the 13 CPRD UK regions
- Temporal pattern analysis including seasonal, winter-pressure, and pre/post-COVID comparisons
- Progression analysis for patients with repeated polypharmacy episodes (escalation, de-escalation, stability)
- Referral pattern analysis using
refurgencyidto relate polypharmacy complexity to healthcare utilisation
Synthetic sample data based on the CPRD Aurum structure ships with the package, so you can run the entire pipeline before you have your own CPRD extract.
polypharmacyR is not on CRAN. Install the development version from GitHub:
# install.packages("remotes")
remotes::install_github("timadeg/polypharmacyR")This runs the full core pipeline end-to-end on the bundled synthetic data:
library(polypharmacyR)
# 1. Load the bundled synthetic CPRD tables
cprd <- load_cprd_data("all")
# 2. Integrate and clean the raw tables
processed <- process_cprd_data(
drug_issue = cprd$drug_issue,
patient = cprd$patient,
product_dict = cprd$product_dict,
practice = cprd$practice,
staff = cprd$staff,
default_duration = 28, # fallback when prescription duration is missing
grace_period = 30 # days between prescriptions before a new episode starts
)
# 3. Detect continuous treatment episodes per patient-drug
episodes <- detect_treatment_episodes(processed)
# 4. Identify polypharmacy episodes (5+ concurrent drugs by default)
poly <- analyze_polypharmacy_episodes(
treatment_episodes = episodes,
poly_threshold = 5,
show_tables = FALSE
)
# Print or summarise the result
print(poly)
summary(poly)poly$episodes is the canonical polypharmacy episode table — one row per continuous run of days at or above the threshold, per patient. Every downstream analysis takes that table as its starting point.
polypharmacyR is organised as a layered pipeline. The first three steps are mandatory; the rest are independent analyses you mix and match depending on the research question.
┌───────────────────────────────┐
│ load_cprd_data() │ (or bring your own data)
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ process_cprd_data() │ integrate & clean
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ detect_treatment_episodes() │ continuous exposures
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ analyze_polypharmacy_ │ polypharmacy episodes
│ episodes() │ ← branch point
└───────────────┬───────────────┘
│
┌───────────────┬────────┴────────┬───────────────┬─────────────────┐
▼ ▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌──────────┐ ┌──────────────┐
│ clinical │ │ cost │ │ demographic │ │ practice │ │ seasonal / │
│ context │ │ analysis │ │ stratification │ │ variation│ │ progression /│
│ │ │ │ │ │ │ │ │ referrals │
└─────────────┘ └─────────────┘ └─────────────────┘ └──────────┘ └──────────────┘
Each branch is a single function call against poly$episodes:
# Clinical appropriateness — uses the Problem table
clinical <- calculate_clinical_polypharmacy(poly$episodes, cprd$problem)
# Cost — uses estnhscost from processed drug issues
cost_data <- processed[, c("patid", "issuedate", "estnhscost", "prodcodeid")]
cost <- calculate_cost_polypharmacy(poly$episodes, cost_data)
# Demographics — by age band and gender
demo <- analyze_demographic_polypharmacy(poly$episodes, cprd$patient)
# Practice and regional variation
prac <- analyze_practice_variation(poly$episodes, cprd$practice, cprd$patient)
# Seasonal and COVID-era temporal patterns
seasonal <- analyze_seasonal_patterns(poly$episodes)
# Trajectories across repeat episodes per patient
progression <- analyze_polypharmacy_progression(poly$episodes)
# Referral urgency vs polypharmacy complexity
referral <- analyze_referral_patterns(poly$episodes, cprd$referral)All analysis functions accept show_tables = FALSE to suppress in-console preview tables (useful when running inside a Shiny app or knitting a report) and return a structured list of data frames you can pull directly into ggplot2, gt, or whatever your reporting stack is.
polypharmacyR is built specifically around the CPRD Aurum table structure. If you have a CPRD Aurum extract, your tables should already match. If you have data from another source, you will need to map your column names to the Aurum schema before using the package.
The key tables and the columns the package depends on:
drug_issue (DrugIssue) — one row per prescription
patid— patient identifierprodcodeid— product code from the CPRD drug dictionaryissuedate— prescription issue dateduration— intended days of supplyquantity— pack sizeestnhscost— estimated NHS cost (used by cost analysis)staffid,pracid— prescriber and practice identifiers
patient (Patient) — one row per patient
patid,yob,gender,pracid,acceptable
product_dict (ProductDictionary) — drug dictionary
ProdCodeId, BNF and DM+D fields
practice (Practice) — one row per practice
pracid,region(integer 1–13, mapped internally to UK region names)
staff (Staff) — one row per staff member
staffid,pracid
problem (Problem) — patient problem/diagnosis records (used by calculate_clinical_polypharmacy)
patid,signid(1 = minor, 2 = significant)probstatusid(1 = active)expduration,probenddate,parentprobrelid
referral (Referral) — referral records (used by analyze_referral_patterns)
patid,refurgencyid(1 = 2-week wait, 2 = urgent, 3 = soon, 4 = routine, 5 = dated)
The bundled synthetic data in inst/extdata/ mirrors this structure exactly and is suitable for testing, demos, and method development. It does not contain real patient data and cannot be used for clinical research.
A few decisions worth flagging if you plan to publish from results produced by this package:
Grace periods. Treatment episodes are joined across prescription gaps up to grace_period days (default 30). This follows common pharmacoepidemiology practice — typical defaults in the literature range from 14 to 90 days depending on drug class and research question. Sensitivity analyses across grace periods are recommended.
Default duration substitution. When duration is missing or zero on a drug issue, polypharmacyR substitutes default_duration (default 28 days). This is a pragmatic choice that approximates the standard UK monthly prescription, but it is a simplification — true duration depends on quantity and dose. If you need higher fidelity, pre-process your drug_issue table to derive duration from quantity, formulation, and dose before passing it to process_cprd_data().
Polypharmacy threshold. The default of 5 concurrent drugs aligns with the most common definition in the UK and international literature, but there is no universally agreed cut-off. Some studies use 4+, others distinguish "polypharmacy" (5–9) from "hyperpolypharmacy" or "severe polypharmacy" (10+). The threshold is fully configurable.
Concurrency, not adherence. The package measures exposure overlap derived from prescription records. It does not measure whether a patient actually took the drugs. Adherence-aware metrics (proportion of days covered, medication possession ratio) are out of scope.
Region codes. The integer-to-name region mapping inside analyze_practice_variation() follows the CPRD Aurum Data Specification (regions 1–13 covering the nine English regions plus Northern Ireland, Scotland, and Wales).
A demo Shiny app showcasing the full pipeline ships in poly_demo_app/:
shiny::runApp(system.file("poly_demo_app", package = "polypharmacyR"))There is also a simpler pre-computed version in poly_demo_app/simple_app.R if you just want to inspect results without configuring the pipeline.
Clone the repo and use devtools for the standard package workflow:
# install.packages("devtools")
devtools::load_all() # load the package without installing
devtools::document() # regenerate Rd files from roxygen
devtools::test() # run the testthat test suite
devtools::check() # run R CMD checkTests live in tests/testthat/.
Contributions are very welcome — bug reports, feature requests, documentation improvements, additional analyses, and code review are all valuable.
The general workflow:
- Open an issue describing the bug or proposed feature before doing significant work, so we can agree on the approach.
- Fork the repository and create a feature branch.
- Add tests in
tests/testthat/that exercise the new behaviour. - Run
devtools::check()and make sure it passes cleanly. - Open a pull request that explains why the change is useful, not just what it changes.
If you are adding a new analysis function, try to follow the existing pattern: take poly_episodes plus any auxiliary CPRD tables, validate inputs explicitly with informative error messages, support show_tables and n_preview arguments, and return a named list with a custom S3 class so print() and summary() methods can be defined.
Planned and possible future work:
- Dose-aware duration calculation from
quantityand DM+D pack information - BNF-chapter-level summaries (cardiovascular, central nervous system, etc.)
- Anticholinergic burden scoring (ACB, ADS, ARS)
- Drug-drug interaction screening against an external interaction database
- Survival-style analysis of time to first polypharmacy episode
- Linkage helpers for CPRD HES APC, ONS mortality, and IMD deprivation data
- Performance optimisation (data.table backend throughout) for national-scale extracts
- A pkgdown website with worked vignettes
If any of these are particularly relevant to your work, opening an issue helps with prioritisation.
If you use polypharmacyR in published work, please cite the package. A formal citation will be added once a corresponding paper or Zenodo DOI is published. In the meantime:
Adegbola, T. (2025). polypharmacyR: Calculate Polypharmacy Indicators
from CPRD Data. R package version 0.1.0.
https://github.com/timadeg/polypharmacyR
You can also retrieve a BibTeX entry from R:
citation("polypharmacyR")MIT. See LICENSE and LICENSE.md for the full text.
Developed in collaboration with the School of Medicine at Keele University. Thanks to colleagues there for clinical input and methodological feedback during development.
CPRD Aurum is a service of the Medicines and Healthcare products Regulatory Agency (MHRA). Access to CPRD data requires ISAC/RDG approval — this package does not provide or grant access to CPRD data; it is a tool for analysing data you have already obtained through proper channels.
