-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.R
754 lines (642 loc) · 37 KB
/
app.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
rm(list=ls())
require(shiny)
require(shinyWidgets)
require(rhandsontable)
require(gt)
require(ggplot2)
require(DiagrammeR)
require(DiagrammeRsvg)
require(svglite)
require(tidyverse)
require(discoEPG)
require(bslib)
# Code for running on Clay's laptop
# appPath <- "/Users/c/Documents/EPG Project/EPGShinyApp/Deployed/"
# parameterMetadata <- read.csv(paste0(appPath, "ParameterMetadata.csv"))
# Code for running on Daniel's PC
#appPath <- "/Users/danie/OneDrive - Colostate/NALAM LAB/R package (EPG)/Deployed/Deployed_PC/"
#parameterMetadata <- read.csv(paste0(appPath, "ParameterMetadata.csv"))
# Code for running on Shiny server
appPath <- ""
parameterMetadata <- read.csv("ParameterMetadata.csv")
# Rest of the code is "platform-agnostic"
colnames(parameterMetadata) <- tolower(colnames(parameterMetadata))
parameterMetadata$acronym <- gsub('\\*\\d+$', '', parameterMetadata$acronym) # remove asterix, to help with merge to R parameter data
parameterMetadata <- rename(parameterMetadata, parameter = definition)
waveform_labels = c("np" = 1,
"C" = 2,
"E1e" = 3,
"E1" = 4,
"E2" = 5,
"F" = 6,
"G" = 7,
"pd-S" = 8,
"pd-S-II-2" = 9,
"pd-S-II-3" = 10,
"pd-L" = 11,
"pd-L-II-2" = 13,
"pd-L-II-3" = 14,
"end" = 12)
# ---- Shiny App Starts Here ----
shinyApp(
ui <- fluidPage(
tabsetPanel(id = "tabsets",
# ---- Tab 0: Home Page - Background & Instructions
tabPanel(title = 'Home Page', fluid = TRUE, value = 'homeTab',
#titlePanel("EPG Shiny App: Start Here"),
mainPanel(
#disco logo
br(),
br(),
imageOutput('discoLogo', height = '100px'),
br(),
hr(),
#title and version
h2(strong("discoEPG Shiny App v1.0")),
# contact info
p(style="text-align: justify; font-size = 50px",
"Developed by",strong("Clayton Bliss, Daniel Kunk, and Vamsi Nalam."),"For more information or to download the discoEPG R-package please visit our ", strong(a(href = "https://github.com/nalamvj/EPG_ANALYSIS", "github repository")),
"."
),
p(style = "text-align: justify; font-size = 50px",
strong("We value your feedback!"), "If you encounter any issues, find bugs, or have suggestions for improvement, please don’t hesitate to reach out to", strong("[email protected]"), "and let us know."
),
hr(),
# project description essay
h4(strong("Project Description")),
p(style="text-align: justify; font-size = 50px",
"Electrical penetration graph (EPG) is a technique used to study the feeding behavior of aphids on plants. In this technique, the aphid and plant are made part of an electrical circuit, which is completed when aphid mouthparts penetrate plant tissue. When the aphid stylet is inserted intercellularly, the voltage is positive and when inserted intracellularly, the voltage is negative. Waveforms in EPG have been correlated to specific aphid feeding behaviors by stylectomy followed by microscopy of the plant tissue to determine the approximate location of the stylet as well as observing aphid head movement, posture, and muscle dynamics. EPG is well established and has been widely used to study the mechanisms of plant virus transmission by aphids, the effect of resistant and susceptible lines on aphid feeding behaviors, and to better our understanding of the mechanisms that aphids use to continuously drink from the phloem."
),
p(style="text-align: justify; font-size = 50px",
"During EPG, fluctuating voltage signals in the circuit are graphed, and a researcher interprets resulting waveforms as specific stylet activity. EPG research can generate hundreds of recordings with thousands of events (individual occurrences of a waveform) per experiment. After measurement of waveforms, data consist of a list of different behaviors and associated durations. These data are further processed to yield hundreds of variables that are compiled, statistically analyzed, and converted to easily understood, visually compelling information before publication. Therefore, efficient measurement and analysis of these large data sets are critical."
),
p(style="text-align: justify; font-size = 50px",
"Software such as Stylet+, Windaq, and MacStylet allow users to view waveform recordings and provide ‘calls’ or annotations to waveforms. These programs also generate flat files (with the extension ‘.ANA’) composed of a number corresponding to the particular waveform in the first column and the time at which that particular waveform occurred in the second column. Currently, there are no programs that allows users to calculate aphid feeding behavior parameters and also perform statistical analysis from ANA files quickly and easily."
),
p(style="text-align: justify; font-size = 50px",
"The discoEPG package gives users the ability to easily calculate feeding behavior parameters directly from ‘.ANA’ files, perform statistical analysis on calculated parameters, and generate summary tables and graphs all in one package. Users can upload their ANA files, assign treatments, and calculate feeding behavior parameters in the 'Upload Data' tab. In the 'Validation Errors' tab, users can check their waveform calls and ensure the typical sequence of feeding behaviors are being followed in their annotations. The 'Summary Statistics' tab allows users to generate summary tables and perform statistical analysis between user selected treatments. The tables provide the mean and standard error of a particular, the group p-value, and individual comparison p-values for the selected parameter. Recordings that display over 70% of the recording duration in the F, G or NP waveforms are excluded from statistical analysis. The 'Boxplots' tab provides users boxplot figures of a selected parameter and treatment. The 'Timeseries Plot' tab provides users with timeseries plots, which display the feeding behavior that was displayed by the majority of aphids at a specific time during the recording. The 'Kinetogram' tab provides users with a behavioral kinetogram that shows the number of transitional events for each waveform."
),
hr(),
# getting started page
h3(strong("Getting Started: Uploading Data and Assigning Treatments")),
p(style="text-align: justify; font-size = 50px",
"The first steps in using this app are outlined below. You will start in the 'Upload Data' tab, by clicking the 'Browse' button:"
),
imageOutput('uploadDataScreenshot', height = '500px'),
br(),
p(style="text-align: justify; font-size = 50px",
"The above screenshot shows the file selection dialogue. After selecting the .ANA files, you will be presented with a table where you can assign treatments to each analysis:"
),
imageOutput('assignTreatmentsScreenshot', height = '400px'),
br(),
p(style="text-align: justify; font-size = 50px",
"Once you have assigned treatments to each .ANA file, click the 'Assign Treatments and Calculate Parameters' button. You can now view either the raw .ANA file data (displayed below), or the computed parameter values (not shown), by using the radio-button toggle in the menu bar:"
),
imageOutput('afterAssignTreatmentsScreenshot', height = '400px'),
br(),
p(style="text-align: justify; font-size = 50px",
"You're all done with data processing! Now you can move on to other tabs to see data validation errors, summary statistics, and plots."
# fin tab 0, begin functional tabs
)
)
),
# ---- Tab 1: Upload Data & Calculate Parameters
tabPanel(title = "Upload Data", fluid = TRUE, value = "dataTab",
titlePanel("EPG Shiny App: Upload Data and Calculate Parameters"),
sidebarLayout(
sidebarPanel(
fileInput(inputId = "files", label = "Choose ANA File(s)", multiple = T, placeholder = "Please Upload Files"),
conditionalPanel("output.filesUploaded", {
radioButtons(inputId = 'tableChoice', label = "Choose data to display",
choices = c('Files/Treatment Assignments' = 'files',
'Raw Data' = 'rawData',
'Computed Parameters' = 'paramData')
)
}),
conditionalPanel("output.filesUploaded", {
actionButton(inputId = 'assignTrtCalcParam', label = "Assign Treatments and Compute Parameters")
}),
# conditionalPanel("output.metadataSaved", {
# actionButton(inputId = 'calculateParameters', label = "Calculate Parameter Values")
# }),
conditionalPanel("output.trtAssignParamCalc", {
downloadButton(outputId = "downloadData", label = "Download Parameter Data")
})
),
mainPanel(
conditionalPanel("output.filesUploaded && input.tableChoice == 'files'", {
rHandsontableOutput("metadata_hot")
}),
conditionalPanel("output.trtAssignParamCalc && input.tableChoice == 'rawData'", {
gt_output("data_gt")
}),
conditionalPanel("output.trtAssignParamCalc && input.tableChoice == 'paramData'", {
gt_output("params_gt")
})
)
)
),
# ---- Tab 1b: Validation Errors
tabPanel(title = "Validation Errors", fluid = TRUE, value = "validationErrors",
titlePanel("EPG Shiny App: Raw Data Validation Errors"),
verbatimTextOutput("validationText")
),
# ---- Tab 2: Summary Statistics
tabPanel(title = "Summary Statistics", fluid = TRUE, value = "summaryStatsTab",
titlePanel("EPG Shiny App: Summary Statistics, by Treatment"),
sidebarLayout(
sidebarPanel(
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "treatmentPicker", label = "Select Treatment(s)",
choices = "Please enter treatments!",
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "parameterGroupPicker", label = "Select Parameter Grouping(s)",
choices = unique(parameterMetadata$experiment),
selected = unique(parameterMetadata$experiment),
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "parameterPicker", label = "Select Parameter(s)",
choices = unique(parameterMetadata$acronym),
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
radioButtons(inputId = "timeUnits", label = "Select Units of Time", choices = c('seconds', 'minutes', 'hours'), selected = 'seconds')
}),
conditionalPanel("output.trtAssignParamCalc", {
actionButton(inputId = 'calculateSummaryStats', label = "Calculate Summary Statistics")
}),
conditionalPanel("output.sumstatsCalculated", {
downloadButton(outputId = "downloadSummaryStats", label = "Download Summary Statistics")
})
),
mainPanel(
conditionalPanel("output.trtAssignParamCalc", {
gt_output("summaryStats_gt")
})
)
)
),
# ---- Tab 3: Boxplots
tabPanel(title = "Boxplots", fluid = TRUE, value = "boxplotsTab",
titlePanel("EPG Shiny App: Boxplots, by Treatment"),
sidebarLayout(
sidebarPanel(
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "treatmentPickerBoxplots", label = "Select Treatment(s)",
choices = "Please enter treatments!",
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
radioButtons(inputId = 'boxplotRadio', label = "Which boxplots to show?", choices = c('Statistically significant', 'Selected parameters'), selected = 'Statistically significant')
}),
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "boxplotParameterPicker", label = "Select Parameter(s)",
choices = unique(parameterMetadata$acronym),
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
actionButton(inputId = 'showBoxplots', label = "Show Boxplots")
})
),
mainPanel(
plotOutput("boxplots")
)
)
),
# ---- Tab 4: TimeSeries Plots
tabPanel(title = "Timeseries Plot", fluid = TRUE, value = "timeseriesTab",
titlePanel("EPG Shiny App: Timeseries plots, by Treatment"),
sidebarLayout(
sidebarPanel(
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "treatmentPickerTimeseries", label = "Select Treatment(s)",
choices = "Please enter treatments!",
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
actionButton(inputId = 'plotTimeseries', label = "Plot Timeseries")
}),
conditionalPanel("output.timeseriesCalculated", {
downloadButton(outputId = "downloadTimeseriesData", label = "Download Timeseries Data")
}),
conditionalPanel("output.timeseriesCalculated", {
downloadButton(outputId = "downloadTimeseriesPlot", label = "Download Timeseries Plot")
})
),
mainPanel(
conditionalPanel("output.trtAssignParamCalc", {
plotOutput("timeseriesPlot")
})
)
)
),
# ---- Tab 5: Kinetogram
tabPanel(title = "Kinetogram", fluid = TRUE, value = "kinetogramTab",
titlePanel("EPG Shiny App: Kinetograms, by Treatment"),
sidebarLayout(
sidebarPanel(
conditionalPanel("output.trtAssignParamCalc", {
shinyWidgets::pickerInput(inputId = "treatmentPickerKinetogram", label = "Select Exactly 2 Treatment(s)",
choices = "Please enter treatments!",
multiple = TRUE,
options = pickerOptions(actionsBox = TRUE))
}),
conditionalPanel("output.trtAssignParamCalc", {
actionButton(inputId = 'showKinetogram', label = "Show Kinetogram")
}),
conditionalPanel("output.kinetogramCalculated", {
downloadButton(outputId = "downloadKinetogramData", label = "Download Kinetogram Data")
}),
conditionalPanel("output.kinetogramCalculated", {
downloadButton(outputId = "downloadKinetogramPlot1", label = "Download Kinetogram Plot, 1st Treatment")
}),
conditionalPanel("output.kinetogramCalculated", {
downloadButton(outputId = "downloadKinetogramPlot2", label = "Download Kinetogram Plot, 2nd Treatment")
}),
),
mainPanel(
p(style="text-align: justify; font-size = 50px",
"Note: Statistically significant differences between treatments are shown by red arrows or node outlines."
),
conditionalPanel("output.trtAssignParamCalc", {
grVizOutput("kinetogramPlot1")
}),
conditionalPanel("output.trtAssignParamCalc", {
grVizOutput("kinetogramPlot2")
})
)
)
)
)
),
server <- function(input, output, session) {
### ----- Define Control Variables ----
output$filesUploaded <- reactive(if(length(input$files) > 0) TRUE else FALSE)
output$trtAssignParamCalc <- reactive(if(input$assignTrtCalcParam > 0) TRUE else FALSE)
output$sumstatsCalculated <- reactive(if(input$calculateSummaryStats > 0) TRUE else FALSE)
output$timeseriesCalculated <- reactive(if(input$plotTimeseries > 0) TRUE else FALSE)
output$kinetogramCalculated <- reactive(if(input$showKinetogram > 0) TRUE else FALSE)
outputOptions(output, "filesUploaded", suspendWhenHidden = FALSE)
outputOptions(output, "trtAssignParamCalc", suspendWhenHidden = FALSE)
outputOptions(output, "sumstatsCalculated", suspendWhenHidden = FALSE)
outputOptions(output, "timeseriesCalculated", suspendWhenHidden = FALSE)
outputOptions(output, "kinetogramCalculated", suspendWhenHidden = FALSE)
### ----- Tab 0: Home Page ----
# Render images
output$discoLogo <- renderImage(list(src = paste0(appPath, 'discoEPGLogo.png'), contentType = 'image/png', width = 500, height = 100), deleteFile = F)
output$uploadDataScreenshot <- renderImage(list(src = paste0(appPath, 'uploadDataScreenshot.png'), contentType = 'image/png', width = 1500, height = 500), deleteFile = F)
output$assignTreatmentsScreenshot <- renderImage(list(src = paste0(appPath, 'assignTreatmentsScreenshot.png'), contentType = 'image/png', width = 1000, height = 400), deleteFile = F)
output$afterAssignTreatmentsScreenshot <- renderImage(list(src = paste0(appPath, 'afterAssignTreatmentsScreenshot.png'), contentType = 'image/png', width = 1500, height = 400), deleteFile = F)
### ----- Tab 1: Data Processing ----
# Table for Assigning Treatments
metadata_handson <- reactive({
filenames <- sort(input$files$name)
df <- data.frame(treatment = rep('', length(filenames)), filename = filenames)
rhandsontable(df) %>%
hot_table(highlightCol = TRUE, highlightRow = TRUE)
})
output$metadata_hot <- renderRHandsontable(metadata_handson())
dataAndErrorsList <- reactive({
filepaths <- input$files$datapath
filenames <- input$files$name
# Read in all .ANA data into a list of dataframes and error messages
out <- combine_ana(filenames = filenames, directory = NULL, filepaths_shiny = filepaths, waveform_labels = waveform_labels)
nErrors <- length(out$errors)
# If any errors in ANA files, show a pop-up warning to the user
if(nErrors > 0) {
showNotification(ui=paste0("** WARNING ** Data validation checks failed in ", nErrors, " files! See the 'Data Validation' page for details."),
type = 'warning', closeButton = TRUE, duration = 10)
}
return(out)
})
# Assign treatments to all data and return a single data frame
alldata <- reactive({
dat <- dataAndErrorsList()$data
# Get updated treatment assignments and merge to data
meta <- hot_to_r(input$metadata_hot)
dat <- merge(dat, meta, by = 'filename')
return(dat)
}) %>% bindEvent(input$assignTrtCalcParam)
# Output data table
output$data_gt <- render_gt(alldata())
# Calculate Parameters and return a data frame
paramData <- reactive({
dat <- alldata()
if(nrow(dat) > 0) {
# Convert combined ANA data to parameters
epgData <- ana_to_parameters(ana_df = dat, waveform_labels = waveform_labels)
# Check if any files have > 70% of total time spent in F, G or np. If so, remove these from analysis and show notification in app.
idx <- which(epgData$acronym == "%timeinF+G+np" & epgData$value > 70)
if(length(idx) > 0) {
filesToRemove <- unique(epgData$filename[idx])
showNotification(ui=paste("These files have > 70% of time spent in F/G/np phases combined, and will be omitted from analyses: ", paste(filesToRemove, collapse = ', ')),
type = 'warning', closeButton = TRUE, duration = NULL)
epgData <- subset(epgData, !(filename %in% filesToRemove))
}
# Remove this variable, not used in analyses
epgData <- subset(epgData, acronym != "%timeinF+G+np")
# Merge to treatments
meta <- hot_to_r(input$metadata_hot)
epgData <- merge(epgData, meta, by = c('filename'))
return(epgData)
} else {
return(NULL)
}
}) %>% bindEvent(input$assignTrtCalcParam)
# Export Data
output$downloadData <- downloadHandler(
filename = function() {
paste0("ShinyAppParameterDataExport_", Sys.time(), ".csv")
},
content = function(file) {
write.csv(paramData(), file, row.names = FALSE)
}
)
# Format Parameter Table For Display
output$params_gt <- render_gt({
paramDat <- select(paramData(), -one_of('description', 'treatment'))
trtKey <- hot_to_r(input$metadata_hot)
trtKey <- subset(trtKey, filename %in% unique(paramDat$filename))
# Need to group columns by treatment. and then sort alphabetically(?)
trtKey <- trtKey[order(trtKey$treatment, trtKey$filename), ]
# Rearrange so each filename has its own column
paramList <- split(paramDat, paramDat$filename)
paramList <- lapply(paramList, function(df) {
colnames(df)[colnames(df) == 'value'] <- unique(df$filename)
select(df, -one_of('filename'))
})
paramDat <- paramList[[1]]
if(length(paramList) > 1) {
for(i in 2:length(paramList)) {
paramDat <- merge(paramDat, paramList[[i]], by='acronym')
}
}
# Sort columns and add Treatment prefix to file columns
paramDat <- relocate(paramDat, c('acronym', trtKey$filename))
for(tx in unique(trtKey$treatment)) {
idx <- colnames(paramDat) %in% trtKey$filename[trtKey$treatment == tx]
colnames(paramDat)[idx] <- paste(tx, colnames(paramDat)[idx], sep = '^^^')
}
paramTbl <- paramDat %>%
gt() %>%
cols_label(acronym = 'Parameter') %>%
tab_spanner_delim(columns = -one_of('acronym'), delim = '^^^')
return(paramTbl)
}) %>% bindEvent(input$assignTrtCalcParam)
### ----- Tab 1b: Validation Errors ----
output$validationText <- renderText({
errors <- dataAndErrorsList()$errors
nErrors <- length(errors)
# If any errors in ANA files, show a pop-up warning to the user
if(nErrors > 0) {
# msg <- unlist(errorList[errorInd])
msg <- paste(errors, collapse = '')
} else {
msg <- ""
}
return(msg)
})
### ----- Tab 2: Summary Statistics Tables ----
summaryStats <- reactive({
compute_summary_stats(parameter_df = paramData(),
parameter_metadata = parameterMetadata,
time_units = input$timeUnits,
treatments = input$treatmentPicker,
acronyms = input$parameterPicker,
alpha = 0.05,
p_adjust_method = 'bonferroni')
}) %>% bindEvent(input$calculateSummaryStats)
# Summary Stats Table (for Display)
output$summaryStats_gt <- render_gt({
sumStats <- summaryStats()
# Convert NA to empty cell
dunnCols <- grep('dunn_pval', colnames(sumStats))
for(dc in dunnCols) {
sumStats[is.na(sumStats[ , dc]), dc] <- ''
}
# Make pretty
sumStats <- sumStats %>%
select(-one_of('experiment')) %>%
rename(Parameter = acronym, Unit = unit)
if("dunn_pval^^overall" %in% colnames(sumStats)) {
sumStats <- arrange(sumStats, `dunn_pval^^overall`)
}
colnames(sumStats) <- gsub("mean.se", "Mean \u00B1 SE", colnames(sumStats))
colnames(sumStats) <- gsub("dunn_pval", "p-value", colnames(sumStats))
sumStats_gt <- sumStats %>%
gt() %>%
tab_header(title = "Summary Statistics, by Treatment") %>%
tab_spanner_delim(delim = '^^') %>%
tab_stubhead(label = "Treatments")
return(sumStats_gt)
})
# Update treatments available in pick-list based on parameter group pick-list, and data
observeEvent(input$tabsets, {
# Run only if Summary Stats Tab/Page is selected, and treatments have been assigned
if(input$tabsets == 'summaryStatsTab' & input$assignTrtCalcParam > 0) {
tx <- unique(hot_to_r(input$metadata_hot)$treatment)
updatePickerInput(session = session, inputId = "treatmentPicker", choices = tx, selected = tx)
}
}, ignoreInit = TRUE)
# Update parameters available in pick-list based on parameter group pick-list, and data
observeEvent(input$parameterGroupPicker, {
parameterGroups <- input$parameterGroupPicker
params <- summaryStats() %>%
filter(experiment %in% parameterGroups) %>%
select('acronym') %>%
distinct() %>%
as.vector()
updatePickerInput(session = session, inputId = "parameterPicker", choices = params, selected = params)
}, ignoreInit = TRUE)
# Export Summary Statistics (This works on a remotely served version)
output$downloadSummaryStats <- downloadHandler(
filename = function() {
paste0("ShinyAppSummaryStatsExport_", Sys.time(), ".csv")
},
content = function(file) {
write.csv(summaryStats(), file, row.names = FALSE)
}
)
### ----- Tab 3: Box Plots ----
# Update treatments available in pick-list
observeEvent(input$tabsets, {
# Run only if Timeseries Tab/Page is selected, and treatments have been assigned
if(input$tabsets == 'boxplotsTab' & input$assignTrtCalcParam > 0) {
tx <- unique(hot_to_r(input$metadata_hot)$treatment)
updatePickerInput(session = session, inputId = "treatmentPickerBoxplots", choices = tx, selected = tx)
}
}, ignoreInit = TRUE)
# Boxplots
output$boxplots <- renderPlot({
dat <- paramData()
sumStats <- summaryStats()
if(input$boxplotRadio == 'Statistically significant') {
pvalCols <- grep('dunn_pval', colnames(sumStats), value = TRUE)
if(length(pvalCols) > 0) {
if(length(pvalCols) > 1) {
sumStats <- subset(sumStats, `dunn_pval^^overall` < 0.05)
dat <- subset(dat, acronym %in% sumStats$acronym)
} else { # only 1 pvalue column
sumStats <- subset(sumStats, pvalCols < 0.05)
dat <- subset(dat, acronym %in% sumStats$acronym)
}
if(nrow(dat) == 0) showNotification("No statistically significant parameter differences.", type = 'warning', closeButton = TRUE, duration = 5)
} else {
showNotification("Data only exists for 1 treatment. Cannot compute statistical differences.",
type = 'warning', closeButton = TRUE, duration = 5)
}
} else {
dat <- subset(dat, acronym %in% input$boxplotParameterPicker)
}
if(nrow(dat) > 0) {
subDat <- subset(dat, treatment %in% input$treatmentPickerBoxplots)
out <- ggplot(subDat, aes(x=treatment, y=value, color=treatment)) +
geom_boxplot() +
theme_classic(base_size = 16) +
geom_jitter(width = 0.02, height = 0) +
facet_wrap(~acronym, scales = 'free')
} else {
out <- NULL
}
return(out)
}) %>% bindEvent(input$showBoxplots)
### ----- Tab 4: Timeseries Plot ----
# Update treatments available in pick-list
observeEvent(input$tabsets, {
# Run only if Timeseries Tab/Page is selected, and treatments have been assigned
if(input$tabsets == 'timeseriesTab' & input$assignTrtCalcParam > 0) {
tx <- unique(hot_to_r(input$metadata_hot)$treatment)
updatePickerInput(session = session, inputId = "treatmentPickerTimeseries", choices = tx, selected = tx)
}
}, ignoreInit = TRUE)
timeseriesDat <- reactive({
anaDat <- alldata()
selected_treatments <- input$treatmentPickerTimeseries
tsList <- lapply(selected_treatments, function(tx) {
trt_dat <- subset(anaDat, treatment == tx)
tsdat <- ana_to_timeseries(ana_df = trt_dat, waveform_labels = waveform_labels)$data
tsdat$treatment <- tx
return(tsdat)
})
tsDat <- do.call(rbind, tsList)
return(tsDat)
}) %>% bindEvent(input$plotTimeseries)
# Timeseries Plot (for Display)
tsPlot <- function() {
modeDat <- timeseriesDat()
# re-order factor, in same order as "waveform_labels"
modeDat$activity <- factor(toupper(modeDat$activity),
levels = toupper(intersect(names(waveform_labels), unique(modeDat$activity))))
# remove 'end' for plotting
modeDat <- subset(modeDat, modeDat$activity != 'END')
ggplot(modeDat, aes(x=activity, y=time, color=activity)) +
geom_point(shape=20, size=1) +
theme_classic(base_size = 16) +
coord_flip() +
facet_wrap(~treatment, ncol=1)
}
output$timeseriesPlot <- renderPlot({
tsPlot()
})
# Export Timeseries Data
output$downloadTimeseriesData <- downloadHandler(
filename = function() {
paste0("ShinyAppTimeseriesDataExport_", Sys.time(), ".csv")
},
content = function(file) {
write.csv(timeseriesDat(), file, row.names = FALSE)
}
)
# Export Timeseries Plot (SVG)
output$downloadTimeseriesPlot <- downloadHandler(
filename = function() {
paste0("ShinyAppTimeseriesPlotExport_", Sys.time(), ".svg")
},
content = function(file) {
svglite(file, width = 8, height = 5)
print(tsPlot())
dev.off()
}
)
### ----- Tab 5: Kinetograms ----
# Update treatments available in pick-list
observeEvent(input$tabsets, {
# Run only if Kinetogram Tab/Page is selected, and treatments have been assigned
if(input$tabsets == 'kinetogramTab' & input$assignTrtCalcParam > 0) {
tx <- unique(hot_to_r(input$metadata_hot)$treatment)
updatePickerInput(session = session, inputId = "treatmentPickerKinetogram", choices = tx, selected = tx)
}
}, ignoreInit = TRUE)
# A list of 2 graph viz objects (one kinetogram "viz" per treatment)
# kinetogramList <- reactive({
# deList <- dataAndErrorsList()
# anaDat <- deList$data
# trtKey <- hot_to_r(input$metadata_hot)
# anaDat <- merge(anaDat, trtKey)
# anaDat <- subset(anaDat, treatment %in% input$treatmentPickerKinetogram)
# kineDat <- ana_to_kinetogram_df(ana_df = anaDat)
# kinePlots <- compare_kinetograms(kineDat = kineDat)
# return(list(data = kineDat, plots = kinePlots))
# }) %>% bindEvent(input$showKinetogram)
kinetogramList <- function() {
deList <- dataAndErrorsList()
anaDat <- deList$data
trtKey <- hot_to_r(input$metadata_hot)
anaDat <- merge(anaDat, trtKey)
anaDat <- subset(anaDat, treatment %in% input$treatmentPickerKinetogram)
kineDat <- ana_to_kinetogram_df(ana_df = anaDat)
kinePlots <- compare_kinetograms(kineDat = kineDat)
return(list(data = kineDat, plots = kinePlots))
}
# kinetogramList <- reactive({
# kineList()
# }) %>% bindEvent(input$showKinetogram)
output$kinetogramPlot1 <- renderGrViz({
kinetogramList()$plots[[1]]
}) %>% bindEvent(input$showKinetogram)
output$kinetogramPlot2 <- renderGrViz({
kinetogramList()$plots[[2]]
}) %>% bindEvent(input$showKinetogram)
# Export Kinetogram Data
output$downloadKinetogramData <- downloadHandler(
filename = function() {
paste0("ShinyAppKinetogramDataExport_", Sys.time(), ".csv")
},
content = function(file) {
write.csv(kinetogramList()$data$summaryStats, file, row.names = FALSE)
}
)
# Export Kinetogram Plots (SVG)
output$downloadKinetogramPlot1 <- downloadHandler(
filename = function() {
paste0("ShinyAppKinetogramPlotExport_Treatment_", names(kinetogramList()$plots)[1], "_", Sys.time(), ".svg")
},
content = function(file) {
svg <- DiagrammeRsvg::export_svg(kinetogramList()$plots[[1]])
fileConn <- file(file)
writeLines(svg, fileConn)
close(fileConn)
}
)
output$downloadKinetogramPlot2 <- downloadHandler(
filename = function() {
paste0("ShinyAppKinetogramPlotExport_Treatment_", names(kinetogramList()$plots)[2], "_", Sys.time(), ".svg")
},
content = function(file) {
svg <- DiagrammeRsvg::export_svg(kinetogramList()$plots[[2]])
fileConn <- file(file)
writeLines(svg, fileConn)
close(fileConn)
}
)
}
)