Skip to content

Commit 2ac4e21

Browse files
authored
Merge pull request #90 from opensafely/viv3ckj/move_validation_data
Viv3ckj/move validation data
2 parents fa12372 + 77f607b commit 2ac4e21

File tree

6 files changed

+247
-41
lines changed

6 files changed

+247
-41
lines changed

analysis/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212

1313
# measures_definition_pf_medications.py
1414
start_date_measure_medications = "2023-11-01"
15-
monthly_intervals_measure_medications = 9
15+
monthly_intervals_measure_medications = 9
16+
17+
# measures_definition_pf_consultation_pf_counts.py
18+
start_date_measure_med_counts = "2024-02-01"
19+
monthly_intervals_measure_med_counts = 6
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from ehrql import INTERVAL, create_measures, months
2+
from ehrql.tables.tpp import (
3+
patients,
4+
clinical_events,
5+
practice_registrations,
6+
)
7+
from ehrql.tables.raw.tpp import medications
8+
9+
from config import start_date_measure_med_counts, monthly_intervals_measure_med_counts
10+
from codelists import (
11+
pharmacy_first_consultation_codelist,
12+
pharmacy_first_med_codelist,
13+
)
14+
from pf_variables_library import select_events
15+
16+
# Script taken from Pharmacy First Data Development (for top 10 PF meds table)
17+
18+
measures = create_measures()
19+
measures.configure_dummy_data(population_size=1000)
20+
21+
start_date = start_date_measure_med_counts
22+
monthly_intervals = monthly_intervals_measure_med_counts
23+
24+
registration = practice_registrations.for_patient_on(INTERVAL.end_date)
25+
26+
# Select Pharmacy First events during interval date range
27+
pharmacy_first_events = select_events(
28+
clinical_events,
29+
start_date=INTERVAL.start_date,
30+
end_date=INTERVAL.end_date).where(
31+
clinical_events.snomedct_code.is_in(
32+
pharmacy_first_consultation_codelist
33+
)
34+
)
35+
36+
pharmacy_first_ids = pharmacy_first_events.consultation_id
37+
has_pharmacy_first_consultation = pharmacy_first_events.exists_for_patient()
38+
39+
# Select Pharmacy First consultations during interval date range
40+
selected_medications = select_events(
41+
medications,
42+
start_date=INTERVAL.start_date, end_date=INTERVAL.end_date
43+
).where(medications.consultation_id.is_in(pharmacy_first_ids))
44+
45+
# First medication for each patient
46+
first_selected_medication = (
47+
selected_medications.sort_by(selected_medications.date).first_for_patient().dmd_code
48+
)
49+
# Boolean variable that selected medication is part of pharmacy first med codelists
50+
has_pharmacy_first_medication = first_selected_medication.is_in(pharmacy_first_med_codelist)
51+
52+
# Numerator, patients with a PF medication
53+
# This allows me to count all (first) medications linked to a PF consultation
54+
numerator = first_selected_medication.is_not_null()
55+
56+
# Denominator, registered patients (f/m) with a PF consultation
57+
denominator = (
58+
registration.exists_for_patient()
59+
& patients.sex.is_in(["male", "female"])
60+
& has_pharmacy_first_consultation
61+
)
62+
63+
measures.define_measure(
64+
name="pf_medication_count",
65+
numerator = first_selected_medication.is_not_null(),
66+
denominator=denominator,
67+
group_by={
68+
"dmd_code": first_selected_medication,
69+
"pharmacy_first_med": has_pharmacy_first_medication,
70+
},
71+
intervals=months(monthly_intervals).starting_on(start_date),
72+
)

analysis/measures_definition_pf_descriptive_stats.py

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,118 @@
22
from ehrql.tables.raw.tpp import medications
33
from ehrql.tables.tpp import practice_registrations, patients, clinical_events
44

5-
from pf_variables_library import select_events_from_codelist, select_events_by_consultation_id
5+
from pf_variables_library import select_events
66
from codelists import (
77
pharmacy_first_med_codelist,
88
pharmacy_first_consultation_codelist,
99
pharmacy_first_conditions_codelist,
1010
)
11-
from config import start_date_measure_descriptive_stats, monthly_intervals_measure_descriptive_stats
11+
from config import (
12+
start_date_measure_descriptive_stats,
13+
monthly_intervals_measure_descriptive_stats,
14+
)
1215

1316
measures = create_measures()
14-
measures.configure_dummy_data(population_size=1000)
17+
measures.configure_dummy_data(population_size=100)
1518
measures.configure_disclosure_control(enabled=True)
1619

1720
start_date = start_date_measure_descriptive_stats
1821
monthly_intervals = monthly_intervals_measure_descriptive_stats
1922

2023
registration = practice_registrations.for_patient_on(INTERVAL.end_date)
2124

22-
# Function to retrieve consultation ids from clinical events that are PF consultations
23-
pharmacy_first_ids = select_events_from_codelist(
24-
clinical_events, pharmacy_first_consultation_codelist
25-
).consultation_id
25+
# Select clinical events and medications for measures INTERVAL
26+
selected_events = clinical_events.where(
27+
clinical_events.date.is_on_or_between(
28+
INTERVAL.start_date,
29+
INTERVAL.end_date,
30+
)
31+
)
32+
selected_medications = medications.where(
33+
medications.date.is_on_or_between(
34+
INTERVAL.start_date,
35+
INTERVAL.end_date,
36+
)
37+
)
38+
39+
# Select all Pharmacy First consultation events
40+
pf_consultation_events = select_events(
41+
selected_events,
42+
codelist=pharmacy_first_consultation_codelist,
43+
)
44+
45+
# Extract Pharmacy First consultation IDs and dates
46+
pf_ids = pf_consultation_events.consultation_id
47+
pf_dates = pf_consultation_events.date
2648

27-
# Function to retrieve selected events using pharmacy first ids
28-
selected_clinical_events = select_events_by_consultation_id(
29-
clinical_events, pharmacy_first_ids
30-
).where(clinical_events.date.is_on_or_between(INTERVAL.start_date, INTERVAL.end_date))
49+
has_pf_consultation = pf_consultation_events.exists_for_patient()
3150

32-
selected_med_events = select_events_by_consultation_id(medications, pharmacy_first_ids).where(
33-
medications.date.is_on_or_between(INTERVAL.start_date, INTERVAL.end_date)
51+
# Select Pharmacy First conditions by ID and date
52+
selected_pf_id_conditions = selected_events.where(
53+
selected_events.consultation_id.is_in(pf_ids)
54+
).where(selected_events.snomedct_code.is_in(pharmacy_first_conditions_codelist))
55+
56+
selected_pf_date_conditions = (
57+
selected_events.where(selected_events.consultation_id.is_not_in(pf_ids))
58+
.where(selected_events.date.is_in(pf_dates))
59+
.where(selected_events.snomedct_code.is_in(pharmacy_first_conditions_codelist))
3460
)
3561

36-
# Create variable which contains boolean values of whether pharmacy first event exists for patient
37-
has_pf_consultation = select_events_from_codelist(selected_clinical_events, pharmacy_first_consultation_codelist).exists_for_patient()
62+
has_pf_id_condition = selected_pf_id_conditions.exists_for_patient()
63+
has_pf_date_condition = selected_pf_date_conditions.exists_for_patient()
64+
65+
# Select Pharmacy First Medications by ID and date
66+
selected_pf_id_medications = selected_medications.where(
67+
selected_medications.consultation_id.is_in(pf_ids)
68+
).where(selected_medications.dmd_code.is_in(pharmacy_first_med_codelist))
3869

39-
# PF consultations with PF clinical condition
40-
has_pf_condition = select_events_from_codelist(selected_clinical_events, pharmacy_first_conditions_codelist).exists_for_patient()
70+
selected_pf_date_medications = (
71+
selected_medications.where(selected_medications.consultation_id.is_not_in(pf_ids))
72+
.where(selected_medications.date.is_in(pf_dates))
73+
.where(selected_medications.dmd_code.is_in(pharmacy_first_med_codelist))
74+
)
4175

42-
# PF consultations with prescribed PF medication
43-
has_pf_medication = selected_med_events.where(
44-
selected_med_events.dmd_code.is_in(pharmacy_first_med_codelist)
45-
).exists_for_patient()
76+
has_pf_id_medication = selected_pf_id_medications.exists_for_patient()
77+
has_pf_date_medication = selected_pf_date_medications.exists_for_patient()
4678

47-
# Define the denominator as the number of patients registered
48-
denominator = (
49-
registration.exists_for_patient()
50-
& patients.sex.is_in(["male", "female"])
51-
& has_pf_consultation
79+
# Define measures
80+
measures.define_defaults(
81+
denominator=(
82+
registration.exists_for_patient()
83+
& patients.sex.is_in(["male", "female"])
84+
& has_pf_consultation
85+
),
86+
intervals=months(monthly_intervals).starting_on(start_date),
5287
)
53-
measures.define_defaults(denominator=denominator)
5488

55-
# Measures for PF consultations with PF medication
89+
# Measures linked by Pharmacy First consultation ID
5690
measures.define_measure(
57-
name="pf_with_pfmed",
58-
numerator=has_pf_medication,
59-
intervals=months(monthly_intervals).starting_on(start_date),
91+
name="pfmed_with_pfid",
92+
numerator=has_pf_id_medication,
6093
)
61-
# Measures for PF consultations with PF condition
94+
6295
measures.define_measure(
63-
name="pf_with_pfcondition",
64-
numerator=has_pf_condition,
65-
intervals=months(monthly_intervals).starting_on(start_date),
96+
name="pfcondition_with_pfid",
97+
numerator=has_pf_id_condition,
6698
)
6799

68-
# Measures for PF consultations with both PF medication and condition
69100
measures.define_measure(
70-
name="pf_with_pfmed_and_pfcondition",
71-
numerator=has_pf_condition & has_pf_medication,
72-
intervals=months(monthly_intervals).starting_on(start_date),
101+
name="pfmed_and_pfcondition_with_pfid",
102+
numerator=has_pf_id_medication & has_pf_id_condition,
103+
)
104+
105+
# Measures linked by Pharmacy First consultation date
106+
measures.define_measure(
107+
name="pfmed_on_pfdate",
108+
numerator=has_pf_date_medication,
109+
)
110+
111+
measures.define_measure(
112+
name="pfcondition_on_pfdate",
113+
numerator=has_pf_date_condition,
114+
)
115+
116+
measures.define_measure(
117+
name="pfmed_and_pfcondition_on_pfdate",
118+
numerator=has_pf_date_medication & has_pf_date_condition,
73119
)

lib/functions/create_tables.R

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,42 @@ create_clinical_conditions_codes_table <- function(title) {
129129
heading.subtitle.font.size = "small"
130130
)
131131
}
132+
133+
# Create top 5 table grouped by pharmacy_first_med status
134+
# Data needs to have the following columns:
135+
# pharmacy_first_med
136+
# term
137+
# count
138+
# ratio_by_group
139+
gt_top_meds <- function(data) {
140+
data |>
141+
gt(
142+
groupname_col = "pharmacy_first_med",
143+
rowname_col = "term"
144+
) %>%
145+
tab_header(
146+
title = "Top 5 medications linked to Pharmacy First consultations",
147+
subtitle = "Timeframe: 1st Feb 2024 to 31st July 2024"
148+
) %>%
149+
cols_label(
150+
term = md("**Medication**"),
151+
count = md("**Count**"),
152+
ratio_by_group = md("**%**")
153+
) %>%
154+
fmt_number(
155+
columns = count,
156+
decimals = 0
157+
) %>%
158+
fmt_percent(
159+
columns = ratio_by_group,
160+
decimals = 1
161+
) %>%
162+
tab_style(
163+
style = cell_text(weight = "bold"),
164+
locations = cells_row_groups(groups = everything())
165+
) %>%
166+
tab_stub_indent(
167+
rows = everything(),
168+
indent = 3
169+
)
170+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
library(tidyverse)
2+
library(janitor)
3+
library(here)
4+
library(httr)
5+
6+
# Function to download and read the xlsx files
7+
read_xlsx_from_url <- function(url_list, sheet = NULL, skip = NULL, ...) {
8+
temp_file <- tempfile(fileext = ".xlsx")
9+
GET(
10+
url_list,
11+
write_disk(temp_file, overwrite = TRUE)
12+
)
13+
readxl::read_xlsx(
14+
temp_file,
15+
col_names = TRUE,
16+
.name_repair = janitor::make_clean_names,
17+
sheet = sheet,
18+
skip = skip,
19+
...
20+
)
21+
}
22+
23+
df <- read_xlsx_from_url(
24+
"https://github.com/user-attachments/files/17774058/EPS.and.eRD.Prescribing.Dashboard.July.2024.xlsx",
25+
skip = 2,
26+
sheet = "Historical Data"
27+
)
28+
29+
df_filtered <- df %>%
30+
select(month, region_code, practice_code, eps_items, erd_items) %>%
31+
filter(month %in% c(202402, 202403, 202404, 202405, 202406, 202407)) %>%
32+
mutate(month = ym(month))
33+
34+
df_filtered |> write_csv(
35+
here("lib", "validation", "data", "eps_erd_prescribing_2024-02-01_to_2024-07-01.csv")
36+
)

project.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ actions:
4747
--output output/population/pf_population.csv.gz
4848
outputs:
4949
highly_sensitive:
50-
cohort: output/population/pf_population.csv.gz
50+
cohort: output/population/pf_population.csv.gz
51+
52+
generate_pf_med_counts_measures:
53+
run: >
54+
ehrql:v1 generate-measures analysis/measures_definition_pf_consultation_med_counts.py
55+
--dummy-tables dummy_tables
56+
--output output/measures/consultation_med_counts_measures.csv
57+
outputs:
58+
moderately_sensitive:
59+
measure: output/measures/consultation_med_counts_measures.csv

0 commit comments

Comments
 (0)