Global options
epishiny
sets the following global options on start up
that are used across various modules. You can change any of these to
suit your needs via the options()
function as below. Make
sure you do this after epishiny
has been loaded
for the changes to take effect.
# first load libraries
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(sf))
suppressPackageStartupMessages(library(epishiny))
# then set your options. the options below are the defaults
options(
epishiny.na.label = "(Missing)", # label to be used for NA values in outputs
epishiny.count.label = "Patients", # if data is un-aggregated, the label to represent row counts
epishiny.week.letter = "W", # letter to represent 'Week'. Change to S for 'Semaine' etc
epishiny.week.start = 1 # day the epiweek starts on. 1 = Monday, 7 = Sunday
)
Setting up data for epishiny
epishiny
can work with either aggregated or
un-aggregated data. Here we will use an example of an un-aggregated
‘linelist’ dataset. A linelist is a (tidy) data format used in public
health data collection with each row representing an individual
(patient, participant, etc) and each column representing a variable
associated with said individual.
df_ll
is an example dataset within the package
containing data for a fake measles outbreak in Yemen. The data contains
temporal, demographic, and geographic information for each patient, as
well as other medical indicators.
glimpse(df_ll)
#> Rows: 1,000
#> Columns: 30
#> $ case_id <chr> "P0001", "P0002", "P0003", "P0004", "P…
#> $ date_notification <date> 2022-10-07, 2022-09-18, 2022-04-11, 2…
#> $ date_symptom_start <date> 2022-10-03, 2022-09-12, 2022-04-01, 2…
#> $ date_hospitalisation_start <date> NA, NA, 2022-04-14, 2022-04-20, 2022-…
#> $ date_sample_occurred <date> 2022-10-12, NA, NA, NA, 2022-08-09, 2…
#> $ date_sample_lab_result_occurred <date> 2022-10-16, NA, NA, NA, 2022-08-10, 2…
#> $ date_hospitalisation_end <date> NA, NA, 2022-04-23, 2022-05-04, 2022-…
#> $ sex_id <chr> "Male", "Male", "Male", NA, NA, "Unkno…
#> $ age_years <dbl> 42.00000000, 0.13150685, 36.00000000, …
#> $ age_group <fct> 35-49, <5, 35-49, <5, 50+, <5, <5, NA,…
#> $ adm1_origin <chr> "Amran / عمران", "Raymah / ريمه", "Amr…
#> $ adm2_origin <chr> "Huth / حوث", "As Salafiyyah / السلفيه…
#> $ adm3_origin <chr> "Huth / حوث", "An Nubah / النوبه", NA,…
#> $ adm4_origin <chr> "Tho Hamzah / ذو حمزه", "Al-Acamah Al-…
#> $ adm1_pcode <chr> "YE29", "YE31", "YE29", "YE29", "YE31"…
#> $ adm2_pcode <chr> "YE2902", "YE3102", "YE2901", "YE2907"…
#> $ fever <chr> "Yes", "Yes", NA, "Yes", NA, "Yes", "N…
#> $ rash <chr> "Yes", "Uncertain", "Uncertain", "No",…
#> $ cough <chr> "Yes", "No", "Yes", "Yes", "No", NA, "…
#> $ oral_lesions <chr> "Uncertain", "No", "Yes", "Yes", "Unce…
#> $ muac <chr> NA, "Yellow (115 - 124 mm)", "Red (<11…
#> $ oedema <chr> NA, NA, NA, "+++ (feet, shins and face…
#> $ hospitalised_yn <chr> "No", "No", "Yes", "Yes", "Yes", NA, "…
#> $ measles_stage <chr> "Post-measles", "Acute measles", "Post…
#> $ oxygen <chr> "Yes", "No", "No", "No", "Yes", "Yes",…
#> $ vacci_measles_yn <chr> "No", "No", "Yes - card", "Uncertain",…
#> $ vacci_measles_doses <chr> "2 doses", "Uncertain", "4 doses or mo…
#> $ TDR_malaria <chr> "Undetermined", "Positive", "Negative"…
#> $ outcome <chr> NA, "Healed", "Deceased", "Healed", "H…
#> $ death_cause <chr> "Respiratory distress", "Respiratory d…
We have geographical patient origin data contained in the
adm
columns, each being an administrative boundary level in
Yemen. If we wan’t to visualise this data on a map we need to provide
accompanying geo data that we can match to the linelist data.
sf_yem
, a dataset within the package, is a list containing
geo boundary data for adm1 and adm2 levels in Yemen, stored as sf objects.
sf_yem
#> $adm1
#> Simple feature collection with 22 features and 5 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: 41.81479 ymin: 12.10665 xmax: 54.5382 ymax: 19
#> Geodetic CRS: WGS 84
#> First 10 features:
#> adm0_iso3 adm0_name adm1_name pcode adm1_pop
#> 1 YEM Yemen / اليمن Ibb / اب YE11 3117999
#> 2 YEM Yemen / اليمن Abyan / ابين YE12 619003
#> 3 YEM Yemen / اليمن Sana'a City / امانة العاصمه YE13 3981000
#> 4 YEM Yemen / اليمن Al Bayda / البيضاء YE14 830001
#> 5 YEM Yemen / اليمن Ta'iz / تعز YE15 3487612
#> 6 YEM Yemen / اليمن Al Jawf / الجوف YE16 645000
#> 7 YEM Yemen / اليمن Hajjah / حجه YE17 2415001
#> 8 YEM Yemen / اليمن Al Hodeidah / الحديده YE18 3653999
#> 9 YEM Yemen / اليمن Hadramawt / حضرموت YE19 1618329
#> 10 YEM Yemen / اليمن Dhamar / ذمار YE20 2170000
#> geometry
#> 1 MULTIPOLYGON (((44.08076 14...
#> 2 MULTIPOLYGON (((46.29563 14...
#> 3 MULTIPOLYGON (((44.3338 15....
#> 4 MULTIPOLYGON (((44.72676 14...
#> 5 MULTIPOLYGON (((43.41111 12...
#> 6 MULTIPOLYGON (((46.34001 17...
#> 7 MULTIPOLYGON (((42.80233 15...
#> 8 MULTIPOLYGON (((42.6918 13....
#> 9 MULTIPOLYGON (((50.83766 16...
#> 10 MULTIPOLYGON (((44.70527 14...
#>
#> $adm2
#> Simple feature collection with 335 features and 6 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: 41.81479 ymin: 12.10665 xmax: 54.5382 ymax: 19
#> Geodetic CRS: WGS 84
#> First 10 features:
#> adm0_iso3 adm0_name adm1_name adm2_name pcode adm2_pop
#> 1 YEM Yemen / اليمن Ibb / اب Al Qafr / القفر YE1101 152248
#> 2 YEM Yemen / اليمن Ibb / اب Yarim / يريم YE1102 257878
#> 3 YEM Yemen / اليمن Ibb / اب Ar Radmah / الرضمه YE1103 111778
#> 4 YEM Yemen / اليمن Ibb / اب An Nadirah / النادره YE1104 108243
#> 5 YEM Yemen / اليمن Ibb / اب Ash Sha'ir / الشعر YE1105 58233
#> 6 YEM Yemen / اليمن Ibb / اب As Saddah / السده YE1106 119880
#> 7 YEM Yemen / اليمن Ibb / اب Al Makhadir / المخادر YE1107 166083
#> 8 YEM Yemen / اليمن Ibb / اب Hobeish / حبيش YE1108 152918
#> 9 YEM Yemen / اليمن Ibb / اب Hazm Al Odayn / حزم العدين YE1109 115630
#> 10 YEM Yemen / اليمن Ibb / اب Far' Al Odayn / فرع العدين YE1110 130477
#> geometry
#> 1 MULTIPOLYGON (((43.82405 14...
#> 2 MULTIPOLYGON (((44.2682 14....
#> 3 MULTIPOLYGON (((44.4855 14....
#> 4 MULTIPOLYGON (((44.4946 14....
#> 5 MULTIPOLYGON (((44.307 14.0...
#> 6 MULTIPOLYGON (((44.44934 14...
#> 7 MULTIPOLYGON (((44.22156 14...
#> 8 MULTIPOLYGON (((44.01363 14...
#> 9 MULTIPOLYGON (((44.05006 14...
#> 10 MULTIPOLYGON (((43.80641 14...
Luckily we can easily match this to the linelist data via the
pcode
columns. In real world scenarios, you might not have
harmonious pcodes available in your data, so if you need help matching
messy epi data to geo data, check out our hmatch package.
Now let’s set up the geo data in the format required for epishiny. We
use the geo_layer()
function to define a layer, and since
we have multiple layers in this case, we combine them in a list.
# setup geo data for adm1 and adm2 using the
# geo_layer function to be passed to the place module
# if population variable is provided, attack rates
# will be shown on the map as a choropleth
geo_data <- list(
geo_layer(
layer_name = "Governorate", # name of the boundary layer
sf = sf_yem$adm1, # sf object with boundary polygons
name_var = "adm1_name", # column with place names
pop_var = "adm1_pop", # column with population data (optional)
join_by = c("pcode" = "adm1_pcode") # geo to data join vars: LHS = sf, RHS = data
),
geo_layer(
layer_name = "District",
sf = sf_yem$adm2,
name_var = "adm2_name",
pop_var = "adm2_pop",
join_by = c("pcode" = "adm2_pcode")
)
)
Launching modules
Now we have everything we need to launch the time
,
place
and person
modules. As well as for use
in dashboards, each epishiny module can be launched individually from
within an R script. Below is an example of each module passing the
minimum required arguments. See each modules documentation
(?time_server
, ?place_server
,
?person_server
) for full details of all features and
arguments that can be passed.
Time
launch_module(
module= "time",
df = df_ll,
date_vars = "date_notification"
)
Place
# launch place map module
launch_module(
module = "place",
df = df_ll,
geo_data = geo_data
)
Person
launch_module(
module= "person",
df = df_ll,
age_group_var = "age_group",
sex_var = "sex_id",
male_level = "Male",
female_level = "Female"
)
Dashboards
epishiny
modules can be included in any shiny dashboard
by adding the module_ui
component to the shiny ui function,
and the module_server
component to the shiny server
function. If you’re new to shiny, don’t worry, epishiny
is
designed so that you can build fully-featured dashboards without
worrying about what is happening on the shiny back-end. But if you do
want to learn more, a good place to start is the Mastering Shiny book, authored
by Hadley Wickham.
epishiny
modules use UI elements from the bslib package, so
the only constraint is that you use bslib
for your
dashboards user interface. See the bslib
shiny dashboards article for full details on designing a UI with
bslib
.
Below is a full dashboard example incorporating all
epishiny
modules in a bslib
user
interface.
library(shiny)
library(bslib)
library(epishiny)
# example package data
data("df_ll") # linelist
data("sf_yem") # sf geo boundaries for Yemen admin 1 & 2
# setup geo data for adm1 and adm2 using the
# geo_layer function to be passed to the place module
# if population variable is provided, attack rates
# will be shown on the map as a choropleth
geo_data <- list(
geo_layer(
layer_name = "Governorate", # name of the boundary level
sf = sf_yem$adm1, # sf object with boundary polygons
name_var = "adm1_name", # column with place names
pop_var = "adm1_pop", # column with population data (optional)
join_by = c("pcode" = "adm1_pcode") # geo to data join vars: LHS = sf, RHS = data
),
geo_layer(
layer_name = "District",
sf = sf_yem$adm2,
name_var = "adm2_name",
pop_var = "adm2_pop",
join_by = c("pcode" = "adm2_pcode")
)
)
# range of dates used in filter module to filter time period
date_range <- range(df_ll$date_notification, na.rm = TRUE)
# define date variables in data as named list to be used in app
date_vars <- c(
"Date of notification" = "date_notification",
"Date of onset" = "date_symptom_start",
"Date of hospitalisation" = "date_hospitalisation_start",
"Date of outcome" = "date_hospitalisation_end"
)
# define categorical grouping variables
# in data as named list to be used in app
group_vars <- c(
"Governorate" = "adm1_origin",
"Sex" = "sex_id",
"Hospitalised" = "hospitalised_yn",
"Vaccinated measles" = "vacci_measles_yn",
"Outcome" = "outcome"
)
# user interface
ui <- page_sidebar(
title = "epishiny",
# sidebar
sidebar = filter_ui(
"filter",
group_vars = group_vars,
date_range = date_range,
period_lab = "Notification period"
),
# main content
layout_columns(
col_widths = c(12, 7, 5),
place_ui(
id = "map",
geo_data = geo_data,
group_vars = group_vars
),
time_ui(
id = "curve",
title = "Time",
date_vars = date_vars,
group_vars = group_vars,
ratio_line_lab = "Show CFR line?"
),
person_ui(id = "age_sex")
)
)
# app server
server <- function(input, output, session) {
app_data <- filter_server(
id = "filter",
df = df_ll,
date_var = "date_notification",
group_vars = group_vars
)
place_server(
id = "map",
df = reactive(app_data()$df),
geo_data = geo_data,
group_vars = group_vars,
filter_info = reactive(app_data()$filter_info)
)
time_server(
id = "curve",
df = reactive(app_data()$df),
date_vars = date_vars,
group_vars = group_vars,
show_ratio = TRUE,
ratio_var = "outcome",
ratio_lab = "CFR",
ratio_numer = "Deceased",
ratio_denom = c("Deceased", "Healed", "Abandonment"),
filter_info = reactive(app_data()$filter_info)
)
person_server(
id = "age_sex",
df = reactive(app_data()$df),
age_var = "age_years",
sex_var = "sex_id",
male_level = "Male",
female_level = "Female",
filter_info = reactive(app_data()$filter_info)
)
}
# launch app
if (interactive()) {
shinyApp(ui, server)
}