#' SCONE Report Browser: Browse Evaluation of Normalization Performance #' #' This function opens a shiny application session for visualizing performance #' of a variety of normalization schemes. #' #' @param x a \code{SconeExperiment} object #' @param methods character specifying the normalizations to report. #' @param qc matrix. QC metrics to be used for QC evaluation report. Required. #' @param bio factor. A biological condition (variation to be preserved). #' Default NULL. #' @param batch factor. A known batch variable (variation to be removed). #' Default NULL. #' @param negcon character. Genes to be used as negative controls for #' evaluation. These genes should be expected not to change according to the #' biological phenomenon of interest. Default empty character. #' @param poscon character. Genes to be used as positive controls for #' evaluation. These genes should be expected to change according to the #' biological phenomenon of interest. Default empty character. #' @param eval_proj function. Projection function for evaluation (see #' \code{\link{score_matrix}} for details). If NULL, PCA is used for #' projection. #' @param eval_proj_args list. List of args passed to projection function as #' eval_proj_args. #' #' @importFrom RColorBrewer brewer.pal #' @importFrom rARPACK svds #' @importFrom graphics boxplot #' @importFrom utils write.csv #' @importFrom stats setNames var #' @export #' #' @return An object that represents the SCONE report app. #' #' @examples #' set.seed(101) #' mat <- matrix(rpois(1000, lambda = 5), ncol=10) #' colnames(mat) <- paste("X", 1:ncol(mat), sep="") #' obj <- SconeExperiment(mat) #' res <- scone(obj, scaling=list(none=identity, uq=UQ_FN, deseq=DESEQ_FN), #' evaluate=TRUE, k_ruv=0, k_qc=0, eval_kclust=2, #' bpparam = BiocParallel::SerialParam()) #' qc = as.matrix(cbind(colSums(mat),colSums(mat > 0))) #' rownames(qc) = colnames(mat) #' colnames(qc) = c("NCOUNTS","NGENES") #' \dontrun{ #' sconeReport(res,rownames(get_params(res)), qc = qc) #' } #' sconeReport = function(x, methods, qc, bio = NULL, batch = NULL, poscon = character(), negcon = character(), eval_proj = NULL, eval_proj_args = NULL){ if (!requireNamespace("shiny", quietly = TRUE)) { stop("shiny package needed for sconeReport()") } if (!requireNamespace("DT", quietly = TRUE)) { stop("DT package needed for sconeReport()") } if (!requireNamespace("NMF", quietly = TRUE)) { stop("NMF package needed for sconeReport()") } if (!requireNamespace("reshape2", quietly = TRUE)) { stop("reshape2 package needed for sconeReport()") } if (!requireNamespace("plotly", quietly = TRUE)) { stop("plotly package needed for sconeReport()") } if (!requireNamespace("visNetwork", quietly = TRUE)) { stop("visNetwork package needed for sconeReport()") } if (!requireNamespace("ggplot2", quietly = TRUE)) { stop("ggplot2 package needed for sconeReport()") } scone_res = list() # List of normalized matrices scone_res$normalized_data = lapply(as.list(methods), FUN = function(z){ get_normalized(x,method = z,log=TRUE) }) names(scone_res$normalized_data) = methods # Parameter matrix scone_res$params = get_params(x)[methods,] # Merged score matrix scone_res$scores = cbind(get_scores(x),get_score_ranks(x))[methods,] colnames(scone_res$scores)[ncol(scone_res$scores)] = "mean_score_rank" ## ----- If NULL classifications, Replace with NA ------ if(is.null(bio)){ bio = factor(rep("NA",ncol(scone_res$normalized_data[[1]]))) } if(is.null(batch)){ batch = factor(rep("NA",ncol(scone_res$normalized_data[[1]]))) } ## ----- Network Data for Visualization ----- # Matrix nodes in scone_res leaves = rownames(scone_res$params) # Parents lay1 = unique(gsub(",[^,]*$","",leaves)) lay2 = unique(gsub(",[^,]*$","",lay1)) lay3 = unique(gsub(",[^,]*$","",lay2)) lay4 = unique(gsub(",[^,]*$","",lay3)) all_nodes = c(leaves,lay1,lay2,lay3,lay4) rm(list = c("lay1","lay2","lay3","lay4")) # Collapse no-op names for all node names all_nodes = unique(gsub("^,*|,*$","", gsub("\\,+",",", gsub("none|no_uv|no_bio|no_batch","", all_nodes)))) # Order by decreasing number of operations (root at the end) all_nodes = all_nodes[order(paste0(gsub(".+",",",all_nodes), gsub("[^,]*","",all_nodes)), decreasing = TRUE)] # Collapse no-op names for all leaves leaves = gsub("^,*|,*$","", gsub("\\,+",",", gsub("none|no_uv|no_bio|no_batch","", leaves))) # Re-title nodes in scone_res and root (instead of collapsed names) titles = all_nodes names(titles) = paste0("_",all_nodes) titles[paste0("_",leaves)] = rownames(scone_res$params) titles[titles == ""] = "none" # Create Nodes nodes <- data.frame(id = 1:length(all_nodes), title = titles, group = c("NoData", "Loaded")[1 + (all_nodes %in% leaves)]) # Create Edges edges = data.frame() for(i in 1:length(all_nodes)){ parents = as.vector(sapply(all_nodes, function(x){ grepl(paste0("^",x),all_nodes[i]) })) parents = parents & (all_nodes != all_nodes[i]) if(any(parents)){ edges = rbind(edges,cbind(min(which(parents )),i)) } } colnames(edges)= c("from","to") # Re-map indices according to performance new_title_list = rownames(scone_res$params) # Ordered by perfomance new_title_list = c(new_title_list, as.character( nodes$title[!nodes$title %in% new_title_list]) ) old_ids = nodes[paste0("_", gsub("^,*|,*$","", gsub("\\,+",",", gsub("none|no_uv|no_uv|no_bio|no_batch", "",new_title_list)))),]$id old_nodes = nodes old_edges = edges for(i in 1:length(old_ids)){ nodes$id[old_nodes$id == old_ids[i]] = i edges[old_edges == old_ids[i]] = i } nodes = nodes[order(nodes$id),] ## ----- Color Variable ----- cc <- c(brewer.pal(9, "Set1"), brewer.pal(8, "Set2"),brewer.pal(12,"Set3"), brewer.pal(8, "Accent")) ## ----- UI Definition ----- ui <- shiny::fluidPage( shiny::titlePanel("SCONE Report Browser"), shiny::sidebarLayout( shiny::sidebarPanel( shiny::selectInput("norm_code", label = "Normalization", choices = structure(as.list(rownames(scone_res$params)), names = rownames(scone_res$params)), selected = rownames(scone_res$params)[1]), shiny::selectInput("color_code", label = "Stratify Plots by", choices = list("Batch"="batch", "Biological Condition"="bio", "PAM Cluster"="clust"), selected = "batch"), shiny::sliderInput("dim", label = "Reduced Dimension (PCA)", min=2, max=min(length(batch)-1,10), value = 3), shiny::sliderInput("k", label = "Number of Clusters (PAM)", min=2, max=10, value = 2), shiny::helpText(paste0("Please note that with many cells (>500),", " some plots may take several seconds ", "(sometimes minutes) to appear.")), shiny::downloadLink('downloadData', 'Download') ), shiny::mainPanel(shiny::tabsetPanel(type = "tabs", shiny::tabPanel("Overview", shiny::br(), visNetworkOutput("norm_net", width = "650px"), shiny::br(), DT::dataTableOutput('tbl_score') ), shiny::tabPanel("PCA", shiny::br(), shiny::p(paste0("This panel shows principal ", "component analysis ", "(PCA) results", " on different subsets ", "of genes.")), shiny::br(), shiny::h6("Variance Explained (All Genes)"), shiny::p(paste0("Use this plot", " to decide on the ", "dimensionality of the reduced", " spaced used for evaluation.")), shiny::plotOutput("plot_scree",width = "650px", height = "400px"), shiny::br(), shiny::h6("2D (All Genes)"), shiny::plotOutput("plot_base", width = "650px", height = "450px"), shiny::br(), shiny::h6("3D Interactive (All Genes)"), plotlyOutput("plot3d_base", width = "650px", height = "650px"), shiny::br(), shiny::selectInput("gene_set", label = "Gene selection", choices = list("Negative Controls"= "neg", "Positive Controls"= "pos"), selected = "neg"), shiny::h6("2D (Select Genes)"), shiny::p(paste0("Visualize cells by PCs", " of control gene sets.")), shiny::plotOutput("plot_select", width = "650px", height = "450px") ), shiny::tabPanel("QC", shiny::br(), shiny::p(paste0("This panel shows the absolute", " correlations between", " Principal", " Components (PCs) of", " expression", " matrix vs. PCs of the QC", " (Quality Control) measure ", "matrix. It also shows the PCA ", "of the QC measures. Note that", " the PCA score signs are not", " meaningful (e.g.,", " 'good' cells", " could have a low value of ", "PC1 and 'bad' cells a", " high value).")), shiny::br(), plotlyOutput('qccorPlot', width = "650px", height = "450px"), shiny::h5("PCA of QC metrics"), shiny::h6("2D"), shiny::plotOutput("plot_qc", width = "650px", height = "450px"), shiny::h6("3D Interactive"), plotlyOutput("plot3d_qc", width = "650px", height = "650px") ), shiny::tabPanel("Silhouette", shiny::br(), shiny::p(paste0("Silhouette Width per", " Sample and Contingency", " Tables")), shiny::br(), shiny::plotOutput("plotsil", width = "650px", height = "450px"), shiny::selectInput("cat1", label = "Row Class", choices = list("Batch"= "batch", "Biological Condition"= "bio", "PAM Cluster"= "clust"), selected = "batch"), shiny::selectInput("cat2", label = "Column Class", choices = list("Batch"= "batch", "Biological Condition"= "bio", "PAM Cluster"= "clust"), selected = "bio"), shiny::tableOutput("cat_tab") ), shiny::tabPanel("Control Genes", shiny::br(), shiny::p(paste0("Heatmap of control genes, ", "colored by all", " three categories.")), shiny::br(), shiny::p("Positive controls:"), shiny::plotOutput("hmposcon", width = "650px", height = "450px"), shiny::p("Negative controls:"), shiny::plotOutput("hmnegcon", width = "650px", height = "450px") ), shiny::tabPanel("Stratified PCA", shiny::br(), shiny::p(paste0("Violin plots showing", " conditional ", "distributions of", " selected Principal", " Component.")), shiny::br(), shiny::sliderInput("pcsel", label = paste0("Selected", " Principal ", "Component"), min=1, max= min(length(batch)-1,10), value = 1), shiny::h6("All Genes:"), shiny::plotOutput("violin_base", width = "650px", height = "450px"), shiny::selectInput("gene_set2", label = "Gene selection", choices = list("Negative Controls"= "neg", "Positive Controls"= "pos"), selected = "neg"), shiny::h6("Select Genes:"), shiny::plotOutput("violin_select", width = "650px", height = "450px") ), shiny::tabPanel("Relative Log-Expression", shiny::br(), shiny::p(paste0("Relative ", "Log-Expression Plot ", "for top 100 Most ", "Variable Genes.")), shiny::plotOutput("rle", width = "850px", height = "450px") ) )))) server <- function(input, output, session) { ## ------ Overview Tab ------ # Render Network output$norm_net <- renderVisNetwork({ # Awk: Check if any non-loaded methods are included if(any(!all_nodes %in% leaves)){ visNetwork(nodes, edges,width = "100%",main = "Tree of Methods") %>% visHierarchicalLayout(sortMethod = "directed" ) %>% visGroups(groupname = "NoData", shape = "dot", size = 10, color = list(background = "lightgrey", border = "darkblue", highlight = list(background = "black", border = "red")), shadow = list(enabled = TRUE)) %>% visGroups(groupname = "Loaded", shape = "dot", size = 20, color = list(background = "lightblue", border = "darkblue", highlight = list(background = "cyan", border = "red")), shadow = list(enabled = TRUE)) %>% visEdges(shadow = TRUE, arrows = list(to = list(enabled = TRUE)), color = list(color = "darkblue", highlight = "red")) %>% visOptions(nodesIdSelection = list(enabled = TRUE, values = nodes$id[nodes$group == "Loaded"], selected = nodes$id[nodes$title == rownames(scone_res$params)[1]])) %>% visLegend(width = 0.1, position = "right", main = "Status") }else{ visNetwork(nodes, edges,width = "100%",main = "Tree of Methods") %>% visHierarchicalLayout(sortMethod = "directed" ) %>% visEdges(shadow = TRUE, arrows = list(to = list(enabled = TRUE)), color = list(color = "darkblue", highlight = "red")) %>% visOptions(nodesIdSelection = list(enabled = TRUE, values = nodes$id[nodes$group == "Loaded"], selected = nodes$id[nodes$title == rownames(scone_res$params)[1]])) } }) # Render Table output$tbl_score <- DT::renderDataTable({ DT::datatable(scone_res$scores, extensions = 'Buttons', selection = list( mode = 'single', target = 'row', selected = c(1)), options= list(columnDefs = list(list(visible=FALSE, targets=1:(ncol(scone_res$scores)-1))), dom = 'Bfrtip', buttons = list( list(extend = 'colvis', columns = c(1:(ncol(scone_res$scores)-1)))) )) %>% DT::formatSignif(columns=c(1:(ncol(scone_res$scores))), digits=3) }) # Update Menu Upon Network Selection of (Loaded) Normalization shiny::observeEvent(input$norm_net_selected,{ if(is.character(input$norm_net_selected)){ # probably unnecessary if(!input$norm_net_selected == ""){ # probably unnecessary if(as.integer(input$norm_net_selected) %in% nodes$id[nodes$group == "Loaded"]){ shiny::updateSelectInput( session, "norm_code", selected = as.character( nodes$title[nodes$id == as.integer(input$norm_net_selected)] )) } } } }) # Update Menu Upon Table Selection shiny::observeEvent(input$tbl_score_rows_selected,{ if(length(input$tbl_score_rows_selected) > 0 ){ # probably unnecessary shiny::updateSelectInput(session, "norm_code", selected = as.character( nodes$title[nodes$id == as.integer( input$tbl_score_rows_selected )] ) ) } }) # Upon Menu Selection of Normalization shiny::observeEvent(input$norm_code,{ # Update Network if(is.character(input$norm_net_selected)){ # If selection is empty, then no valid node is selected. if(input$norm_net_selected == ""){ visNetworkProxy("norm_net") %>% visSelectNodes(id = array(nodes$id[nodes$title == input$norm_code])) }else{ # If selection is different from menu, then network must be updated. if(as.character(nodes$title[nodes$id == as.integer( input$norm_net_selected )]) != input$norm_code){ visNetworkProxy("norm_net") %>% visSelectNodes(id = array(nodes$id[nodes$title == input$norm_code])) } } } # Update Table # If selection is empty, then no row is selected. if(length(input$tbl_score_rows_selected) == 0){ DT::dataTableProxy("tbl_score") %>% DT::selectRows(nodes$id[nodes$title == input$norm_code]) }else{ # If selection is different from menu, then row must be updated. if(input$tbl_score_rows_selected != nodes$id[nodes$title == input$norm_code]){ DT::dataTableProxy("tbl_score") %>% DT::selectRows(nodes$id[nodes$title == input$norm_code]) } } }) ## ------ PCA Tab ------ normle <- shiny::reactive({ as.matrix(scone_res$normalized_data[[input$norm_code]]) }) strat_col <- shiny::reactive({ switch(input$color_code, bio = bio, batch = batch, clust = pam_obj()$clust) }) pc_col <- shiny::reactive({ cc[strat_col()] }) pc_gene <- shiny::reactive({ switch(input$gene_set, pos = intersect(poscon,rownames(normle())), neg = intersect(negcon,rownames(normle()))) }) pc_obj_base <- shiny::reactive({ # If user has not provided evaluation projection if(is.null(eval_proj)){ prcomp(t(normle()), center = TRUE, scale = TRUE) } else { proj = eval_proj(normle(), eval_proj_args = eval_proj_args) colnames(proj) = paste0("PC",1:ncol(proj)) pc_out = list(x = proj, sdev = apply(proj,2,sd)) pc_out } }) pc_obj_select <- shiny::reactive({ if(length(pc_gene()) > 0){ # If user has not provided evaluation projection if(is.null(eval_proj)){ prcomp(t(normle()[pc_gene(),]), center = TRUE, scale = TRUE) } else { proj = eval_proj(normle()[pc_gene(),], eval_proj_args = eval_proj_args) colnames(proj) = paste0("PC",1:ncol(proj)) pc_out = list(x = proj, sdev = apply(proj,2,sd)) pc_out } }else{ list() } }) pam_obj <- shiny::reactive({ cluster::pam(x = pc_obj_base()$x[,1:input$dim],k = input$k) }) output$plot_scree <- shiny::renderPlot({ plot(pc_obj_base()$sdev[1:min(input$dim*2, length(batch)-1)]^2/ sum(pc_obj_base()$sdev^2), typ = "l", ylab = "Proportion of Variance", xlab = "PC Index", log = "y") abline(v = input$dim, col = "red",lty = 2) }) output$plot_base <- shiny::renderPlot({ par(mar=c(5.1, 4.1, 4.1, 10.1), xpd=TRUE) plot(pc_obj_base()$x[,1:2],col = pc_col(), pch = 16) legend("topright", inset=c(-0.3,0), legend=levels(factor(strat_col())), fill = cc[sort(unique(factor(strat_col())))]) }) output$plot3d_base <- plotly::renderPlotly({ PC1 <- PC2 <- PC3 <- NULL df <- setNames(data.frame(pc_obj_base()$x[,1:3]), c("PC1", "PC2", "PC3")) plot_ly(df, x = ~PC1, y = ~PC2, z = ~PC3, type = "scatter3d", mode = "markers", marker = list(color=pc_col() )) }) output$plot_select <- shiny::renderPlot({ par(mar=c(5.1, 4.1, 4.1, 10.1), xpd=TRUE) if(length(pc_gene()) > 0){ plot(pc_obj_select()$x[,1:2], col = pc_col(), pch = 16) legend("topright", inset=c(-0.3,0), legend=levels(factor(strat_col())), fill = cc[sort(unique(factor(strat_col())))]) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Control gene set is empty.") } }) pam_obj <- shiny::reactive({ cluster::pam(x = pc_obj_base()$x[,1:input$dim],k = input$k) }) ## ------ QC Tab ------ cor_qc <- shiny::reactive({ abs(cor(qc,pc_obj_base()$x)) }) output$qccorPlot <- plotly::renderPlotly({ metric <- value <- PC <- NULL df = reshape2::melt(cor_qc()[,1:input$dim]) colnames(df) = c("metric","PC","value") df$metric = factor(df$metric,levels = colnames(qc)) df$PC = factor(df$PC, levels = paste0("PC",1:input$dim)) p <- ggplot(data = df, aes(x = PC, fill = metric, weight=value)) + geom_bar(position = "dodge") + ylim(0, 1) + labs(x="PC of Expression", y="Absolute Correlation") + theme(legend.title=element_blank()) ggplotly(p) }) pc_obj_qc <- shiny::reactive({ prcomp(as.matrix(qc),center = TRUE, scale = TRUE) }) output$plot_qc <- shiny::renderPlot({ par(mar=c(5.1, 4.1, 4.1, 10.1), xpd=TRUE) plot(pc_obj_qc()$x[,1:2],col = pc_col(), pch = 16) legend("topright", inset=c(-0.3,0), legend=levels(factor(strat_col())), fill = cc[sort(unique(factor(strat_col())))]) }) output$plot3d_qc <- plotly::renderPlotly({ if(ncol(pc_obj_qc()$x) >= 3){ PC1 <- PC2 <- PC3 <- NULL df <- setNames(data.frame(pc_obj_qc()$x[,1:3]), c("PC1", "PC2", "PC3")) plot_ly(df, x = ~PC1, y = ~PC2, z = ~PC3, type = "scatter3d", mode = "markers", marker = list(color=pc_col() )) }else{ plot_ly(data.frame(), type = "scatter3d", mode = "markers") } }) ## ------ Silhouette Tab ------ sil_obj <- shiny::reactive({ cluster::silhouette(x = as.numeric(strat_col()), dist = dist(pc_obj_base()$x[,1:input$dim])) }) output$plotsil <- shiny::renderPlot({ par(mar=c(5.1, 4.1, 4.1, 10.1), xpd=TRUE) if(length(unique(strat_col())) > 1){ sil = sil_obj() o1 = order(sil[,3]) o = o1[order(-sil[,1][o1])] barplot(sil[,3][o], main = paste0("ASW = ", signif(mean(sil[,3]),3)), xlab = "silhouette width",horiz=TRUE, xlim = 1.5*range(c(sil[,3],-sil[,3])), col = pc_col()[o], border = pc_col()[o]) legend("topright", inset=c(-0.3,0), legend=levels(factor(strat_col())), fill = cc[sort(unique(factor(strat_col())))]) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Stratify plots by a multi-level classification.") } }) cat1 <- shiny::reactive({ switch(input$cat1, bio = bio, batch = batch, clust = pam_obj()$clust) }) cat2 <- shiny::reactive({ switch(input$cat2, bio = bio, batch = batch, clust = pam_obj()$clust) }) output$cat_tab <- shiny::renderTable({ table(cat1(), cat2()) }) ## ------ Control Genes Tab ------ silo <- shiny::reactive({ sil = sil_obj() o1 = order(sil[,3]) o1[order(-sil[,1][o1])] }) output$hmnegcon <- shiny::renderPlot({ if(length(negcon) > 0){ if(length(unique(strat_col())) > 1){ NMF::aheatmap(normle()[negcon,], Colv = silo(), annCol = list(batch = batch, bio = bio, pam = as.factor(pam_obj()$clust)), annColors = list(batch = cc, bio = cc, pam = cc)) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Stratify plots by a multi-level classification.") } }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Control gene set is empty.") } }) output$hmposcon <- shiny::renderPlot({ if(length(poscon) > 0){ if(length(unique(strat_col())) > 1){ NMF::aheatmap(normle()[poscon,], Colv = silo(), annCol = list(batch = batch, bio = bio, pam = as.factor(pam_obj()$clust)), annColors = list(batch = cc, bio = cc, pam = cc)) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Stratify plots by a multi-level classification.") } }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Control gene set is empty.") } }) ## ------ Stratified PCA Tab ------ output$violin_base <- shiny::renderPlot({ Class = factor(strat_col()) Val = pc_obj_base()$x[,input$pcsel] ggplot(data.frame(Class,Val ),aes(x = Class,y = Val)) + geom_violin(scale = "width", trim = TRUE, aes(fill = Class))+ labs(x = "Plot Stratification", y = "PC") + coord_cartesian(ylim = max(abs(range(Val)))*c(-1.5,1.5)) + scale_fill_manual(values=cc[sort(unique(factor(strat_col())))]) + geom_point(colour = "black") + guides(fill=FALSE) }) pc_gene2 <- shiny::reactive({ switch(input$gene_set2, pos = intersect(poscon,rownames(normle())), neg = intersect(negcon,rownames(normle()))) }) pc_obj_select2 <- shiny::reactive({ # If selected genes exist if(length(pc_gene2()) > 0){ # If user has not provided evaluation projection if(is.null(eval_proj)){ prcomp(t(normle()[pc_gene2(),]), center = TRUE, scale = TRUE) } else { proj = eval_proj(normle()[pc_gene2(),], eval_proj_args = eval_proj_args) colnames(proj) = paste0("PC",1:ncol(proj)) pc_out = list(x = proj, sdev = apply(proj,2,sd)) pc_out } }else{ list() } }) output$violin_select <- shiny::renderPlot({ if(length(pc_gene2()) > 0){ Class = factor(strat_col()) Val = pc_obj_select2()$x[,input$pcsel] ggplot(data.frame(Class,Val ),aes(x = Class,y = Val)) + geom_violin(scale = "width", trim = TRUE, aes(fill = Class))+ labs(x = "Plot Stratification", y = "PC") + coord_cartesian(ylim = max(abs(range(Val)))*c(-1.5,1.5)) + scale_fill_manual(values=cc[sort(unique(factor(strat_col())))]) + geom_point(colour = "black") + guides(fill=FALSE) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Control gene set is empty.") } }) ## ------ Relative Log-Expression ------ output$rle <- shiny::renderPlot({ par(mar=c(5.1, 4.1, 4.1, 10.1), xpd=TRUE) if(length(unique(strat_col())) > 1){ vars = apply(exp(normle()),1,var) is_var = rank(-vars) <= 100 median <- apply(normle()[is_var,], 1, median) rle <- apply(normle()[is_var,], 2, function(x) x - median) boxplot(rle[,rev(silo())],col = pc_col()[rev(silo())], outline = FALSE, names = rep("",ncol(rle))) abline(h=0, lty=2) legend("topright", inset=c(-0.2,0), legend=levels(factor(strat_col())), fill = cc[sort(unique(factor(strat_col())))]) }else{ plot(0, type = 'n',xlab = "",ylab = "",xaxt = 'n',yaxt = 'n') text(0,labels = "Stratify plots by a multi-level classification.") } }) ## ----- Download Button ----- output$downloadData <- shiny::downloadHandler( filename = function() { paste('scone_out-', Sys.Date(), '.csv', sep='') }, content = function(con) { datt = cbind(as.character(bio), as.character(batch), as.character(pam_obj()$clust)) colnames(datt) = c("Class-Bio","Class-Batch","Class-Pam") rownames(datt) = colnames(normle()) datt = rbind(t(datt),normle()) write.csv(datt, con) } ) } # Shiny App shiny::shinyApp(ui = ui, server = server) }