]> nmode's Git Repositories - Rnaught/blob - inst/app/scripts/data.R
02b57c569700b4f797a72f9c9151baccd65a38bb
[Rnaught] / inst / app / scripts / data.R
1 # Main logic block for data-related interactions.
2 data_logic <- function(input, output, react_values) {
3 # Initialize a data frame to hold the datasets.
4 react_values$data_table <- data.frame(
5 Name = character(0),
6 `Time units` = character(0),
7 `Case counts` = character(0),
8 check.names = FALSE
9 )
10
11 render_plot(input, output)
12 single_entry(input, output, react_values)
13 manual_bulk_entry(input, output, react_values)
14 upload_data(input, output, react_values)
15 load_samples(input, output, react_values)
16 render_data(output, react_values)
17 delete_data(input, react_values)
18 export_data(output, react_values)
19 }
20
21 # Convert the input case counts string to an integer vector.
22 tokenize_counts <- function(counts_str) {
23 suppressWarnings(as.integer(unlist(strsplit(trimws(counts_str), ","))))
24 }
25
26 # Render the preview plot for single entry data.
27 render_plot <- function(input, output) {
28 observe({
29 counts <- tokenize_counts(input$data_counts)
30 if (length(counts) > 0 && !anyNA(counts) && all(counts >= 0)) {
31 output$data_plot <- renderPlot(
32 plot(seq_along(counts) - 1, counts, type = "o", pch = 16, col = "black",
33 xlab = input$data_units, ylab = "Cases", cex.lab = 1.5,
34 xlim = c(0, max(length(counts) - 1, 1)), ylim = c(0, max(counts, 1))
35 )
36 )
37 } else {
38 output$data_plot <- renderPlot(
39 plot(NULL, xlim = c(0, 10), ylim = c(0, 10),
40 xlab = input$data_units, ylab = "Cases", cex.lab = 1.5
41 )
42 )
43 }
44 })
45 }
46
47 # Add a single dataset to the existing table.
48 single_entry <- function(input, output, react_values) {
49 observeEvent(input$data_single, {
50 valid <- TRUE
51
52 # Ensure the dataset name is neither blank nor a duplicate.
53 name <- trimws(input$data_name)
54 if (name == "") {
55 output$data_name_warn <- renderText("The dataset name cannot be blank.")
56 valid <- FALSE
57 } else if (name %in% react_values$data_table[, 1]) {
58 output$data_name_warn <- renderText(
59 "There is already a dataset with the specified name."
60 )
61 valid <- FALSE
62 } else {
63 output$data_name_warn <- renderText("")
64 }
65
66 # Ensure the case counts are specified as a comma-separated of one or more
67 # non-negative integers.
68 counts <- tokenize_counts(input$data_counts)
69 if (length(counts) == 0) {
70 output$data_counts_warn <- renderText("Case counts cannot be blank.")
71 valid <- FALSE
72 } else if (anyNA(counts) || any(counts < 0)) {
73 output$data_counts_warn <- renderText(
74 "Case counts can only contain non-negative integers."
75 )
76 valid <- FALSE
77 } else {
78 output$data_counts_warn <- renderText("")
79 }
80
81 if (valid) {
82 # Add the new dataset to the data table.
83 new_row <- data.frame(name, input$data_units, toString(counts))
84 colnames(new_row) <- c("Name", "Time units", "Case counts")
85 react_values$data_table <- rbind(react_values$data_table, new_row)
86
87 # Evaluate all existing estimators on the new dataset and update the
88 # corresponding row in the estimates table.
89 update_estimates_rows(new_row, react_values)
90
91 showNotification("Dataset added successfully.",
92 duration = 3, id = "notify-success"
93 )
94 }
95 })
96 }
97
98 manual_bulk_entry <- function(input, output, react_values) {
99 observeEvent(input$data_bulk, {
100 validate_bulk_data(input, output, react_values, "data_area")
101 })
102 }
103
104 upload_data <- function(input, output, react_values) {
105 observeEvent(input$data_upload, {
106 validate_bulk_data(input, output, react_values, "data_upload")
107 })
108 }
109
110 validate_bulk_data <- function(input, output, react_values, data_source) {
111 tryCatch(
112 {
113 if (data_source == "data_area") {
114 datasets <- read.csv(text = input$data_area, header = FALSE, sep = ",")
115 } else if (data_source == "data_upload") {
116 datasets <- read.csv(
117 file = input$data_upload$datapath, header = FALSE, sep = ","
118 )
119 }
120
121 names <- trimws(datasets[, 1])
122 units <- trimws(datasets[, 2])
123 counts <- apply(data.frame(datasets[, 3:ncol(datasets)]), 1,
124 function(row) {
125 row <- suppressWarnings(as.integer(row))
126 toString(row[!is.na(row) & row >= 0])
127 }
128 )
129
130 warning_text <- ""
131
132 # Ensure the dataset names are neither blank nor duplicates.
133 if (anyNA(names) || any(names == "")) {
134 warning_text <- paste0(warning_text,
135 "Each row must begin with a non-blank dataset name.<br>"
136 )
137 } else {
138 if (length(unique(names)) != length(names)) {
139 warning_text <- paste0(warning_text,
140 "The rows contain duplicate dataset names.<br>"
141 )
142 }
143 if (any(names %in% react_values$data_table[, 1])) {
144 warning_text <- paste0(warning_text,
145 "The rows contain dataset names which already exist.<br>"
146 )
147 }
148 }
149
150 # Ensure the second entry in each row is a time unit equal to
151 # "Days" or "Weeks".
152 if (!all(units %in% c("Days", "Weeks"))) {
153 warning_text <- paste0(warning_text,
154 "The second entry in each row must be either 'Days' or 'Weeks'.<br>"
155 )
156 }
157
158 # Ensure the counts in each row have at least one non-negative integer.
159 if (any(counts == "")) {
160 warning_text <- paste0(warning_text,
161 "Each row must contain at least one non-negative integer.<br>"
162 )
163 }
164
165 output[[paste0(data_source, "_warn")]] <- renderUI(HTML(warning_text))
166
167 if (warning_text == "") {
168 # Add the new datasets to the data table.
169 new_rows <- data.frame(names, units, counts)
170 colnames(new_rows) <- c("Name", "Time units", "Case counts")
171 react_values$data_table <- rbind(react_values$data_table, new_rows)
172
173 # Evaluate all existing estimators on the new datasets and update the
174 # corresponding rows in the estimates table.
175 update_estimates_rows(new_rows, react_values)
176
177 showNotification("Datasets added successfully.",
178 duration = 3, id = "notify-success"
179 )
180 }
181 },
182 error = function(e) {
183 output[[paste0(data_source, "_warn")]] <- renderText(
184 "The input does not match the required format."
185 )
186 }
187 )
188 }
189
190 # Load sample datasets.
191 load_samples <- function(input, output, react_values) {
192 observeEvent(input$data_samples, {
193 names <- c()
194 units <- c()
195 counts <- c()
196
197 # COVID-19 Canada, March 2020 (weekly).
198 if (input$covid_canada) {
199 names <- c(names, "COVID-19 Canada 2020/03/03 - 2020/03/31")
200 units <- c(units, "Weeks")
201 counts <- c(counts, toString(Rnaught::COVIDCanada[seq(41, 69, 7), 2]))
202 }
203 # COVID-19 Ontario, March 2020 (weekly).
204 if (input$covid_ontario) {
205 names <- c(names, "COVID-19 Ontario 2020/03/03 - 2020/03/31")
206 units <- c(units, "Weeks")
207 counts <- c(counts,
208 toString(Rnaught::COVIDCanadaPT[seq(10176, 10204, 7), 3])
209 )
210 }
211
212 if (length(names) == 0) {
213 output$data_samples_warn <- renderText(
214 "At least one sample dataset must be selected."
215 )
216 } else if (any(names %in% react_values$data_table[, 1])) {
217 output$data_samples_warn <- renderText(
218 "At least one of the selected dataset names already exist."
219 )
220 } else {
221 output$data_samples_warn <- renderText("")
222
223 new_rows <- data.frame(names, units, counts)
224 colnames(new_rows) <- c("Name", "Time units", "Case counts")
225 react_values$data_table <- rbind(react_values$data_table, new_rows)
226
227 # Evaluate all existing estimators on the sample datasets and update the
228 # corresponding rows in the estimates table.
229 update_estimates_rows(new_rows, react_values)
230
231 showNotification("Datasets added successfully.",
232 duration = 3, id = "notify-success"
233 )
234 }
235 })
236 }
237
238 # Render the data table when new datasets are added.
239 render_data <- function(output, react_values) {
240 observe({
241 output$data_table <- DT::renderDataTable(react_values$data_table)
242 })
243 }
244
245 # Delete rows in the data table,
246 # and the corresponding rows in the estimates table.
247 delete_data <- function(input, react_values) {
248 observeEvent(input$data_delete, {
249 new_table <- react_values$data_table[-input$data_table_rows_selected, ]
250 if (nrow(new_table) > 0) {
251 rownames(new_table) <- seq_len(nrow(new_table))
252 }
253 react_values$data_table <- new_table
254
255 if (ncol(react_values$estimates_table) == 1) {
256 react_values$estimates_table <- data.frame(
257 Datasets = react_values$data_table[, 1]
258 )
259 } else {
260 react_values$estimates_table <-
261 react_values$estimates_table[-input$data_table_rows_selected, ]
262 }
263 })
264 }
265
266 # Export data table as a CSV file.
267 export_data <- function(output, react_values) {
268 output$data_export <- downloadHandler(
269 filename = function() {
270 paste0("Rnaught_data_", format(Sys.time(), "%y-%m-%d_%H-%M-%S"), ".csv")
271 },
272 content = function(file) {
273 write.csv(react_values$data_table, file, row.names = FALSE)
274 }
275 )
276 }
277
278 # When new datasets are added, evaluate all existing estimators on them and
279 # add new rows to the estimates table.
280 update_estimates_rows <- function(datasets, react_values) {
281 new_rows <- data.frame(
282 matrix(nrow = nrow(datasets), ncol = ncol(react_values$estimates_table))
283 )
284 colnames(new_rows) <- colnames(react_values$estimates_table)
285
286 for (row in seq_len(nrow(datasets))) {
287 new_rows[row, 1] <- datasets[row, 1]
288
289 if (length(react_values$estimators) > 0) {
290 for (col in 2:ncol(react_values$estimates_table)) {
291 new_rows[row, col] <- eval_estimator(
292 react_values$estimators[[col - 1]], datasets[row, ]
293 )
294 }
295 }
296 }
297
298 react_values$estimates_table <- rbind(
299 react_values$estimates_table, new_rows
300 )
301 }