]> nmode's Git Repositories - Rnaught/blob - R/server.R
99290107147bf33260f505b8ea59493177b96d87
[Rnaught] / R / server.R
1 #' @importFrom methods is
2 #' @importFrom utils read.csv write.csv
3 server <- function(input, output) {
4 # Hide the sidebar if the 'About' tab is active.
5 shiny::observeEvent(input$tabset, {
6 if (input$tabset == "About") {
7 shinyjs::hideElement(selector="#sidebar")
8 shinyjs::removeCssClass("main", "col-sm-8")
9 shinyjs::addCssClass("main", "col-sm-12")
10 } else {
11 shinyjs::showElement(selector="#sidebar")
12 shinyjs::removeCssClass("main", "col-sm-12")
13 shinyjs::addCssClass("main", "col-sm-8")
14 }
15 })
16
17 reactive <- shiny::reactiveValues(
18 data_table=data.frame(Name=character(0), `Reporting Frequency`=character(0), `Case Counts`=numeric(0), check.names=FALSE),
19 est_table=data.frame(Dataset=character(0)),
20 estimators=list()
21 )
22
23 # Validate and add datasets when button is clicked.
24 # Also evaluate the new datasets on existing estimators.
25 shiny::observeEvent(input$addData, {
26 # Option 1: Manual entry.
27 if (input$dataInputMethod == 1) {
28 checks_passed <- TRUE
29
30 # Ensure the dataset name is not blank.
31 if (grepl("^\\s*$", input$dataName)) {
32 output$dataNameWarn <- shiny::renderText("Error: The dataset name cannot be blank.")
33 checks_passed <- FALSE
34 }
35 # Ensure the dataset name is not a duplicate.
36 else if (input$dataName %in% reactive$data_table[,1]) {
37 output$dataNameWarn <- shiny::renderText("Error: There is already a dataset with the specified name.")
38 checks_passed <- FALSE
39 }
40 else
41 output$dataNameWarn <- shiny::renderText("")
42
43 # Ensure the case counts consist only of non-negative integers, separated by commas.
44 counts <- as.numeric(unlist(strsplit(input$dataCounts, split=",")))
45 if (any(is.na(counts)) || any(counts <= 0) || any(counts %% 1 != 0)) {
46 output$dataCountsWarn <- shiny::renderText("Error: The list of case counts should only contain non-negative integers, separated by commas.")
47 checks_passed <- FALSE
48 }
49 # Ensure the case counts contain at least two entries.
50 else if (length(counts) < 2) {
51 output$dataCountsWarn <- shiny::renderText("Error: The list of case counts should contain at least two entries.")
52 checks_passed <- FALSE
53 }
54 else
55 output$dataCountsWarn <- shiny::renderText("")
56
57 if (checks_passed)
58 d <- data.frame(input$dataName, input$dataUnits, t(counts))
59 }
60
61 else {
62 checks_passed <- FALSE
63
64 # Option 2: Upload .csv
65 if (input$dataInputMethod == 2)
66 d <- try(read.csv(input$dataUpload$datapath, header=FALSE))
67 # Option 3: Paste .csv
68 else
69 d <- try(read.csv(text=input$dataPaste, header=FALSE))
70
71 if (is(d, "try-error"))
72 output$dataCSVWarn <- shiny::renderText("Error reading file.")
73 else if (ncol(d) < 4 || anyNA(d[,1]) || anyNA(sapply(d[,3:4], as.numeric)) || !all(trimws(d[,2]) %in% c("Daily", "Weekly")))
74 output$dataCSVWarn <- shiny::renderText("Error: The provided .csv file does not match the required format.")
75 else if (length(intersect(reactive$data_table[,1], d[,1])) > 0)
76 output$dataCSVWarn <- shiny::renderText("Error: The provided .csv file contains dataset names which already exist.")
77 else if (length(unique(d[,1])) != length(d[,1]))
78 output$dataCSVWarn <- shiny::renderText("Error: The provided .csv file contains duplicate dataset names.")
79 else {
80 output$dataCSVWarn <- shiny::renderText("")
81 checks_passed <- TRUE
82 }
83 }
84
85 if (checks_passed) {
86 d[,3:ncol(d)] <- apply(d[,3:ncol(d)], 2, as.numeric)
87 d[,3] <- data.frame(I(lapply(split(d[,3:ncol(d)], 1:nrow(d)), function(x) x[!is.na(x)])))
88 d <- d[,1:3]
89 d[,2] <- trimws(d[,2])
90 colnames(d) <- c("Name", "Reporting Frequency", "Case Counts")
91 reactive$data_table <- rbind(reactive$data_table, d)
92 reactive$est_table <- update_est_row(input, output, d, reactive$estimators, reactive$est_table)
93 }
94 })
95
96 output$dataTable <- shiny::renderDataTable(reactive$data_table, escape=FALSE)
97 output$estTable <- shiny::renderDataTable(reactive$est_table, escape=FALSE)
98
99 # Download table of estimates as a .csv file.
100 output$downloadEst <- shiny::downloadHandler(
101 filename=function() { paste0("Rnaught-", Sys.Date(), ".csv") },
102 content=function(file) { write.csv(reactive$est_table, file) }
103 )
104
105 shiny::observeEvent(input$addWP, {
106 if (input$serialWPKnown == 1) {
107 serial <- validate_serial(input, output, "serialWPInput", "serialWPWarn")
108 if (!is.na(serial)) {
109 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)
110 reactive$est_table <- update_est_col(input, output, reactive$data_table, reactive$estimators[[length(reactive$estimators)]], reactive$est_table)
111 }
112 }
113 else {
114 checks_passed <- TRUE
115
116 grid_length <- as.numeric(input$gridLengthInput)
117 max_shape <- as.numeric(input$gridShapeInput)
118 max_scale <- as.numeric(input$gridScaleInput)
119
120 if (is.na(grid_length) || grid_length <= 0 || grid_length %% 1 != 0) {
121 output$gridLengthWarn <- shiny::renderText("Error: The grid size must be a positive integer.")
122 output$gridShapeWarn <- shiny::renderText("")
123 output$gridScaleWarn <- shiny::renderText("")
124 checks_passed <- FALSE
125 }
126 else {
127 output$gridLengthWarn <- shiny::renderText("")
128
129 if (is.na(max_shape) || max_shape < 1 / grid_length) {
130 output$gridShapeWarn <- shiny::renderText("Error: The maximum shape must be at least the reciprocal of the grid length.")
131 checks_passed <- FALSE
132 }
133 else
134 output$gridShapeWarn <- shiny::renderText("")
135
136 if (is.na(max_scale) || max_scale < 1 / grid_length) {
137 output$gridShapeWarn <- shiny::renderText("Error: The maximum scale must be at least the reciprocal of the grid length.")
138 checks_passed <- FALSE
139 }
140 else
141 output$gridScaleWarn <- shiny::renderText("")
142 }
143
144 if (checks_passed) {
145 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)
146 reactive$est_table <- update_est_col(input, output, reactive$data_table, reactive$estimators[[length(reactive$estimators)]], reactive$est_table)
147 }
148 }
149 })
150
151 shiny::observeEvent(input$addseqB, {
152 serial <- validate_serial(input, output, "serialseqBInput", "serialseqBWarn")
153 checks_passed <- !is.na(serial)
154
155 kappa <- as.numeric(input$kappaInput)
156 if (is.na(kappa) || kappa <= 0) {
157 output$kappaWarn <- shiny::renderText("Error: The maximum value must be a positive number.")
158 checks_passed <- FALSE
159 }
160 else
161 output$kappaWarn <- shiny::renderText("")
162
163 if (checks_passed) {
164 reactive$estimators[[length(reactive$estimators)+1]] <- list(method="seqB", mu=serial, kappa=kappa, mu_units=input$serialseqBUnits)
165 reactive$est_table <- update_est_col(input, output, reactive$data_table, reactive$estimators[[length(reactive$estimators)]], reactive$est_table)
166 }
167 })
168
169 shiny::observeEvent(input$addID, {
170 serial <- validate_serial(input, output, "serialIDInput", "serialIDWarn")
171 if (!is.na(serial)) {
172 reactive$estimators[[length(reactive$estimators)+1]] <- list(method="ID", mu=serial, mu_units=input$serialIDUnits)
173 reactive$est_table <- update_est_col(input, output, reactive$data_table, reactive$estimators[[length(reactive$estimators)]], reactive$est_table)
174 }
175 })
176
177 shiny::observeEvent(input$addIDEA, {
178 serial <- validate_serial(input, output, "serialIDEAInput", "serialIDEAWarn")
179 if (!is.na(serial)) {
180 reactive$estimators[[length(reactive$estimators)+1]] <- list(method="IDEA", mu=serial, mu_units=input$serialIDEAUnits)
181 reactive$est_table <- update_est_col(input, output, reactive$data_table, reactive$estimators[[length(reactive$estimators)]], reactive$est_table)
182 }
183 })
184 }
185
186 validate_serial <- function(input, output, serialInputId, serialWarnId) {
187 serial <- as.numeric(input[[serialInputId]])
188 if (is.na(serial) || serial <= 0) {
189 output[[serialWarnId]] <- shiny::renderText("Error: The mean serial interval should be a non-negative number.")
190 serial <- NA
191 }
192 else
193 output[[serialWarnId]] <- shiny::renderText("") # Clear warning text.
194
195 return(serial)
196 }
197
198 update_est_col <- function(input, output, datasets, estimator, est_table) {
199 if (nrow(datasets) == 0)
200 new_est_table <- data.frame(matrix(nrow=0, ncol=ncol(est_table)+1))
201 else {
202 estimates <- rep(NA, nrow(datasets))
203
204 for (row in 1:nrow(datasets))
205 estimates[row] <- eval_estimator(input, output, estimator, datasets[row,])
206
207 if (nrow(est_table) == 0)
208 new_est_table <- cbind(datasets[,1], estimates)
209 else
210 new_est_table <- cbind(est_table, estimates)
211 }
212
213 colnames(new_est_table) <- c(colnames(est_table), shiny::HTML(paste0(estimator$method, "<br/>(&mu; = ", estimator$mu, " ", tolower(estimator$mu_units), ")")))
214 return(new_est_table)
215 }
216
217 update_est_row <- function(input, output, datasets, estimators, est_table) {
218 if (length(estimators) == 0) {
219 if (nrow(est_table) == 0)
220 new_est_table <- data.frame(datasets[,1])
221 else
222 new_est_table <- data.frame(c(est_table[,1], datasets[,1]))
223
224 colnames(new_est_table) <- colnames(est_table)
225 }
226 else {
227 new_est_table <- data.frame(matrix(nrow=nrow(datasets), ncol=length(estimators)))
228
229 for (row in 1:nrow(datasets))
230 for (col in 1:length(estimators))
231 new_est_table[row, col] <- eval_estimator(input, output, estimators[[col]], datasets[row,])
232
233 new_est_table <- cbind(datasets[,1], new_est_table)
234 colnames(new_est_table) <- colnames(est_table)
235 new_est_table <- rbind(est_table, new_est_table)
236 }
237
238 return(new_est_table)
239 }
240
241 eval_estimator <- function(input, output, estimator, dataset) {
242 # Adjust serial interval to match time unit of case counts.
243 serial <- estimator$mu
244 if (estimator$mu_units == "Days" && dataset[2] == "Weekly")
245 serial <- serial / 7
246 else if (estimator$mu_units == "Weeks" && dataset[2] == "Daily")
247 serial <- serial * 7
248
249 # White and Panago
250 if (estimator$method == "WP") {
251 estimate <- WP(unlist(dataset[3]), mu=serial, search=estimator$search)
252
253 if (!is.na(estimator$mu))
254 estimate <- round(estimate$Rhat, 2)
255 # Display the estimated mean of the serial distribution if mu was not specified.
256 else {
257 if (dataset[2] == "Daily")
258 mu_units <- "days"
259 else
260 mu_units <- "weeks"
261 MSI <- sum(estimate$SD$supp * estimate$SD$pmf)
262 estimate <- shiny::HTML(paste0(round(estimate$Rhat, 2), "<br/>(&mu; = ", round(MSI, 2), " ", mu_units, ")"))
263 }
264 }
265 # Sequential Bayes
266 else if (estimator$method == "seqB")
267 estimate <- round(seqB(unlist(dataset[3]), mu=serial, kappa=estimator$kappa)$Rhat, 2)
268 # Incidence Decay
269 else if (estimator$method == "ID")
270 estimate <- round(ID(unlist(dataset[3]), mu=serial), 2)
271 # Incidence Decay with Exponential Adjustement
272 else if (estimator$method == "IDEA")
273 estimate <- round(IDEA(unlist(dataset[3]), mu=serial), 2)
274
275 return(estimate)
276 }