Skip to content

Commit 7d68ba4

Browse files
authored
Merge pull request #23 from The-Strategy-Unit/16-funnel
Add funnel chart
2 parents 9831cd3 + 92493a9 commit 7d68ba4

File tree

13 files changed

+2513
-3
lines changed

13 files changed

+2513
-3
lines changed

DESCRIPTION

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ Imports:
1616
bslib,
1717
dplyr,
1818
ggplot2,
19+
ggrepel,
1920
glue,
2021
jsonlite,
2122
markdown,
2223
purrr,
24+
readr,
2325
rlang,
26+
scales,
2427
shiny,
2528
stringr,
29+
tidyr,
30+
tidyselect,
2631
withr
2732
Remotes:
2833
The-Strategy-Unit/azkit

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
export(convert_md_to_html)
44
export(fetch_strategy_description)
5+
export(generate_rates_baseline_data)
6+
export(generate_rates_funnel_data)
7+
export(isolate_provider_peers)
8+
export(plot_funnel)
59
export(plot_rates)
610
export(run_app)
711
importFrom(rlang,.data)

R/app_server.R

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#' @param input,output,session Internal parameters for 'shiny'.
33
#' @noRd
44
app_server <- function(input, output, session) {
5+
# Data ----
56
container <- azkit::get_container(Sys.getenv("AZ_CONTAINER_INPUTS"))
67
rates_data <- azkit::read_azure_parquet(container, "rates", "dev")
78

9+
# Lookups ----
810
providers_lookup <- jsonlite::read_json(
911
app_sys("app", "data", "datasets.json"),
1012
simplify_vector = TRUE
@@ -14,10 +16,15 @@ app_server <- function(input, output, session) {
1416
simplify_vector = TRUE
1517
)
1618
descriptions_lookup <- jsonlite::read_json(
17-
"inst/app/data/descriptions.json",
19+
app_sys("app", "data", "descriptions.json"),
1820
simplifyVector = TRUE
1921
)
22+
peers_lookup <- readr::read_csv(
23+
app_sys("app", "data", "peers.csv"),
24+
col_types = "c"
25+
)
2026

27+
# User inputs ----
2128
selected_provider <- mod_select_provider_server(
2229
"mod_select_provider",
2330
providers_lookup
@@ -27,6 +34,7 @@ app_server <- function(input, output, session) {
2734
strategies_lookup
2835
)
2936

37+
# Modules ----
3038
mod_show_description_server(
3139
"mod_show_description",
3240
descriptions_lookup,
@@ -38,4 +46,11 @@ app_server <- function(input, output, session) {
3846
selected_provider,
3947
selected_strategy
4048
)
49+
mod_plot_funnel_server(
50+
"mod_plot_funnel",
51+
rates_data,
52+
peers_lookup,
53+
selected_provider,
54+
selected_strategy
55+
)
4156
}

R/app_ui.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
app_ui <- function(request) {
55
bslib::page_sidebar(
66
title = "TPMA Explorer",
7+
78
sidebar = bslib::sidebar(
89
mod_select_provider_ui("mod_select_provider"),
910
mod_select_strategy_ui("mod_select_strategy"),
@@ -26,9 +27,14 @@ app_ui <- function(request) {
2627
bslib::card_body(mod_show_description_ui("mod_show_description"))
2728
),
2829
bslib::card(
29-
bslib::card_header("Trend in rates"),
30+
bslib::card_header("Trend"),
3031
bslib::card_body(mod_plot_trend_ui("mod_plot_trend")),
3132
full_screen = TRUE
33+
),
34+
bslib::card(
35+
bslib::card_header("Funnel"),
36+
bslib::card_body(mod_plot_funnel_ui("mod_plot_funnel")),
37+
full_screen = TRUE
3238
)
3339
)
3440
)

R/fct_plot.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,50 @@ plot_rates <- function(rates_df, fyear_col = "fyear", rate_col = "rate") {
2222
y = "Rate"
2323
)
2424
}
25+
26+
#' Plot Funnel
27+
#' @param rates_funnel_data A data.frame.
28+
#' @param plot_range Character. A vector of length two giving the y-axis limits.
29+
#' @param x_axis_title Character.
30+
#' @return A ggplot2 object.
31+
#' @export
32+
plot_funnel <- function(rates_funnel_data, plot_range, x_axis_title) {
33+
lines_data <- rates_funnel_data |>
34+
dplyr::select(
35+
"denominator",
36+
tidyselect::matches("^(lower|upper)"),
37+
"mean"
38+
) |>
39+
tidyr::pivot_longer(-"denominator", values_to = "rate")
40+
41+
rates_funnel_data |>
42+
ggplot2::ggplot(ggplot2::aes(.data$denominator, .data$rate)) +
43+
ggplot2::geom_line(
44+
data = lines_data,
45+
ggplot2::aes(group = .data$name),
46+
linetype = "dashed",
47+
na.rm = TRUE
48+
) +
49+
ggplot2::geom_point(ggplot2::aes(colour = .data$is_peer)) +
50+
ggrepel::geom_text_repel(
51+
data = dplyr::filter(rates_funnel_data, !is.na(.data$is_peer)),
52+
ggplot2::aes(label = .data$provider, colour = .data$is_peer),
53+
max.overlaps = Inf # include all labels
54+
) +
55+
ggplot2::scale_colour_manual(
56+
values = c("TRUE" = "black", "FALSE" = "red"),
57+
na.value = "lightgrey"
58+
) +
59+
ggplot2::theme(legend.position = "none") +
60+
ggplot2::scale_x_continuous(labels = scales::comma_format()) +
61+
ggplot2::coord_cartesian(ylim = plot_range) +
62+
ggplot2::labs(x = x_axis_title) +
63+
ggplot2::theme(
64+
axis.ticks.y = ggplot2::element_blank(),
65+
axis.text.y = ggplot2::element_blank(),
66+
axis.title.y = ggplot2::element_blank(),
67+
legend.position = "none",
68+
panel.background = ggplot2::element_blank(),
69+
panel.grid.major.y = ggplot2::element_line("#9d928a", linetype = "dotted")
70+
)
71+
}

R/mod_plot_funnel.R

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#' Plot Funnel UI
2+
#' @param id,input,output,session Internal parameters for `shiny`.
3+
#' @noRd
4+
mod_plot_funnel_ui <- function(id) {
5+
ns <- shiny::NS(id)
6+
shiny::plotOutput(ns("funnel_plot"))
7+
}
8+
9+
#' Plot Funnel Server
10+
#' @param id Internal parameter for `shiny`.
11+
#' @param rates A data.frame. Annual rate values for combinations of provider
12+
#' and TPMA.
13+
#' @param peers_lookup A data.frame. A row per provider-peer pair.
14+
#' @param selected_provider Character. Provider code, e.g. `"RCX"`.
15+
#' @param selected_strategy Character. TPMA variable name, e.g.
16+
#' `"alcohol_partially_attributable_acute"`.
17+
#' @noRd
18+
mod_plot_funnel_server <- function(
19+
id,
20+
rates,
21+
peers_lookup,
22+
selected_provider,
23+
selected_strategy
24+
) {
25+
shiny::moduleServer(id, function(input, output, session) {
26+
rates_prepared <- shiny::reactive({
27+
shiny::req(rates)
28+
shiny::req(peers_lookup)
29+
shiny::req(selected_provider())
30+
shiny::req(selected_strategy())
31+
32+
provider_peers <- isolate_provider_peers(
33+
selected_provider(),
34+
peers_lookup
35+
)
36+
rates_baseline_data <- rates |>
37+
generate_rates_baseline_data(
38+
selected_provider(),
39+
provider_peers,
40+
selected_strategy(),
41+
start_year = "202324"
42+
)
43+
generate_rates_funnel_data(rates_baseline_data)
44+
})
45+
46+
output$funnel_plot <- shiny::renderPlot({
47+
rates <- rates_prepared()
48+
shiny::validate(shiny::need(
49+
nrow(rates) > 0,
50+
"No data available for these selections"
51+
))
52+
plot_funnel(
53+
rates,
54+
plot_range = c(0, max(rates$rate)),
55+
x_axis_title = "Denominator"
56+
)
57+
})
58+
})
59+
}

R/utils_plot.R

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#' Find the Peers for a Given Provider
2+
#' @param provider Character. Provider code, e.g. `"RCX"`.
3+
#' @param peers A data.frame. A row per provider-peer pair.
4+
#' @return Character vector of peers for given `provider`.
5+
#' @export
6+
isolate_provider_peers <- function(provider, peers) {
7+
peers |>
8+
dplyr::filter(
9+
.data$procode == .env$provider & .data$peer != .env$provider
10+
) |>
11+
dplyr::pull(.data$peer)
12+
}
13+
14+
#' Generate Rates Baseline Data
15+
#' @param rates A data.frame.
16+
#' @param provider Character. Provider code, e.g. `"RCX"`.
17+
#' @param peers Character. A vector of peers for given `provider`.
18+
#' @param strategy Character. TPMA variable name, e.g.
19+
#' `"alcohol_partially_attributable_acute"`.
20+
#' @param start_year Integer. Baseline year in the form `202324`.
21+
#' @return A data.frame.
22+
#' @export
23+
generate_rates_baseline_data <- function(
24+
rates,
25+
provider,
26+
peers,
27+
strategy,
28+
start_year
29+
) {
30+
rates |>
31+
dplyr::filter(
32+
.data$strategy == .env$strategy,
33+
.data$fyear == .env$start_year
34+
) |>
35+
dplyr::mutate(
36+
is_peer = dplyr::case_when(
37+
.data$provider == .env$provider ~ FALSE,
38+
.data$provider %in% .env$peers ~ TRUE,
39+
.default = NA # if scheme is neither focal nor a peer
40+
)
41+
) |>
42+
dplyr::filter(!is.na(.data$is_peer)) |> # only focal scheme and peers
43+
dplyr::arrange(dplyr::desc(.data$is_peer)) # to plot focal scheme last
44+
}
45+
46+
#' Generate Data for the Funnel Plot
47+
#' @param rates_baseline_data A data.frame. Output created by
48+
#' [generate_rates_baseline_data].
49+
#' @return A data.frame.
50+
#' @export
51+
generate_rates_funnel_data <- function(rates_baseline_data) {
52+
rates_baseline_data |>
53+
dplyr::mutate(
54+
mean = rates_baseline_data$national_rate,
55+
sdev_pop_i = sqrt(abs(.data$mean) / .data$denominator),
56+
z = (.data$rate - .data$mean) / .data$sdev_pop_i,
57+
sigz = stats::sd(.data$z, na.rm = TRUE),
58+
cl2 = 2 * .data$sdev_pop_i * .data$sigz,
59+
cl3 = 3 * .data$sdev_pop_i * .data$sigz,
60+
lower2 = .data$mean - .data$cl2,
61+
lower3 = .data$mean - .data$cl3,
62+
upper2 = .data$mean + .data$cl2,
63+
upper3 = .data$mean + .data$cl3
64+
)
65+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Project Status: Concept – Minimal or no implementation has been done yet, or the repository is only intended to be a limited example, demo, or proof-of-concept.](https://www.repostatus.org/badges/latest/concept.svg)](https://www.repostatus.org/#concept)
44

5-
An app to explore data for Types of Potentially-Mitigable Activity (TPMAs).
5+
An app to explore data for Types of Potentially-Mitigatable Activity (TPMAs).
66

77
The app is [deployed to Posit Connect](https://connect.strategyunitwm.nhs.uk/tpma-explorer/) (login and permissions required).
88

0 commit comments

Comments
 (0)