
Example 1: Regional inequality analysis
Source:vignettes/articles/ex_1_regional.Rmd
ex_1_regional.Rmd
library(cvdprevent)
# We will also be usng the following packages in this article.
# These will be fully namespaced, e.g. dplyr::select() throughout.
# library(dplyr)
# library(stringr)
# library(reactable)
# library(tidyr)
# library(gt)
# library(plotly)This article walks through a complete, reproducible workflow for analysing regional variation in CVD indicators using the {cvdprevent} package.
| 🎯 Goal | Identify variation in CVD indicators (e.g., AF prevalence, hypertension treatment) across a geography, such as ICS, PCN or practice to target interventions. |
| 📋 Steps |
|
| 🪧 Why {cvdprevent}? | Tidyverse-friendly outputs and helper functions for indicators, time periods and areas simplify pulling and comparing NHS CVDPREVENT audit data. |
Step 1: Select our system level and time period
Data are published quarterly, each with a unique numeric ID. The first step is to identify the relevant time period and system level. Here we filter for NHS Region-level data.
cvd_time_period_system_levels() |>
# keep time periods where regional data is available
dplyr::filter(SystemLevelName == "Region") |>
# order the time periods most recent first
dplyr::arrange(dplyr::desc(TimePeriodID)) |>
# select variables that appear interesting
dplyr::select(dplyr::any_of(c(
"TimePeriodID",
"TimePeriodName",
"SystemLevelID",
"SystemLevelName"
))) |>
# display in an interactive table
reactable::reactable()From this table, TimePeriodID value 26 (“To
June 2025”) is appropriate. NHS Region corresponds to
SystemLevelID 6.
Step 2: Identifying available indicators
We next explore which indicators are reported for this time period and system level:
inds <-
cvd_indicator_list(time_period_id = 26, system_level_id = 6) |>
# select relevant columns to help
dplyr::select(dplyr::any_of(c(
"IndicatorID",
"IndicatorCode",
"IndicatorShortName",
"IndicatorOrder"
)))
inds |>
# display in an interactive table
reactable::reactable(searchable = TRUE, defaultPageSize = 5)This returns 40 indicators - too many to review manually. To focus, we filter for hypertension-related measures:
# show hypertension-related indicators
inds |>
dplyr::filter(stringr::str_starts(
string = IndicatorShortName,
pattern = "Hypertension"
)) |>
# sort them by the IndicatorOrder
dplyr::arrange(IndicatorOrder) |>
# display in a table and remove default row shading
gt::gt() |> gt::tab_options(quarto.disable_processing = TRUE)| IndicatorID | IndicatorCode | IndicatorShortName | IndicatorOrder |
|---|---|---|---|
| 20 | CVDP005HYP | Hypertension: High risk – one high BP with no recorded hypertension (CVDP005HYP) | 1 |
| 52 | CVDP009HYP | Hypertension: Monitoring with ACR (CVDP009HYP) | 1 |
| 11 | CVDP001HYP | Hypertension: Prevalence (CVDP001HYP) | 2 |
| 4 | CVDP004HYP | Hypertension: BP monitoring (CVDP004HYP) | 3 |
| 32 | CVDP007HYP | Hypertension: Treated to appropriate threshold (all ages) (CVDP007HYP) | 4 |
| 2 | CVDP002HYP | Hypertension: Treated to appropriate threshold (age < 80) (CVDP002HYP) | 5 |
| 3 | CVDP003HYP | Hypertension: Treated to appropriate threshold (age >= 80) (CVDP003HYP) | 6 |
| 21 | CVDP006HYP | Hypertension: Potential antihypertensive overtreatment (CVDP006HYP) | 7 |
Step 3: Obtain indicator metadata
Hypertension indicators include prevalence, monitoring, treatment to
threshold and potential over-treatment. Suppose we are interested in
treatment to appropriate threshold (IndicatorID =
32). To understand its definition and calculation:
# gather some details about this indicator
ind_details <- cvd_indicator_details(indicator_id = 32)
# nest the details to break up into useful chunks
ind_details <-
ind_details |>
dplyr::select(dplyr::any_of(c(
"MetaDataCategoryID",
"MetaDataTitle",
"MetaData"
))) |>
tidyr::nest(data = dplyr::any_of(c("MetaDataTitle", "MetaData")))
ind_details[[2]][[1]] |>
gt::gt() |>
gt::tab_options(quarto.disable_processing = TRUE)| MetaDataTitle | MetaData |
|---|---|
| Copyright | Produced by Office for Health Improvement and Disparities, and NHS Benchmarking Network. © Copyright NHS England. All rights reserved. |
| Data source | Cardiovascular Disease Prevention Audit (CVDPREVENT) |
| Definition | The percentage of patients aged 18 and over with GP recorded hypertension, in whom the last clinic blood pressure (BP) or equivalent home or ambulatory blood pressure reading (measured in the preceding 12 months), is to the age and setting appropriate treatment threshold. The age appropriate treatment thresholds used in this indicator are different depending on whether the latest BP reading is performed in a clinic or are a home or ambulatory BP reading. The thresholds are defined below: Clinic blood pressure reading • Patients aged 79 and under <=140/90mmHg • Patients aged 80 and over <=150/90mmHg Home or ambulatory blood pressure reading • Patients aged 79 and under <=135/85mmHg • Patients aged 80 and over <=145/85mmHg |
| Indicator ID | CVDP007HYP |
| Indicator short title | Hypertension: Treated to appropriate threshold (all ages) (CVDP007HYP) |
| Indicator title | Patients with GP recorded hypertension, whose last blood pressure reading is to the appropriate treatment threshold, in the preceding 12 months. |
| Rationale | High blood pressure, or hypertension, rarely has noticeable symptoms. But if untreated, it increases the risk of serious problems such as heart attacks and strokes. Blood pressure (BP) is recorded with two numbers. The systolic pressure (higher number) is the force at which the heart pumps blood around the body. The diastolic pressure (lower number) is the resistance to the blood flow in the blood vessels. They are both measured in millimetres of mercury (mmHg). Following a diagnosis of hypertension, regular monitoring and a reduction of blood pressure to specific treatment thresholds is recommended, to reduce the risk of developing problems such as heart attacks or strokes. BP can be managed through lifestyle changes and/or medications. There are different treatment thresholds for blood pressure depending on a person’s age and whether they have other comorbidities such as diabetes or chronic kidney disease. This indicator only uses the treatment thresholds depending on their age and not their level of comorbidity. The NICE hypertension guidance recommends that adults with hypertension should be reviewed every year and patients aged under 80, should have a clinic BP below 140/90 mmHg (or a home or ambulatory BP reading under 135/85mmHg) and adults aged 80 and over a clinic BP below 150/90mmHg (or if home or ambulatory BP reading under 145/85mmHg). This indicator measures two aspects of hypertension care. Firstly that regular BP monitoring is taking place, and secondly that BP is managed to the age appropriate treatment threshold. It combines two age dependant hypertension management indicators from CVDPREVENT (CVDP002HYP and CVDP003HYP) to give a summary for all patients aged 18 and over. This indicator is intended to inform changes in clinical practice by highlighting areas where monitoring or treatment levels vary. It measures two processes, regular blood pressure measurement and treatment to the threshold levels (for levels of blood pressure testing in the previous year for patients with hypertension please see CVDPREVENT indicator CVDP004HYP). Higher proportions of patients managed to threshold can represent better quality of care, lower levels can represent low levels of testing or low numbers of patients achieving the hypertension treatment thresholds. This indicator can be used to identify areas of good practice and areas for improvement. |
ind_details[[2]][[2]] |>
gt::gt() |>
gt::tab_options(quarto.disable_processing = TRUE)| MetaDataTitle | MetaData |
|---|---|
| Age groups | 18 and over |
| Confidence interval details | A confidence interval is a range of values that is used to quantify the imprecision in the estimate of a particular indicator. Specifically it quantifies the imprecision that results from random variation in the measurement of the indicator. A wider confidence interval shows that the indicator value presented is likely to be a less precise estimate of the true underlying value. The Wilson Score method (1) gives very accurate approximate confidence intervals for proportions and odds based on the assumption of a Binomial distribution. It can be used with any data values, even when the denominator is very small and, unlike some methods, it does not fail to give an interval when the numerator count, and therefore the proportion, is zero. The Wilson Score method is the preferred method for calculating confidence intervals for proportions and odds, but it can also be used for rates, as long as the event rate is low (relatively rare events within the population) as the Binomial distribution is a very good approximation to the Poisson distribution when the event rate is low. The method is described in detail in APHO Technical Briefing 3: Commonly used public health statistics and their confidence intervals (2). 1 Wilson EB. Probable inference, the law of succession, and statistical inference. J Am Stat Assoc 1927;22:209-12. 2 Eayres D. APHO Technical Briefing 3: Commonly used public health statistics and their confidence intervals York: APHO; 2008. |
| Confidence interval method | Wilson Score method |
| Definition of denominator | Total number of patients aged 18 and over, with a GP record of hypertension with no subsequent recorded hypertension resolution |
| Definition of numerator | Of the denominator, number of patients in whom the last blood pressure reading (measured in the preceding 12 months) is to the age and setting appropriate treatment threshold.* *For the age and setting treatment thresholds please see the definition |
| Geography breakdowns | See CVDPREVENT Indicator breakdown document for details found here: https://www.cvdprevent.nhs.uk/resources |
| Indicator value type | Proportion |
| Indicator value unit | Percent |
| Methodology | Numerator/ Denominator *100 |
| Other breakdowns | See CVDPREVENT Indicator breakdown document for details found here: https://www.cvdprevent.nhs.uk/resources |
| Sex | Persons. For details of additional sex breakdowns see CVDPREVENT Indicator breakdown document here: https://www.cvdprevent.nhs.uk/resources for details of additional sex breakdowns |
| Significance level | 95% |
| Source of denominator | Cardiovascular Disease Prevention Audit (CVDPREVENT) Further details - https://digital.nhs.uk/data-and-information/data-collections-and-data-sets/data-collections/quality-and-outcomes-framework-qof/cardiovascular-disease-prevention-audit-cvdprevent |
| Source of numerator | Cardiovascular Disease Prevention Audit (CVDPREVENT) Further details - https://digital.nhs.uk/data-and-information/data-collections-and-data-sets/data-collections/quality-and-outcomes-framework-qof/cardiovascular-disease-prevention-audit-cvdprevent |
| Time periods | Initially to March 2022 and is ongoing |
ind_details[[2]][[3]] |>
gt::gt() |>
gt::tab_options(quarto.disable_processing = TRUE)| MetaDataTitle | MetaData |
|---|---|
| Data quality | CVDPREVENT is a large, but incomplete, picture of patients with GP recorded CVD in England. A small proportion of patients have opted out of their identifiable patient data being shared outside their GP practice for purposes except their own care. These patients are not included in the audit. |
| Data quality improvement plan | User feedback will be sought on the usefulness and interpretation of this indicator to make possible improvements. It is hoped that the publication of the CVDPREVENT audit dashboard will encourage non-participating GPs to submit their data and lead to better coverage of registered patients in England. |
ind_details[[2]][[4]] |>
gt::gt() |>
gt::tab_options(quarto.disable_processing = TRUE)| MetaDataTitle | MetaData |
|---|---|
| Disclosure control | Suppressed sub-national counts between 1 and 7. |
| Frequency | Updated quarterly from March 2022. Data collected at the end of the specified extract month. |
| Interpretation guidance | This indicator reflects blood pressure treatment for patients with hypertension. It measures whether regular BP checking is taking place and if so whether it is below the recommended age specific BP for patients with hypertension. Higher percentages represent increased levels of BP monitoring with BP records below the recommended treatment threshold, which can help to reduce the development of CVD or progression of CVD if it is already present. Lower levels can represent low levels of testing or low numbers of patients achieving the BP treatment thresholds. The CVDPREVENT audit returns a sample of the population and so local teams need to consider whether the coverage is sufficient to be representative of the total area population. National and regional population coverage may indicate that the CVDPREVENT sample is representative as a whole, population coverage at lower level breakdowns may mean they are less representative. This indicator is an all age indicator. It is similar to the sum of the two age specific indicators HYP008 and HYP009 that are reported in Quality and Outcomes Framework (QOF). The values in CVDPREVENT and QOF will not be exactly the same due to the differences in data capture methods, patient populations, and methodology differences when calculating monitoring and treatment indicators. These differences should be considered when comparing results from CVDPREVENT and QOF. Please refer to the Methods document for further limitations and caveats: https://s3.eu-west-2.amazonaws.com/nhsbn-static/CVDPREVENT/2023/CVDPREVENT%20Online%20Methodology%20Annex%20v1.1%20Dec%2022%20-%20PDF.pdff All data reporting for this indicator prior to the June 2024 extract used clinic treatment thresholds to measure whether a patient was being managed to the age appropriate treatment threshold. These clinic thresholds are outlined below: Clinic blood pressure reading • Patients aged 79 and under <=140/90mmHg • Patients aged 80 and over <=150/90mmHg NICE recommends that lower thresholds are applied to BP measurements that took place either at home or using an ambulatory BP machine. From the June 2024 extract a change was made to the indicator so that when the latest recorded BP was either home or ambulatory then the thresholds below have been applied: Home or ambulatory blood pressure reading • Patients aged 79 and under <=135/85mmHg • Patients aged 80 and over <=145/85mmHg This change has led to a reduction in achievement in this indicator for the June 2024 extract. This change has been indicated in the data explorer to aid interpretation. NICE guidance recommends that BP thresholds should be less than the thresholds stated above. This indicator includes patients that have a latest BP that is less than or equal to the appropriate treatment threshold. NICE also suggest a lower BP treatment threshold for patients aged 80 and over who also have CKD. These adjustments have not been applied to this indicator so that it can align with the QOF methodology which also does not include these adjustments. This indicator therefore will slightly over-estimate the achievement of BP management compared to the NICE recommendations. |
| Rounding | Rounding to the nearest five for subnational geographies. |
From the Definition section, this indicator measures the percentage of people with hypertension whose blood pressure is controlled to recommended thresholds, varying by age and setting. Metadata also explains:
Purpose and interpretation
Numerator and denominator definitions
Suppression rules
Guidance for use in dashboards
Step 4: Gather performance measures
We now have the key components:
System level: NHS Region (
system_level_id = 6)Time period: To June 2025 (
time_period_id = 26)Indicator: Hypertension treatment to threshold (
indicator_id = 32)Metadata: Definitions and interpretation guidance
With these, we can retrieve the raw data:
ind_data <-
cvd_indicator_raw_data(time_period_id = 26, system_level_id = 6, indicator_id = 32)This dataset provides the values needed to compare NHS Regions and assess inequality in hypertension management.
ind_data |>
# get the overall performance metric
dplyr::filter(MetricCategoryName == "Persons") |>
# wrangle data
dplyr::mutate(
# sort areas by their value
AreaName = AreaName |> forcats::fct_infreq(w = Value),
# prepare some labels
lbl_value = scales::percent(Value, accuracy = 0.1, scale = 1),
lbl_lcl = scales::percent(LowerConfidenceLimit, accuracy = 0.1, scale = 1),
lbl_ucl = scales::percent(UpperConfidenceLimit, accuracy = 0.1, scale = 1),
lbl_num = scales::comma(Numerator),
lbl_denom = scales::comma(Denominator),
# create text to show the overall value on the bar
Text = lbl_value |> as.character(),
# create some text to include in the context box when you hover over the plot
HoverText = glue::glue(
"{AreaName}
{lbl_value} (CI: {lbl_lcl} to {lbl_ucl})
{lbl_num} out of a total of {lbl_denom} people
"
)
) |>
# plot as an interactive chart
plotly::plot_ly(
x = ~Value,
y = ~AreaName,
# name = ~AreaName,
text = ~Text,
hovertext = ~HoverText,
hoverinfo = "text",
type = "bar",
marker = list(color = "#16a085")
) |>
plotly::layout(
# set the title to the indicator name
title = ind_data |> dplyr::pull(IndicatorShortName) |> unique(),
# clear the y-axis title as its unecessary
yaxis = list(title = ""),
# set the x-axis range and title
xaxis = list(range = c(0, 100), title = "Indicator performance (percent)"),
# set the font
font = list(family = "sans-serif", size = 14),
# adjust the margin to avoid the title getting clipped
margin = list(t = 80),
# change the colour of the hovertext box to add some contrast
hoverlabel = list(bgcolor = "#ecf0f1")
) |>
# hide the mode bar
plotly::config(displayModeBar = FALSE)Why this matters:
Clear comparison: Regions are ordered by performance, making inequality visible at a glance.
Actionable insight: Regions with lower rates can be targeted for intervention.
Reusable workflow: Swap out
indicator_id = 32for AF, cholesterol or other conditions to replicate the analysis.