@@ -55,7 +55,10 @@ boxplotInput <- function(id, eselist) {
5555 naked_fields [[1 ]] <- distribution_plot_filters
5656 }
5757
58- field_sets <- c(field_sets , list (expression = expression_filters ))
58+ field_sets <- c(field_sets , list (
59+ expression = expression_filters ,
60+ export = simpletableInput(ns(" summary" ), " Distribution summary" )
61+ ))
5962
6063 list (naked_fields , fieldSets(ns(" fieldset" ), field_sets ))
6164}
@@ -93,6 +96,8 @@ boxplotOutput <- function(id) {
9396 moduleMain(
9497 " Value distributions" ,
9598 uiOutput(ns(" quartilesPlot" )),
99+ h4(" Distribution summary" ),
100+ simpletableOutput(ns(" summary" )),
96101 help = modalInput(ns(boxplot_modal $ id ), " help" , " help" )
97102 )
98103}
@@ -171,18 +176,53 @@ boxplot <- function(id, eselist) {
171176 # leading trace, so a restyled trace index maps directly onto a group.
172177 hiddenGroups <- legendHiddenGroups(plot_source , getLevels , groupby_reactives $ getGroupby , trace_offset = 0L )
173178
179+ getBoxplotStatistics <- reactive({
180+ validate(need(! is.null(input $ whiskerDistance ), " Waiting for whisker distance" ))
181+ selected_matrix <- selectmatrix_reactives $ selectMatrix()
182+ ese <- selectmatrix_reactives $ getExperiment()
183+ boxplot_matrix_statistics(
184+ selected_matrix ,
185+ labels = stats :: setNames(id_to_label(rownames(selected_matrix ), ese ), rownames(selected_matrix )),
186+ whisker_distance = input $ whiskerDistance ,
187+ rmzeros = TRUE
188+ )
189+ })
190+
191+ getLineStatistics <- reactive({
192+ validate(need(! is.null(input $ whiskerDistance ), " Waiting for whisker distance" ))
193+ boxplot_matrix_statistics(
194+ selectmatrix_reactives $ selectMatrix(),
195+ whisker_distance = input $ whiskerDistance ,
196+ rmzeros = FALSE
197+ )
198+ })
199+
174200 output $ sampleBoxplot <- renderPlotly({
175201 withProgress(message = " Making sample boxplot" , value = 0 , {
176- selected_matrix <- selectmatrix_reactives $ selectMatrix()
177- ese <- selectmatrix_reactives $ getExperiment()
178- interactive_boxplot(selected_matrix , selectmatrix_reactives $ selectColData(), groupby_reactives $ getGroupby(),
179- expressiontype = selectmatrix_reactives $ getAssayMeasure(), whisker_distance = input $ whiskerDistance ,
180- palette = groupby_reactives $ getPalette(), hidden_groups = hiddenGroups(), source = plot_source ,
181- labels = stats :: setNames(id_to_label(rownames(selected_matrix ), ese ), rownames(selected_matrix ))
202+ interactive_boxplot_from_statistics(list (" " = getBoxplotStatistics()), selectmatrix_reactives $ selectColData(), groupby_reactives $ getGroupby(),
203+ expressiontype = selectmatrix_reactives $ getAssayMeasure(),
204+ palette = groupby_reactives $ getPalette(), hidden_groups = hiddenGroups(), source = plot_source
182205 ) %> %
183206 shinyngsPlotlyConfig(" boxplot" , format = session $ userData $ plotFormat())
184207 })
185208 })
209+
210+ getDistributionSummary <- reactive({
211+ statistics <- if (identical(input $ plotType , " lines" )) getLineStatistics() else getBoxplotStatistics()
212+ distribution_summary_from_statistics(
213+ statistics ,
214+ selectmatrix_reactives $ selectColData(),
215+ groupby_reactives $ getGroupby()
216+ )
217+ })
218+
219+ simpletable(
220+ " summary" ,
221+ downloadMatrix = getDistributionSummary ,
222+ displayMatrix = getDistributionSummary ,
223+ filename = " distribution_summary" , rownames = FALSE ,
224+ server = FALSE , initial_order = list ()
225+ )
186226 })
187227}
188228
@@ -309,6 +349,96 @@ box_summary <- function(values, labels, whisker_distance = 1.5) {
309349 )
310350}
311351
352+ boxplot_matrix_statistics <- function (matrix , labels = NULL , whisker_distance = 1.5 ,
353+ should_transform = NULL , rmzeros = TRUE ) {
354+ matrix <- as.matrix(matrix )
355+ transformed <- cond_log2_transform_matrix(
356+ matrix ,
357+ should_transform = should_transform ,
358+ rmzeros = rmzeros
359+ )
360+ row_labels <- if (is.null(labels )) {
361+ rownames(transformed )
362+ } else {
363+ resolved <- unname(labels [rownames(transformed )])
364+ resolved [is.na(resolved )] <- rownames(transformed )[is.na(resolved )]
365+ resolved
366+ }
367+
368+ statistics <- lapply(colnames(transformed ), function (sample ) {
369+ values <- transformed [, sample ]
370+ finite <- values [is.finite(values )]
371+ summary <- box_summary(values , row_labels , whisker_distance )
372+ c(summary , list (
373+ non_missing = length(finite ),
374+ minimum = if (length(finite )) min(finite ) else NA_real_ ,
375+ mean = if (length(finite )) mean(finite ) else NA_real_ ,
376+ maximum = if (length(finite )) max(finite ) else NA_real_
377+ ))
378+ })
379+ names(statistics ) <- colnames(transformed )
380+ statistics
381+ }
382+
383+ distribution_sample_layout <- function (samples , experiment = NULL , groupby = NULL ) {
384+ groups <- NULL
385+ if (! is.null(groupby ) && ! is.null(experiment ) && groupby %in% colnames(experiment )) {
386+ groups <- na_replace(
387+ as.character(experiment [[groupby ]][match(samples , rownames(experiment ))]),
388+ " N/A"
389+ )
390+ sample_order <- order(factor (groups , levels = groupLevels(experiment , groupby )))
391+ samples <- samples [sample_order ]
392+ groups <- groups [sample_order ]
393+ }
394+ list (samples = samples , groups = groups )
395+ }
396+
397+ distribution_summary_from_statistics <- function (statistics , experiment = NULL , groupby = NULL ) {
398+ layout <- distribution_sample_layout(names(statistics ), experiment , groupby )
399+
400+ summaries <- lapply(layout $ samples , function (sample ) {
401+ stats <- statistics [[sample ]]
402+ data.frame (
403+ Sample = sample ,
404+ `Non-missing` = stats $ non_missing ,
405+ Minimum = stats $ minimum ,
406+ Q1 = stats $ q1 ,
407+ Median = stats $ median ,
408+ Mean = stats $ mean ,
409+ Q3 = stats $ q3 ,
410+ Maximum = stats $ maximum ,
411+ IQR = stats $ q3 - stats $ q1 ,
412+ Outliers = length(stats $ outlier_values ),
413+ check.names = FALSE , stringsAsFactors = FALSE
414+ )
415+ })
416+ summary <- do.call(rbind , summaries )
417+ rownames(summary ) <- NULL
418+
419+ if (! is.null(layout $ groups )) {
420+ summary <- cbind(
421+ summary [" Sample" ],
422+ stats :: setNames(data.frame (layout $ groups , stringsAsFactors = FALSE ), prettify_variable_name(groupby )),
423+ summary [setdiff(colnames(summary ), " Sample" )]
424+ )
425+ }
426+
427+ numeric_columns <- vapply(summary , is.numeric , logical (1 ))
428+ summary [numeric_columns ] <- lapply(summary [numeric_columns ], function (values ) signif(values , 5 ))
429+ summary
430+ }
431+
432+ distribution_summary <- function (matrix , experiment = NULL , groupby = NULL ,
433+ whisker_distance = 1.5 , rmzeros = TRUE ) {
434+ statistics <- boxplot_matrix_statistics(
435+ matrix ,
436+ whisker_distance = whisker_distance ,
437+ rmzeros = rmzeros
438+ )
439+ distribution_summary_from_statistics(statistics , experiment , groupby )
440+ }
441+
312442# ' Make an interactive boxplot with coloring by experimental variable
313443# '
314444# ' Draws a \code{plotly} box plot of the value distribution in each sample.
@@ -360,18 +490,40 @@ interactive_boxplot <- function(plotmatrices, experiment, colorby = NULL, palett
360490 plotmatrices <- list (" " = plotmatrices )
361491 }
362492
363- # Order samples so members of the same group sit together, preserving
364- # first-seen order of both samples and groups (mirrors ggplotify()).
493+ statistics <- lapply(plotmatrices , function (matrix ) {
494+ boxplot_matrix_statistics(
495+ matrix ,
496+ labels = labels ,
497+ whisker_distance = whisker_distance ,
498+ should_transform = should_transform ,
499+ rmzeros = TRUE
500+ )
501+ })
502+
503+ interactive_boxplot_from_statistics(
504+ statistics , experiment , colorby ,
505+ palette = palette , expressiontype = expressiontype ,
506+ palette_name = palette_name , annotate_samples = annotate_samples ,
507+ max_outliers = max_outliers , hidden_groups = hidden_groups , source = source
508+ )
509+ }
510+
511+ interactive_boxplot_from_statistics <- function (statistics , experiment , colorby = NULL ,
512+ palette = NULL , expressiontype = " expression" ,
513+ palette_name = COLORBLIND_PALETTE_NAME ,
514+ annotate_samples = FALSE , max_outliers = 500 ,
515+ hidden_groups = character (0 ), source = NULL ) {
365516
366517 if (! is.null(colorby )) {
367- groups <- na_replace(as.character(experiment [[colorby ]]), " N/A" )
518+ sample_layout <- distribution_sample_layout(names(statistics [[1 ]]), experiment , colorby )
519+ samples <- sample_layout $ samples
520+ groups <- sample_layout $ groups
521+ group_levels <- groupLevels(experiment , colorby )
368522 } else {
369- groups <- rep(" " , nrow(experiment ))
523+ samples <- names(statistics [[1 ]])
524+ groups <- rep(" " , length(samples ))
525+ group_levels <- " "
370526 }
371- group_levels <- unique(groups )
372- sample_order <- order(factor (groups , levels = group_levels ))
373- samples <- rownames(experiment )[sample_order ]
374- groups <- groups [sample_order ]
375527
376528 palette <- resolvePalette(palette , group_levels , palette_name )
377529
@@ -386,25 +538,13 @@ interactive_boxplot <- function(plotmatrices, experiment, colorby = NULL, palett
386538
387539 # Legend-click events are only routed for a single (non-subplotted) panel,
388540 # which is what the Shiny module produces.
389- event_source <- if (length(plotmatrices ) == 1 ) source else NULL
541+ event_source <- if (length(statistics ) == 1 ) source else NULL
390542
391- facet_names <- prettify_variable_name(names(plotmatrices ))
543+ facet_names <- prettify_variable_name(names(statistics ))
392544 yaxis_title <- expressionAxisLabel(expressiontype )
393545
394- facet_plots <- lapply(seq_along(plotmatrices ), function (i ) {
395- m <- cond_log2_transform_matrix(as.matrix(plotmatrices [[i ]]), should_transform = should_transform , rmzeros = TRUE )
396- m <- m [, samples , drop = FALSE ]
397-
398- row_labels <- if (is.null(labels )) {
399- rownames(m )
400- } else {
401- resolved <- unname(labels [rownames(m )])
402- resolved [is.na(resolved )] <- rownames(m )[is.na(resolved )]
403- resolved
404- }
405-
406- stats <- lapply(samples , function (s ) box_summary(m [, s ], row_labels , whisker_distance ))
407- names(stats ) <- samples
546+ facet_plots <- lapply(seq_along(statistics ), function (i ) {
547+ stats <- statistics [[i ]][samples ]
408548
409549 p <- if (is.null(event_source )) plot_ly() else plot_ly(source = event_source )
410550
@@ -454,9 +594,9 @@ interactive_boxplot <- function(plotmatrices, experiment, colorby = NULL, palett
454594 )
455595 }
456596
457- xaxis <- list (title = if (length(plotmatrices ) > 1 ) facet_names [i ] else NULL )
458- if (length(plotmatrices ) > 1 ) {
459- xaxis $ showticklabels <- i == length(plotmatrices )
597+ xaxis <- list (title = if (length(statistics ) > 1 ) facet_names [i ] else NULL )
598+ if (length(statistics ) > 1 ) {
599+ xaxis $ showticklabels <- i == length(statistics )
460600 }
461601 if (length(visible_samples ) > 0 ) {
462602 xaxis $ categoryorder <- " array"
0 commit comments