1 #' @importFrom methods is
2 #' @importFrom utils read.csv write.csv
3 server
<- function(input
, output
) {
4 reactive
<- shiny
::reactiveValues(
5 data_table
=data.frame(Name
=character(0), `Reporting Frequency`
=character(0), `Case Counts`
=numeric(0), check.names
=FALSE),
6 est_table
=data.frame(Dataset
=character(0)),
10 # Validate and add datasets when button is clicked.
11 # Also evaluate the new datasets on existing estimators.
12 shiny
::observeEvent(input$addData
, {
13 # Option 1: Manual entry.
14 if (input$dataInputMethod
== 1) {
17 # Ensure the dataset name is not blank.
18 if (grepl("^\\s*$", input$dataName
)) {
19 output$dataNameWarn
<- shiny
::renderText("Error: The dataset name cannot be blank.")
20 checks_passed
<- FALSE
22 # Ensure the dataset name is not a duplicate.
23 else if (input$dataName
%in% reactive$data_table
[,1]) {
24 output$dataNameWarn
<- shiny
::renderText("Error: There is already a dataset with the specified name.")
25 checks_passed
<- FALSE
28 output$dataNameWarn
<- shiny
::renderText("")
30 # Ensure the case counts consist only of non-negative integers, separated by commas.
31 counts
<- as.numeric(unlist(strsplit(input$dataCounts
, split
=",")))
32 if (any(is.na(counts
)) || any(counts
<= 0) || any(counts
%% 1 != 0)) {
33 output$dataCountsWarn
<- shiny
::renderText("Error: The list of case counts should only contain non-negative integers, separated by commas.")
34 checks_passed
<- FALSE
36 # Ensure the case counts contain at least two entries.
37 else if (length(counts
) < 2) {
38 output$dataCountsWarn
<- shiny
::renderText("Error: The list of case counts should contain at least two entries.")
39 checks_passed
<- FALSE
42 output$dataCountsWarn
<- shiny
::renderText("")
45 d
<- data.frame(input$dataName
, input$dataUnits
, t(counts
))
49 checks_passed
<- FALSE
51 # Option 2: Upload .csv
52 if (input$dataInputMethod
== 2)
53 d
<- try(read.csv(input$dataUpload$datapath
, header
=FALSE))
54 # Option 3: Paste .csv
56 d
<- try(read.csv(text
=input$dataPaste
, header
=FALSE))
58 if (is(d
, "try-error"))
59 output$dataCSVWarn
<- shiny
::renderText("Error reading file.")
60 else if (ncol(d
) < 4 || anyNA(d
[,1]) || anyNA(sapply(d
[,3:4], as.numeric
)) || !all(trimws(d
[,2]) %in% c("Daily", "Weekly")))
61 output$dataCSVWarn
<- shiny
::renderText("Error: The provided .csv file does not match the required format.")
62 else if (length(intersect(reactive$data_table
[,1], d
[,1])) > 0)
63 output$dataCSVWarn
<- shiny
::renderText("Error: The provided .csv file contains dataset names which already exist.")
64 else if (length(unique(d
[,1])) != length(d
[,1]))
65 output$dataCSVWarn
<- shiny
::renderText("Error: The provided .csv file contains duplicate dataset names.")
67 output$dataCSVWarn
<- shiny
::renderText("")
73 d
[,3:ncol(d
)] <- apply(d
[,3:ncol(d
)], 2, as.numeric
)
74 d
[,3] <- data.frame(I(lapply(split(d
[,3:ncol(d
)], 1:nrow(d
)), function(x
) x
[!is.na(x
)])))
76 d
[,2] <- trimws(d
[,2])
77 colnames(d
) <- c("Name", "Reporting Frequency", "Case Counts")
78 reactive$data_table
<- rbind(reactive$data_table
, d
)
79 reactive$est_table
<- update_est_row(input
, output
, d
, reactive$estimators
, reactive$est_table
)
83 output$dataTable
<- shiny
::renderDataTable(reactive$data_table
, escape
=FALSE)
84 output$estTable
<- shiny
::renderDataTable(reactive$est_table
, escape
=FALSE)
86 # Download table of estimates as a .csv file.
87 output$downloadEst
<- shiny
::downloadHandler(
88 filename
=function() { paste0("Rnaught-", Sys.Date(), ".csv") },
89 content
=function(file
) { write.csv(reactive$est_table
, file
) }
92 shiny
::observeEvent(input$addWP
, {
93 if (input$serialWPKnown
== 1) {
94 serial
<- validate_serial(input
, output
, "serialWPInput", "serialWPWarn")
96 reactive$estimators
[[length(reactive$estimators
)+1]] <- list(method
="WP", mu
=serial
, search
=list(B
=100, shape.max
=10, scale.max
=10), mu_units
=input$serialWPUnits
)
97 reactive$est_table
<- update_est_col(input
, output
, reactive$data_table
, reactive$estimators
[[length(reactive$estimators
)]], reactive$est_table
)
101 checks_passed
<- TRUE
103 grid_length
<- as.numeric(input$gridLengthInput
)
104 max_shape
<- as.numeric(input$gridShapeInput
)
105 max_scale
<- as.numeric(input$gridScaleInput
)
107 if (is.na(grid_length
) || grid_length
<= 0 || grid_length
%% 1 != 0) {
108 output$gridLengthWarn
<- shiny
::renderText("Error: The grid size must be a positive integer.")
109 output$gridShapeWarn
<- shiny
::renderText("")
110 output$gridScaleWarn
<- shiny
::renderText("")
111 checks_passed
<- FALSE
114 output$gridLengthWarn
<- shiny
::renderText("")
116 if (is.na(max_shape
) || max_shape
< 1 / grid_length
) {
117 output$gridShapeWarn
<- shiny
::renderText("Error: The maximum shape must be at least the reciprocal of the grid length.")
118 checks_passed
<- FALSE
121 output$gridShapeWarn
<- shiny
::renderText("")
123 if (is.na(max_scale
) || max_scale
< 1 / grid_length
) {
124 output$gridShapeWarn
<- shiny
::renderText("Error: The maximum scale must be at least the reciprocal of the grid length.")
125 checks_passed
<- FALSE
128 output$gridScaleWarn
<- shiny
::renderText("")
132 reactive$estimators
[[length(reactive$estimators
)+1]] <- list(method
="WP", mu
=NA, search
=list(B
=grid_length
, shape.max
=max_shape
, scale.max
=max_scale
), mu_units
=input$serialWPUnits
)
133 reactive$est_table
<- update_est_col(input
, output
, reactive$data_table
, reactive$estimators
[[length(reactive$estimators
)]], reactive$est_table
)
138 shiny
::observeEvent(input$addseqB
, {
139 serial
<- validate_serial(input
, output
, "serialseqBInput", "serialseqBWarn")
140 checks_passed
<- !is.na(serial
)
142 kappa
<- as.numeric(input$kappaInput
)
143 if (is.na(kappa
) || kappa
<= 0) {
144 output$kappaWarn
<- shiny
::renderText("Error: The maximum value must be a positive number.")
145 checks_passed
<- FALSE
148 output$kappaWarn
<- shiny
::renderText("")
151 reactive$estimators
[[length(reactive$estimators
)+1]] <- list(method
="seqB", mu
=serial
, kappa
=kappa
, mu_units
=input$serialseqBUnits
)
152 reactive$est_table
<- update_est_col(input
, output
, reactive$data_table
, reactive$estimators
[[length(reactive$estimators
)]], reactive$est_table
)
156 shiny
::observeEvent(input$addID
, {
157 serial
<- validate_serial(input
, output
, "serialIDInput", "serialIDWarn")
158 if (!is.na(serial
)) {
159 reactive$estimators
[[length(reactive$estimators
)+1]] <- list(method
="ID", mu
=serial
, mu_units
=input$serialIDUnits
)
160 reactive$est_table
<- update_est_col(input
, output
, reactive$data_table
, reactive$estimators
[[length(reactive$estimators
)]], reactive$est_table
)
164 shiny
::observeEvent(input$addIDEA
, {
165 serial
<- validate_serial(input
, output
, "serialIDEAInput", "serialIDEAWarn")
166 if (!is.na(serial
)) {
167 reactive$estimators
[[length(reactive$estimators
)+1]] <- list(method
="IDEA", mu
=serial
, mu_units
=input$serialIDEAUnits
)
168 reactive$est_table
<- update_est_col(input
, output
, reactive$data_table
, reactive$estimators
[[length(reactive$estimators
)]], reactive$est_table
)
173 validate_serial
<- function(input
, output
, serialInputId
, serialWarnId
) {
174 serial
<- as.numeric(input
[[serialInputId
]])
175 if (is.na(serial
) || serial
<= 0) {
176 output
[[serialWarnId
]] <- shiny
::renderText("Error: The mean serial interval should be a non-negative number.")
180 output
[[serialWarnId
]] <- shiny
::renderText("") # Clear warning text.
185 update_est_col
<- function(input
, output
, datasets
, estimator
, est_table
) {
186 if (nrow(datasets
) == 0)
187 new_est_table
<- data.frame(matrix(nrow
=0, ncol
=ncol(est_table
)+1))
189 estimates
<- rep(NA, nrow(datasets
))
191 for (row
in 1:nrow(datasets
))
192 estimates
[row
] <- eval_estimator(input
, output
, estimator
, datasets
[row
,])
194 if (nrow(est_table
) == 0)
195 new_est_table
<- cbind(datasets
[,1], estimates
)
197 new_est_table
<- cbind(est_table
, estimates
)
200 colnames(new_est_table
) <- c(colnames(est_table
), shiny
::HTML(paste0(estimator$method
, "<br/>(μ = ", estimator$mu
, " ", tolower(estimator$mu_units
), ")")))
201 return(new_est_table
)
204 update_est_row
<- function(input
, output
, datasets
, estimators
, est_table
) {
205 if (length(estimators
) == 0) {
206 if (nrow(est_table
) == 0)
207 new_est_table
<- data.frame(datasets
[,1])
209 new_est_table
<- data.frame(c(est_table
[,1], datasets
[,1]))
211 colnames(new_est_table
) <- colnames(est_table
)
214 new_est_table
<- data.frame(matrix(nrow
=nrow(datasets
), ncol
=length(estimators
)))
216 for (row
in 1:nrow(datasets
))
217 for (col
in 1:length(estimators
))
218 new_est_table
[row
, col
] <- eval_estimator(input
, output
, estimators
[[col
]], datasets
[row
,])
220 new_est_table
<- cbind(datasets
[,1], new_est_table
)
221 colnames(new_est_table
) <- colnames(est_table
)
222 new_est_table
<- rbind(est_table
, new_est_table
)
225 return(new_est_table
)
228 eval_estimator
<- function(input
, output
, estimator
, dataset
) {
229 # Adjust serial interval to match time unit of case counts.
230 serial
<- estimator$mu
231 if (estimator$mu_units
== "Days" && dataset
[2] == "Weekly")
233 else if (estimator$mu_units
== "Weeks" && dataset
[2] == "Daily")
237 if (estimator$method
== "WP") {
238 estimate
<- WP(unlist(dataset
[3]), mu
=serial
, search
=estimator$search
)
240 if (!is.na(estimator$mu
))
241 estimate
<- round(estimate$Rhat
, 2)
242 # Display the estimated mean of the serial distribution if mu was not specified.
244 if (dataset
[2] == "Daily")
248 MSI
<- sum(estimate$SD$supp
* estimate$SD$pmf
)
249 estimate
<- shiny
::HTML(paste0(round(estimate$Rhat
, 2), "<br/>(μ = ", round(MSI
, 2), " ", mu_units
, ")"))
253 else if (estimator$method
== "seqB")
254 estimate
<- round(seqB(unlist(dataset
[3]), mu
=serial
, kappa
=estimator$kappa
)$Rhat
, 2)
256 else if (estimator$method
== "ID")
257 estimate
<- round(ID(unlist(dataset
[3]), mu
=serial
), 2)
258 # Incidence Decay with Exponential Adjustement
259 else if (estimator$method
== "IDEA")
260 estimate
<- round(IDEA(unlist(dataset
[3]), mu
=serial
), 2)