-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.Rmd
409 lines (350 loc) · 15.1 KB
/
index.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
---
title: "Age and digital exclusion risk: a map of GP practices in England"
output:
html_document:
theme: default
highlight: pygments
mathjax: null
keep_md: no
smart: no
mainfont: Source Sans Pro
css: style.css
code_folding: hide
include:
in_header: co_header.html
params:
refresh_data: FALSE # if FALSE then the below params have no effect
age_data_update: FALSE
pomi_data_update: FALSE
months_of_data: 6
---
```{r setup, include = FALSE, echo = FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(here)
source(here("R", "gather_data_new.R"))
library(basemaps)
library(htmlwidgets)
library(icon)
library(leaflet)
library(leaflet.extras)
library(scales, warn.conflicts = FALSE)
library(sf)
library(tmap)
ccg_bounds <- readRDS(here("rds_data", "ccg_bounds.Rds")) %>%
sf::st_transform(4326) # https://rstudio.github.io/leaflet/projections.html
full_data <- get_full_data(
refresh = params$refresh_data,
pomi_data_update = params$pomi_data_update,
age_data_update = params$age_data_update,
months = params$months_of_data
)
# just get the most recent month available's data
full_data_latest <- full_data[[1]]
practices_included <- nrow(filter(full_data_latest, !is.na(practice_name)))
practices_excluded <- filter(full_data_latest, is.na(practice_name))
full_data_latest <- full_data_latest %>%
filter(!is.na(practice_name))
full_data_sf <- full_data_latest %>%
dplyr::filter(!is.na(eastings)) %>%
sf::st_as_sf(
coords = c("eastings", "northings"),
crs = 27700
) %>%
sf::st_transform(4326) %>%
sf::st_jitter(0.002)
age_data_month <- tolower(lubridate::month(head(full_data_latest$extract_date, 1), label = TRUE, abbr = FALSE))
age_data_year <- lubridate::year(head(full_data_latest$extract_date, 1))
age_data_url <- paste0(
"https://digital.nhs.uk/",
"data-and-information/",
"publications/",
"statistical/",
"patients-registered-at-a-gp-practice/",
age_data_month,
"-",
age_data_year
)
age_data_date <- paste(age_data_month, age_data_year)
# adjust date to match (POMI issued on last date of month, practice data issued on first of month)
latest_pomi_date <- head(full_data_latest$extract_date, 1) - lubridate::period("1d")
pomi_data_month <- lubridate::month(latest_pomi_date, label = TRUE, abbr = FALSE)
pomi_data_year <- lubridate::year(latest_pomi_date)
median_enabled <- median(full_data_latest$pct_patients_pomi_enabled)
```
#### {#icons}
* [`r icon::ionicons("logo-github")`](https://github.com/citizens-online/cohealthmapr)
* [`r icon::ionicons("logo-twitter")`](https://twitter.com/citizensonline1)
* [`r icon::ionicons("home")`](https://www.citizensonline.org.uk/)
### About
Older people and those with underlying health conditions are at higher risk of being affected by Covid-19.
These groups of people were already at higher risk of being socially isolated.
Now they are potentially cut off from many of the usual sources of essential support, and they are less likely to have the skills, confidence and devices to get help online.
Public sector and community groups are working hard to support more vulnerable people in society, and we know they are looking for information on how best to target that work.
We are determined to put our knowledge and experience to good use, and to help provide that information.
The map below shows `r comma(practices_included)` GP practices in England for which data was available at the last update (`r pomi_data_month` `r pomi_data_year`).
It is designed to help identify areas where there are more people who don’t tend to use digital tools and services, including those who are not active online at all.
To make the map, we've used NHS England data on how many patients are 'enabled' to use Patient Online GP services, as well as data about the age profile of practices' patient lists.
#### Caveat
We acknowledge that registration alone does not imply confident use of online services.
Equally, where people are _not_ enabled for online GP services, this may not indicate a lack of necessary digital skills or access to devices/connectivity.
### Guidance on using the map
* Each practice is represented by a coloured circle on the map.
The larger the circle, the more patients that practice has, in total.
* The circles have a <span style="border: 2px solid #ee1289; border-radius: 4px;"> pink </span> border if less than 40% of patients are enabled for an online service.
**We believe these practices should be a priority for targeted digital skills support, as on balance they are more likely to have patients who lack digital skills**.
Within this group, those with higher numbers of older patients may be further prioritised.
* The practices with more than 40% of patients enabled for online services have a <span style="border: 2px solid #00c5cd; border-radius: 4px;"> turquoise </span> border.
* Each practice is additionally colour-coded according to the proportion of people aged 65+ on its register (see map legend);
however, due to scale, these colours may only become visible when you zoom in.
For example, a <span style="color: `r viridisLite::viridis(n = 5)[[1]]`;"> purple </span>
fill to the circle means the practice is in the top (“oldest”) 20% of practices by age in England; a <span style="color: `r viridisLite::viridis(n = 5)[[5]]`; background-color: #abc;"> yellow </span>
fill means it is among the 20% with the lowest proportions of patients aged 65+.
* Clicking on a practice circle will bring up a bubble with more information.
The `R` source code that generates the map is viewable by clicking the "Code" button, below right.
All the code for this project can be found in the [project repository `r icon::ionicons("logo-github")`](https://github.com/citizens-online/cohealthmapr).
The final data that is used for the GP practices information on the map can be downloaded directly,
[here `r icon::ionicons("document")` (csv format, 9.8MB)](https://github.com/citizens-online/cohealthmapr/raw/master/full_data_gppractices_latest.csv).
```{r essex-static-map, eval = FALSE}
basemaps::set_defaults(map_service = "carto", map_type = "light", map_res = 1)
essex_boundary <- jogger::geo_get("lad", "Essex", "cty", spatial_ref = 3857, return_style = "minimal") %>%
sf::st_union()
bbox_expanded <- myrmidon::expand_sf_bbox(essex_boundary, c(0, 0, 0, 0.34))
basemap <- basemaps::basemap_stars(bbox_expanded)
bbox_crop <- myrmidon::expand_sf_bbox(essex_boundary, 0.05)
basemap_crop <- basemap %>%
sf::st_crop(bbox_crop)
full_data_under70 <- dplyr::filter(full_data_sf, offline_pat_pct < 70)
full_data_over70 <- dplyr::filter(full_data_sf, offline_pat_pct >= 70)
tm_shape(basemap_crop) +
tm_rgb() +
tm_shape(sf::st_join(ccg_bounds, full_data_sf)) +
tm_borders(col = "#6C7B8B", lwd = 1) +
tm_shape(full_data_under70) +
tm_markers(
col = "older_popn_quintile",
size = "total_patients",
scale = 1.2,
perceptual = TRUE,
shape = 21,
alpha = 0.75,
border.col = "#00c5cd",
border.lwd = 2,
border.alpha = 0.9,
palette = "-viridis",
style = "cat",
legend.size.show = TRUE,
legend.col.show = TRUE,
legend.col.is.portrait = FALSE
) +
tm_shape(full_data_over70) +
tm_markers(
col = "older_popn_quintile",
size = "total_patients",
scale = 1.2,
perceptual = TRUE,
shape = 21,
alpha = 0.75,
border.col = "#ee1289",
border.lwd = 2,
border.alpha = 0.9,
palette = "-viridis",
style = "cat",
legend.size.show = FALSE,
legend.col.show = FALSE
) +
tm_shape(essex_boundary) +
tm_borders("cornflowerblue", lwd = 2) +
tm_layout(
title = "",
legend.outside = TRUE,
legend.outside.position = "bottom",
legend.stack = "horizontal"
)
```
<span style = "font-size: 1.2rem">[Skip to below map](#git)</span>
### Map
```{r gp_map, out.width="100%", out.height = "750px"}
# use accessible palette from RColorBrewer
viridis_palette <- leaflet::colorQuantile(palette = "viridis", domain = full_data_sf$age_score, n = 5, reverse = TRUE)
full_data_under40 <- dplyr::filter(full_data_sf, pct_patients_pomi_enabled < 40)
full_data_over40 <- dplyr::filter(full_data_sf, pct_patients_pomi_enabled >= 40)
less_than_forty <- nrow(full_data_under40)
more_than_forty <- nrow(full_data_over40)
gp_map <- leaflet::leaflet(full_data_sf) %>%
addProviderTiles(providers$CartoDB.Positron, options = tileOptions(minZoom = 6, maxZoom = 14)) %>%
addPolygons(
data = ccg_bounds,
stroke = TRUE,
weight = 1,
opacity = 0.75,
color = "#6C7B8B",
fill = TRUE,
fillOpacity = 0,
group = "CCG boundaries",
label = ~ccg20nm
) %>%
addCircles(
data = full_data_over40,
radius = ~ `^`(total_patients, 0.625),
stroke = TRUE,
color = "#00c5cd",
weight = 3,
opacity = 0.75,
fill = TRUE,
fillColor = ~ viridis_palette(age_score),
fillOpacity = 0.75,
label = ~practice_name,
group = "> 40% 'online' (blue border)",
popup = paste0(
"<table>
<tr><th><h3>",
full_data_over40$practice_name,
"</h3>
</th>
<th style = 'font-weight: 400;'>",
full_data_over40$postcode,
"</th>
</tr>",
"<tr>
<td>Total number of patients:</td>
<td style='text-align: right'><strong>",
comma(full_data_over40$total_patients, accuracy = 1),
"</strong></td>
</tr>",
"<tr>
<td>Number of patients aged 65+:</td>
<td style='text-align: right'><strong>",
comma(full_data_over40$over64_patients, accuracy = 1),
"</strong></td>
</tr>",
"<tr>
<td>% patients enabled for online services:</td>
<td style='text-align: right'><strong>",
full_data_over40$pct_patients_pomi_enabled,
"%</strong></td>
</tr>",
"<tr>
<td>Online transactions in ",
pomi_data_month,
" ",
pomi_data_year,
":</td>
<td style='text-align: right'><strong>",
comma(full_data_over40$pomi_total_usage, accuracy = 1),
"</strong></td>
</tr>
</table>"
)
) %>%
addCircles(
data = full_data_under40,
radius = ~ `^`(total_patients, 0.625),
stroke = TRUE,
color = "#ee1289",
weight = 3,
opacity = 0.75,
fill = TRUE,
fillColor = ~ viridis_palette(age_score),
fillOpacity = 0.75,
label = ~practice_name,
group = "< 40% 'online' (pink border)",
popup = paste0(
"<table>
<tr><th><h3>",
full_data_under40$practice_name,
"</h3></th>
<th style = 'font-weight: 400;'>",
full_data_under40$postcode,
"</th>
</tr>",
"<tr>
<td>Total number of patients:</td>
<td style='text-align: right'><strong>",
comma(full_data_under40$total_patients, accuracy = 1),
"</strong></td>
</tr>",
"<tr>
<td>Number of patients aged 65+:</td>
<td style='text-align: right'><strong>",
comma(full_data_under40$over64_patients, accuracy = 1),
"</strong>
</td>
</tr>",
"<tr>
<td>% patients enabled for online services:</td>
<td style='text-align: right'><strong>",
full_data_under40$pct_patients_pomi_enabled,
"%</strong></td>
</tr>",
"<tr>
<td>Online transactions in ",
pomi_data_month,
" ",
pomi_data_year,
":</td>
<td style='text-align: right'><strong>",
comma(full_data_under40$pomi_total_usage, accuracy = 1),
"</strong></td>
</tr>
</table>"
)
) %>%
addLayersControl(
overlayGroups = c(
"< 40% 'online' (pink border)",
"> 40% 'online' (blue border)",
"CCG boundaries"
),
position = "topleft",
options = leaflet::layersControlOptions(collapsed = FALSE)
) %>%
# hideGroup("> 30% 'online' (blue border)") %>%
addLegend(
position = "topright",
colors = viridisLite::viridis(5, direction = -1, alpha = NULL),
opacity = 0.75,
title = "Circle fill colour: proportion of patients<br />aged 65+ (quintiles: 5 = most)",
labels = as.character(1:5)
) %>%
leaflet.extras::addFullscreenControl(
position = "topleft",
pseudoFullscreen = FALSE
) %>%
leaflet::addMiniMap(
tiles = providers$CartoDB.Positron,
zoomLevelOffset = -4,
toggleDisplay = TRUE
)
htmlwidgets::saveWidget(gp_map, "leaflet_widget1.html")
gp_map
```
## Get in touch {#git}
Data alone tells us little about what action to take.
Citizens Online can offer consultancy about how to support people with low or no digital skills – including remote support during conditions of lockdown, self-isolation, and physical distancing – and/or specific advice around health information and services.
We can also provide maps which compare local rather than national data, so you can assess local levels of risk.
Please [get in touch](mailto:[email protected]) to find out how we can help in your area with our mapping and analysis services.
We also welcome comments and feedback about the map, and how it might be made more useful.
### Notes on our approach
We have applied weightings to the reported populations when ranking the practices.
Patient numbers in older age brackets are multiplied by larger amounts in our model, because of their increased risk of digital exclusion and vulnerability to Covid-19.
However, the number of patients aged 65+ reported in the pop-up data bubbles is the unweighted (actual) number.
The map uses the *national* ranking of each practice by age: _i.e._ the purple fill means it is in the top 20% in England by proportion of older patients.
We can provide maps where instead the top 20% is identified within a regional or more local set of GP practices.
Of the `r comma(practices_included)` practices where there is Patient Online data, `r comma(less_than_forty)` (`r percent(less_than_forty/practices_included)`) have *less than* 40% of patients enabled for online services, and `r comma(more_than_forty)` (`r percent(more_than_forty/practices_included)`) have 40% or more of patients enabled.
40% is chosen as it is very near to the current median registration rate. The current (October 2021) _overall_ national rate for England is 41.99% ([Percentage of patients enabled for at least one online service](https://digital.nhs.uk/data-and-information/publications/statistical/mi-patient-online-pomi/current).)
Where we refer to "online services" we mean:
booking/cancelling GP appointments,
ordering repeat prescriptions,
and/or accessing information in personal records.
#### Data sources
1. Practice name, size and location data are obtained from
[NHS Digital Patients Registered](`r age_data_url`) (`r age_data_date`).
2. Data on availability and use of Patient Online services is from
[NHS Digital](https://digital.nhs.uk/data-and-information/data-collections-and-data-sets/data-collections/pomi),
latest data `r latest_pomi_date`.
Unfortunately, Patient Online data was unavailable for `r nrow(practices_excluded)` of the practices listed in (1), and these are not included on the map.
3. Conversion from practice postcode to map location is provided by the [postcodes.io](https://postcodes.io) service.
Small amounts of 'jitter' have been applied to locations in order to reduce overlap, where two or more practices share a postcode.
4. The basemap is from
[OpenStreetMap](https://openstreetmap.org),
with CartoDB tiles.