]> nmode's Git Repositories - Rnaught/blob - R/server.R
e8d29705ac033a654d0b20a8a97cca938439008f
[Rnaught] / R / server.R
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)),
7 estimators=list()
8 )
9
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) {
15 checks_passed <- TRUE
16
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
21 }
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
26 }
27 else
28 output$dataNameWarn <- shiny::renderText("")
29
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
35 }
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
40 }
41 else
42 output$dataCountsWarn <- shiny::renderText("")
43
44 if (checks_passed)
45 d <- data.frame(input$dataName, input$dataUnits, t(counts))
46 }
47
48 else {
49 checks_passed <- FALSE
50
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
55 else
56 d <- try(read.csv(text=input$dataPaste, header=FALSE))
57
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.")
66 else {
67 output$dataCSVWarn <- shiny::renderText("")
68 checks_passed <- TRUE
69 }
70 }
71
72 if (checks_passed) {
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)])))
75 d <- d[,1:3]
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)
80 }
81 })
82
83 output$dataTable <- shiny::renderDataTable(reactive$data_table, escape=FALSE)
84 output$estTable <- shiny::renderDataTable(reactive$est_table, escape=FALSE)
85
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) }
90 )
91
92 shiny::observeEvent(input$addWP, {
93 if (input$serialWPKnown == 1) {
94 serial <- validate_serial(input, output, "serialWPInput", "serialWPWarn")
95 if (!is.na(serial)) {
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)
98 }
99 }
100 else {
101 checks_passed <- TRUE
102
103 grid_length <- as.numeric(input$gridLengthInput)
104 max_shape <- as.numeric(input$gridShapeInput)
105 max_scale <- as.numeric(input$gridScaleInput)
106
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
112 }
113 else {
114 output$gridLengthWarn <- shiny::renderText("")
115
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
119 }
120 else
121 output$gridShapeWarn <- shiny::renderText("")
122
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
126 }
127 else
128 output$gridScaleWarn <- shiny::renderText("")
129 }
130
131 if (checks_passed) {
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)
134 }
135 }
136 })
137
138 shiny::observeEvent(input$addseqB, {
139 serial <- validate_serial(input, output, "serialseqBInput", "serialseqBWarn")
140 checks_passed <- !is.na(serial)
141
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
146 }
147 else
148 output$kappaWarn <- shiny::renderText("")
149
150 if (checks_passed) {
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)
153 }
154 })
155
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)
161 }
162 })
163
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)
169 }
170 })
171 }
172
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.")
177 serial <- NA
178 }
179 else
180 output[[serialWarnId]] <- shiny::renderText("") # Clear warning text.
181
182 return(serial)
183 }
184
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))
188 else {
189 estimates <- rep(NA, nrow(datasets))
190
191 for (row in 1:nrow(datasets))
192 estimates[row] <- eval_estimator(input, output, estimator, datasets[row,])
193
194 if (nrow(est_table) == 0)
195 new_est_table <- cbind(datasets[,1], estimates)
196 else
197 new_est_table <- cbind(est_table, estimates)
198 }
199
200 colnames(new_est_table) <- c(colnames(est_table), shiny::HTML(paste0(estimator$method, "<br/>(&mu; = ", estimator$mu, " ", tolower(estimator$mu_units), ")")))
201 return(new_est_table)
202 }
203
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])
208 else
209 new_est_table <- data.frame(c(est_table[,1], datasets[,1]))
210
211 colnames(new_est_table) <- colnames(est_table)
212 }
213 else {
214 new_est_table <- data.frame(matrix(nrow=nrow(datasets), ncol=length(estimators)))
215
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,])
219
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)
223 }
224
225 return(new_est_table)
226 }
227
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")
232 serial <- serial / 7
233 else if (estimator$mu_units == "Weeks" && dataset[2] == "Daily")
234 serial <- serial * 7
235
236 # White and Panago
237 if (estimator$method == "WP") {
238 estimate <- WP(unlist(dataset[3]), mu=serial, search=estimator$search)
239
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.
243 else {
244 if (dataset[2] == "Daily")
245 mu_units <- "days"
246 else
247 mu_units <- "weeks"
248 MSI <- sum(estimate$SD$supp * estimate$SD$pmf)
249 estimate <- shiny::HTML(paste0(round(estimate$Rhat, 2), "<br/>(&mu; = ", round(MSI, 2), " ", mu_units, ")"))
250 }
251 }
252 # Sequential Bayes
253 else if (estimator$method == "seqB")
254 estimate <- round(seqB(unlist(dataset[3]), mu=serial, kappa=estimator$kappa)$Rhat, 2)
255 # Incidence Decay
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)
261
262 return(estimate)
263 }