Disagapp is an application written in R that can be used to perform disaggregation regression analyses using {disaggregation}. A version is deployed to https://disagapp.le.ac.uk/
Disagapp was built using the {shinyscholar} template which was itself forked from {wallace} v2.0.5 (CRAN, website)
Install disagapp via Github and run the application with the following R code.
install.packages("devtools")
devtools::install_github("simon-smart88/disagapp")
library(disagapp)
run_disagapp()
The application is divided in components that are steps in the analysis and modules that are possible options in each step of the analysis. Each of the modules calls a function of the same name, either in this package or in {disaggregation}
.
resp
)
cov
)
{geodata}
.tif
formatagg
)
.tif
formatprep
)
fit
)
pred
)
rep
)
.Rmd
file that completely replicates the analysisdevtools::install_github("simon-smart88/shinyscholar")
git clone https://github.com/simon-smart88/disagapp
shinyscholar::create_module()
to create the module files:
id
is the module identifier formed from the component identifier (listed above) and a single word identifier for the module. We will use fit_improved
as the identifier in this example.dir
is the root directory of the application.map
should be set to TRUE
if the results of the module need to be plotted on the map.result
should be set to TRUE
if the module produces graphs to be presented in the Results tab.rmd
should be set to TRUE
if the module should be included in the reproducible Rmarkdown.save
should be set to TRUE
if the module has inputs that should be saved and loaded when the app is saved.download
should be set to TRUE
if the module will produce an output that you want the user to be able to download.async
should be set to TRUE
if the the module takes more than a few seconds to run so that it operates asynchronously.init
should be left set as FALSE
.fit_improved_f.R
in the R/
directory and copy the code you have developed into the fit_improved
function.logger
parameter as the last parameter that is set to NULL
by default.async
parameter as the last parameter that is set to FALSE
by default.if (async)
blocks to unwrap rasters using terra::unwrap()
at the start of the function and wrap them at the end using terra::wrap()
list()
along with the main outputwriteLog
or asyncLog
depending on whether the function is synchronous or asynchronous respectively e.g.:
if (!inherits(shape, "sf")){
logger |> writeLog(type = "error", "shape must be an sf object")
return()
}
if (!inherits(shape, "sf")){
return(async |> asyncLog(type = "error", "shape must be an sf object"))
}
R/
directory for examples.test-fit_improved.R
file in the tests/testthat/
directory and add tests that check the error handling checks function correctly and that the function produces the desired results.tests/testthat/helper_data.R
that may help to test the function.devtools::document()
and then install with devtools::install()
inst/shiny/modules/
directory named with the module identifier and ending in .yml
, .R
, .md
and .Rmd
.yml
file is a configuration file and contains five lines:
component
contains the component the module belongs to e.g. "fit"
short_name
contains a short name for the module and is used in the user interface so should be formatted accordingly e.g. "Fit improved model
”long_name
contains a longer name for the module and is also used in the user interface e.g. "Fit an improved model using the new method"
authors
contains your name: e.g. "Simon E H Smart"
package
contains a list of packages used in the module inside square brackets and without quotes e.g. [terra, disaggregation]
class:
and then add mandatory
, choice
or optional
after it (without quotes) depending on whether it is mandatory to use the module, it is one of several of choices but one must be used or it is optional. Depending on the function of the module, you may need to update the .yml
of other modules in the component, for example currently the fit_fit
module is mandatory
but if we were to add a new module to the component that could be changed to choice
.common
object, defined in the common.R
file in the inst/shiny/
directory. If the module uses data created by previous modules you need to find the name of these objects in that file, for example the response data is stored in common$shape
and the unprepared covariates in common$covs
. If the module produces a new type of object then you need to add a new object to the common
data structure by editing the common.R
file. This is not necessary if, for example the module adds a new method for uploading response data or another covariate as these would add their results to common$shape
and common$covs
respectively.R
file is the main module file and contains several sections:
<identifier>_module_ui
function contains the *Input elements which should correspond with the parameters of the function. See https://shiny.posit.co/r/components/ for available input widgets you can use. The input ids need wrapping inside ns()
to create ids that are unique to the module. The template contains an actionButton()
which when clicked runs the code inside the *_module_server
function.<identifier>_module_server
function contains an observeEvent()
which is run when the actionButton()
in the UI is clicked.common$logger %>% writeLog()
. These checks are distinct from those inside the function as they are checking that the state of the app is correct - for example if the module requires the data to have been prepared, you should check that common$prep
is not NULL
. See the documentation of shinyscholar::writeLog()
for more details.common
object.trigger()
is called which can be used to trigger actions elsewhere in the module or app using watch()
. This should not need editing.common
objects to produce outputs like plots using render*
objects which will be passed to the _module_result
function. Inside each render*
function you should add a req()
containing the common object created by the module to stop it executing before the object exists and a watch()
to trigger the execution once the object is created.results
section waits for the results and processes them. Much of the setup is handled automatically in the template module.
common$tasks$fit_improved$invoke()
SpatRaster
objects are present in the function inputs using terra::wrap()
common
if it is the correct type. If an error is returned it will be sent to the logger.SpatRaster
objects in the returned object using terra::unwrap()
<identifier>_module_result
function contains the *Output()
functions that render the results created in the result block of the module server. As in the *_module_ui
function, the object ids need wrapping inside ns()
.<identifier>_module_map
function updates the leaflet map. map
is a leafletProxy()
object created in the main server function so leaflet functions can be piped to it e.g. map |> addRasterImage()
. There are several functions in the package that facilitate adding objects to the map e.g. raster_map()
and shape_map()
which you can either use directly or modify to suit your needs. If you are unfamiliar with leaflet, it is probably sensible to develop this function outside of Shiny.*_module_rmd
function can be left blank for now..md
file contains the module guidance.resp
, cov
or agg
module, some other modules need updating to listen for changes made by the module by adding e.g. watch("fit_improved")
. Modules to update are as follows:
resp
modules update resp_simplify.R
, prep_mesh.R
and prep_final.R
. You should also add watch("resp_edit")
to your own module underneath the existing watch()
line.cov
modules update prep_summary.R
and rep_covs.R
.agg
modules update prep_summary.R
and rep_covs.R
.global.R
file in the inst/shiny/
directory and navigate to the line that creates base_module_configs
. Add a new line to the object in the relevant position to load the new module. The order of modules is important as this object defines the order of chunks in the Rmarkdown and so modules must be placed after any modules that they use the results of. For example if our fit_improved
module used the results of the fit_fit
module, it needs to be placed after it in the list.common
that contain a SpatRaster
, the core_save.R
and core_load.R
files need to be edited to wrap and unwrap them. Use wrap_terra()
and unwrap_terra()
for this as they can handle cases where the objects are NULL
e.g. when your module has not been used. Objects need wrapping before saving, unwrapping after saving and unwrapping after loading. There are many other lines already doing this for other objects.load_file_path
that contains the path to the load file, it will be loaded automatically when the app starts.shinyscholar::save_and_load(".", "fit_improved")
to add lines of code to the module that save and load the inputs and then shinyscholar::metadata(".", "fit_improved")
to add lines to the metadata block in the module server, the module_rmd function and to the .Rmd
file..Rmd
to use the input variables to call the module function. The variables are added automatically by shinyscholar::metadata()
and take the form of e.g. {{fit_improved_input1}}
. The curly brackets are required for the input values to be transferred when the user reproduces their analysis. The common
objects have corresponding variables inside the .Rmd
files of the modules and these need to used so that the chunk of the new module can be linked to previous chunks. For example common$prep
containing the prepared data is replaced by prepared_data
in the .Rmd
files.helper-fit_improved.R
which runs the app and saves it after running the module and the module test file which you created earlier to test the module’s function. This architecture is necessary because saving the app in tests fails erratically and this setup allows the test to be rerun until it saves successfully.helper-fit_improved.R
you start the app, set any necessary input values and then run the module. Add a section to test-fit_improved.R
that calls then function defined in the helper function using e.g. rerun_test_setup("fit_improved_test")
and which then reads the saved file and checks that the common
object modified or created in the module is in the correct state. Note that if the module runs asynchronously you need to wait for it to complete by adding app$wait_for_value(input = "fit_improved-complete")
before saving the app.devtools::test()
.To update deployments running on shinyserver it is necessary to update the package and also copy the contents of /inst
into the /apps
directory.