inst/fackit_gui/app.R

library(shiny)
library(shinydashboard)
library(shinyjs)
library(shinyFiles)
library(shinyFeedback)
library(data.table)
library(DT)
library(stringr)
library(dplyr)
library(rhandsontable)

library(ggplot2)
#library(ggalt)
library(ggbeeswarm)
library(gridExtra)
library(pals)
library(ggthemes)
library(RColorBrewer)

library(vipor)
library(plotly)

library(pheatmap)

library(FACkit)
library(MEM)
## TODO Add MEM to FACkit properly. Rewrite the function to make it more compatible and better.
## TODO MEM: allow use of character vectors as the cluster column
## TODO MEM: allow a vector of column names to choose the desired markeres instead of shitty interactive mode
## TODO MEM: alter plotting function... or change to use pheatmap.... something.

# load the faster fftRtsne if it exists in user lib, else load the standard Rtsne
if(length(find.package("fftRtsne", quiet = T)) != 0){
  library(fftRtsne)
}else{
  library(Rtsne)
}
source(system.file("fackit_gui", "enrichTest-module.R", package = "FACkit"))



## TODO Make the layout pretty with shinydashboards.
## TODO Add documentation guides in the boxes
## TODO implement download of workspace
## TODO implement download of figures
## TODO implement dynamic report creation and download
## TODO Fix all datatables sig figs and layout options - currently is not working.
## TODO implement server side bookmarking of state, including the associated datafiles.
## TODO implement dynamic report rendering from results.
ui <- dashboardPage(skin = "blue",
                    dashboardHeader(title="FACkit Analysis"),
                    dashboardSidebar(sidebarMenu(menuItem("Home", tabName = "home", icon=icon("home", lib = "font-awesome"), selected=TRUE),
                                                 menuItem("Data Import", tabName = "data_import", icon=icon("import", lib = "glyphicon")),
                                                 menuItem("Transformation", tabName = "transformation", icon=icon("equalizer", lib = "glyphicon")),
                                                 menuItem("Dimensional Reduction", tabName = "dim_red", icon=icon("sitemap", lib = "font-awesome")),
                                                 menuItem("Cluster Enrichment", tabName = "clust_enrich", icon=icon("search", lib = "font-awesome")),
                                                 downloadButton(outputId = "fackit.download", label="Download Data", icon=icon("download", lib="font-awesome")) # TODO centre the button in the sidebar, make it pretty!
                                                 )
                    ),
                    dashboardBody(useShinyFeedback(),
                      tabItems(
                        tabItem(tabName = "home",
                                fluidRow(
                                  includeMarkdown(system.file("fackit_gui", "front_page_notes.md", package = "FACkit"))
                                )
                        ),
                        tabItem(tabName = "data_import",
                                h2("Data Import"),
                                fluidRow(box(width = 12, title = h4("Select Data Folder"),
                                             column(fileInput("file1", "Choose CSV File", multiple = TRUE,
                                                              accept = c("text/csv","text/comma-separated-values,text/plain",".csv")),
                                                    width=4),
                                             column(textInput(inputId="n.cond.cols", label="Condition Column Names (space or ',' delimited)", value = NA, placeholder = NA),
                                                    width=8)
                                             )
                                ),
                                fluidRow(
                                  box(width = 12, title = h4("Add Conditional Data:"),
                                      rHandsontableOutput("table")
                                      ),
                                  box(width = 12, title = h4("Check Marker Names:"),
                                      rHandsontableOutput("column.names")
                                      )
                                  ),

                                fluidRow(
                                  box(
                                    actionButton(inputId = "upload", label = "Upload Data", icon = icon("upload", lib="font-awesome")), snackbar("uploadverb", message = "Data has been uploaded!")
                                    )
                                  )
                        ),
                        tabItem(tabName = "transformation",
                                h2("Data Transformation"),
                                ## TODO Add method for tabulating the conditional data columns
                                ## TODO add downsampling methods
                                h4("Define Cutoffs"),
                                ## TODO add option to define cutoffs from scatter plot
                                fluidRow(box(plotOutput("raw.dist", click = "cutoff.click")),
                                         box(selectInput("raw.dist.marker", label = h5("Select Marker"),
                                                         choices = list(),
                                                         selected = 1),
                                             sliderInput("raw.dist.range", label = h5("Plot Range"),
                                                         min = -100, max = 100, value = c(30,40)))),
                                fluidRow(
                                  box(width = 12, title = h4("Cutoff Values"),
                                      rHandsontableOutput("cutoffs")
                                      )
                                  ),
                                fluidRow(
                                  ### TODO Move to box with cutoffs DT
                                  box(width = 12, title = h4("Run Transformation:"),
                                      column(width = 6,
                                             numericInput(inputId = "asincofac", label = h5("Arc Sin Cofactor"), value = 25, min=0, max = Inf,width = "25%")
                                             ),
                                      column(width = 6,
                                             actionButton("transform", label = "Apply Transformation", icon = icon("ok",lib="glyphicon")), snackbar("transformverb", message = "Data is Uploaded!")
                                             )
                                      )
                                ),
                                h3("Check Transformed Data:"),
                                fluidRow(box(plotOutput("norm.dist", click = "bin.click")),
                                         box(selectInput("norm.dist.marker", label = h5("Select Marker"),
                                                         choices = list(), selected = 1)
                                         )),
                                ## TODO Implement bin defs for scatter plot??
                                fluidRow(box(plotlyOutput("norm.scat")),
                                         box(selectInput("norm.scat.x", label=h5("Select X-axis Marker"),
                                                         choices = list(), selected = 1),
                                             selectInput("norm.scat.y", label=h5("Select Y-axis Marker"),
                                                         choices = list(), selected = 1))),
                                br(),
                                h3("Marker Enrichment Modelling"),
                                fluidRow(
                                  box(width = 12, title = "Set MEM Parameters",
                                      uiOutput("marker.select"),
                                      fluidRow(
                                        column(width = 6,
                                               uiOutput("mem.groups"),
                                               actionButton("run.mem", label="Run", icon = icon("magic", lib="font-awesome")), snackbar("run.memverb", "Running MEM Analysis... be patient!")
                                               ),
                                        column(width = 6,
                                               numericInput("mem.iqr", label = "IQR Threshold", value = NULL, width = "25%"))
                                        )
                                      )
                                ),
                                h4("MEM Results"),
                                fluidRow(box(plotOutput("mem.median"), title = "MEM Group Median Expression"),
                                         box(plotOutput("mem.results"), title="MEM Marker Enrichment Score"))
                        ),
                        tabItem(tabName = "dim_red",
                                h2("tSNE"),
                                fluidRow(
                                  uiOutput("tsne.ui")
                                  ),
                                fluidRow(
                                  column(width=7,
                                         box(plotlyOutput(outputId = "tsne.plot"), width = 12, height="700px")
                                  ),
                                  column(width=5,
                                         box(title = "tSNE Plot Parameters", width = 12,
                                             fluidRow(
                                               column(width=6,
                                                      selectInput("tsne.col", label = "Colour Variable", choices = list(), selected=1, multiple = FALSE, width = "60%")),
                                               column(width=6,
                                                      radioButtons("db.tsne.dim", label = "tSNE Dimensions To Use:", choices = c("2D"="tsne", "1D"="tsne1d"), inline = TRUE))
                                               ),
                                             fluidRow(
                                               column(width=6,
                                                      actionButton("db.knn.run", label = "Run DBScan kNN dist", icon=icon("magic"))), snackbar("db.knnrunverb", "Running DBScan kNN... be patient!")
                                             )
                                         ),
                                         box(plotOutput("db.knn.plot"), width = 12)
                                  )
                                ),
                                h2("DBscan Clustering"), ## TODO Implement DBScan clustering
                                fluidRow( ## TODO Implement cluster refinement
                                  box(plotOutput(outputId = "db.opt.plot"), width = 8),
                                  box(title = "DBscan Parameter Scanning", width = 4,
                                      column(width = 6,
                                             numericInput("db.opt.eps.start", label="Epsilon Start", min = 0, value = 0.01, width="50%"),
                                             numericInput("db.opt.eps.step", label="Epsilon Step Size", min = 0, value = 0.001, step = 0.001, width="50%"),
                                             numericInput("db.opt.mpts.start", label = "Min Points Start", min = 1, value = 3, step = 1, width="50%"),
                                             actionButton("db.opt.run", label = "Run", icon = icon("magic", lib="font-awesome")), snackbar("dboptrunverb", "Scanning Through DBScan Parameter ... be patient!")
                                      ),
                                      column(width = 6,
                                             numericInput("db.opt.eps.end", label="Epsilon End", min = 0, value = 0.04, width="50%"),
                                             br(), br(), br(), br(),
                                             numericInput("db.opt.mpts.end", label = "Min Points End", min = 1, value = 7, step = 1, width="50%")
                                      )
                                  )
                                ),
                                fluidRow(
                                  box(title = "Final DBscan Parameters", width=12,
                                      column(width = 6,
                                             numericInput("db.eps", value = 0, min = 0, label = "Epsilon", width="25%"),
                                             actionButton("db.scan.run", label = "Run", icon = icon("magic", lib="font-awesome")), snackbar("dbscanrunverb", "Running DBScan Clustering!")
                                             ),
                                      column(width = 6,
                                             numericInput("db.mpts", value = 0, min = 0, label = "Min Pts", width="25%")
                                             )
                                      )
                                ),
                                fluidRow(
                                  column(width=6,
                                         box(plotlyOutput(outputId = "db.clust.plot"), width=12, height="700px")),
                                  column(width=6,
                                         box(title = "tSNE Colour Param - NOT IMPLEMENTED!", width = 12, height = "200px",
                                             selectInput("db.tsne.col", label = "Colour Variable", choices = list(), selected=1, multiple = FALSE)),
                                         box(plotlyOutput(outputId = "db.clust.detail.plot"), width=12, height="500px")
                                  )
                                ),
                                h2("Cluster Refinement"),
                                fluidRow(
                                  box(title = "Run Reclustering", width = 12,
                                      uiOutput("reclust.markers"),
                                      numericInput(inputId = "reclust.minpts", label = "Minimum Cluster Membership", value = 4, min = 1, step = 1),
                                      numericInput(inputId = "reclust.alpha", label = "Significance Level for Critical Chi-square Cutoff", value = 0.001, min = 0, max = 1, step = 1),
                                      numericInput(inputId = "reclust.iter", label = "Reclustering Iterations", value = 5, min = 1, step = 1),
                                      actionButton(inputId = "reclust.run", label="Run", icon = icon("magic", lib="font-awesome"))), snackbar("reclustrunverb", "Running Binclust ... this may take a while, go have a coffee")
                                ),
                                fluidRow(
                                  column(width = 6,
                                         box(plotlyOutput(outputId = "reclust.plot"), width=12, height = "650px")),
                                  column(width=6,
                                         box(plotlyOutput(outputId = "reclust.detail.plot"), width=12))
                                )
                                ),
                        tabItem(tabName = "clust_enrich",
                                h2("Identification of Clusters Enriched in Annotations of Interest"),
                                tags$div(
                                  id="enrich_test_params", class="row",
                                  fluidRow(
                                    box(title = "Enrichment Testing Parameters", width = 12,
                                        column(width = 6,
                                               uiOutput(outputId = "enrich.clust"),
                                               checkboxInput(inputId = "enrich.equal",  value = FALSE, width = "100%",
                                                             label = "Use Equal Proportions for Category Enrichment?")),
                                        column(width = 6,
                                               uiOutput("enrich.category"),
                                               actionButton(inputId = "enrich.run", label = "Run", icon=icon("magic", lib="font-awesome"))), snackbar("enrichrunverb", "Running Enrichment Analysis ... be patient")
                                    )
                                  )
                                )

                        )
                      )
                    )
)


server <- function(input, output, session) {
  options(shiny.maxRequestSize=100*1024^2)
  # TODO Modularise the shiny app.
  data.folder <- reactiveValues()
  expdata <- reactiveValues()

  ## TODO get the enrichment results, hierarchical clustering data back from the enrichment test module and save along with the rest of expdata.
  ## TODO insert important inputs into expdata prior to save (i.e. any marker choices, enrichment test params, tsne params, clustering params, mem....etc)
  ## TODO implement loading of saved expdata RDS file and update all inputs based on it .... maybe bookmarking will be better here.
  output$fackit.download <- downloadHandler(
    filename = function(){paste('fackit_results', Sys.time(), '.Rds', sep='')},
    content = function(file) {
      saveRDS(reactiveValuesToList(expdata),file = file)
    }
  )

  # Add Conditional Columns
  observe({
    ## TODO add method to add numeric/integer columns - auto detect entered data
    files <- input$file1$name
    if(is.null(files)){return(NULL)}
    n.cols <- input$n.cond.cols
    if(!is.na(n.cols))
    {
      n.cols <- str_split(n.cols, pattern="[[:punct:][:space:]]", simplify = T)
      n.cols <- n.cols[which(nchar(n.cols) != 0)]
      data.files <- matrix(nrow=length(files), ncol=1+length(n.cols),
                           dimnames=list(c(1:length(files)), c("Data.Files",n.cols)))
    }
    else{
      data.files <- matrix(nrow=length(files), ncol=1+length(n.cols),
                           dimnames=list(c(1:length(files)), c("Data.Files",n.cols)))
    }
    data.files <- as.data.frame(data.files, stringsAsFactors=FALSE)
    data.files$Data.Files <- files
    data.files[] <- lapply(data.files, as.character)
    data.folder$files <- data.files

    output$table <- renderRHandsontable( rhandsontable(data.folder[["files"]]) )
  })




  # Check Column Names
  observe({
    path <- input$file1$datapath
    if(is.null(path)){return(NULL)}
    header <- vector(length=length(path), mode="list")
    for(i in 1:length(path))
    {
      header[[i]] <- read.csv(file = path[i],nrows = 1, header = FALSE, stringsAsFactors = FALSE)
    }
    if(length(unique(unlist(lapply(header, length)))) == 1)
    {
      col.names  <- as.data.frame(matrix(nrow=length(path), ncol=length(header[[1]]), data=unlist(header), byrow=TRUE), stringsAsFactors=FALSE)
    }
    else{
      col.names <- matrix(nrow=length(path), ncol=max(unlist(lapply(header, length))))
      for(i in 1:length(header))
      {
        if(length(header[[i]]) == ncol(col.names))
        {
          col.names[i,] <- header[[i]]
        }
        else{
          col.names[i,] <- c(header[[i]], rep(NA, ncol(col.names)-length(header[[i]])))
        }
      }
      col.names <- as.data.frame(col.names, stringsAsFactors = FALSE)
    }
    # check for identical column names, return single row matrix if TRUE.
    if (all(apply(col.names, MARGIN = 2, FUN = function(x){length(unique(x))}) == 1))
    {
      col.names <- col.names[1,]
    }
    data.folder$col.names <- col.names

    rm(col.names);rm(header)

    output$column.names <- renderRHandsontable( rhandsontable(data.folder[["col.names"]]) )
  })




  ## Data Upload
  observeEvent(input$upload,{
    paths <- input$file1$datapath


    data.folder[["files"]] <- hot_to_r(input$table)
    data.folder[["col.names"]] <- hot_to_r(input$column.names)

    for(i in 1:ncol(data.folder[["col.names"]]))
    {
      nameCheck <- data.folder[["col.names"]][,i]
      nameCheck <- as.vector(str_split(nameCheck, pattern = "", simplify = T))
      nameCheck <- gsub(x=nameCheck, pattern="[[:blank:]]", replacement = ".")
      nameCheck <- gsub(x=nameCheck, pattern="[[:punct:]]", replacement=".")
      nameCheck[1] <- gsub(x=nameCheck[1], pattern="[[:punct:]]", replacement="")
      nameCheck[length(nameCheck)] <- gsub(x=nameCheck[length(nameCheck)], pattern="[[:punct:]]", replacement="")
      nameCheck <- nameCheck[which(nchar(nameCheck) != 0)]
      if(!is.na(as.numeric(nameCheck)[1]))
      {
        data.folder[["col.names"]][,i] <- paste("X", data.folder[["col.names"]][,i], sep="")
      }
      else{
        data.folder[["col.names"]][,i] <- paste(nameCheck, collapse="")
      }
    }
    rm(nameCheck)

    # Create Matrix of Column Names if it was reduced to one row (b/c they were all the same)
    if(nrow(data.folder[["col.names"]]) == 1)
    {
      column.names <- vector(mode="list", length=length(paths))
      for(i in 1:length(column.names))
      {
        column.names[[i]] <- data.folder[["col.names"]]
      }
      column.names <- do.call("rbind", column.names)
      column.names <- as.data.frame(column.names, stringsAsFactors=FALSE)
    }
    else{
      column.names <- as.data.frame(data.folder[["col.names"]], stringsAsFactors=FALSE)
    }
    n.cols <- str_split(input$n.cond.cols, pattern="[[:punct:][:space:]]", simplify = T)
    n.cols <- n.cols[which(nchar(n.cols) != 0)]
    raw.data.list <- vector(length=length(paths), mode="list")
    metadata.list <- vector(length=length(paths), mode="list")
    for(i in 1:length(paths))
    {
      raw.data <- fread(file = paths[i], col.names = as.character(column.names[i,]), data.table = F, stringsAsFactors = F)
      if(length(n.cols) > 0)
      {
        metadata <- matrix(nrow=nrow(raw.data), ncol=length(n.cols),
                           dimnames=list(c(1:nrow(raw.data)), c(n.cols)))
        for(n in colnames(metadata))
        {
          metadata[,n] <- as.character(data.folder[["files"]][i,n])
        }
        metadata.list[[data.folder[["files"]][i,"Data.Files"]]] <- as.data.frame(metadata, stringsAsFactors=FALSE)
      }
      raw.data.list[[data.folder[["files"]][i,"Data.Files"]]] <- as.data.frame(raw.data)
    }
    raw.data <- as.data.frame(do.call("rbind", raw.data.list), stringsAsFactors=FALSE)
    rownames(raw.data) <- 1:nrow(raw.data)
    if(length(n.cols) > 0)
    {
      metadata <- as.data.frame(do.call("rbind", metadata.list), stringsAsFactors=FALSE)
      rownames(metadata) <- 1:nrow(metadata)
      expdata$markers.raw <- colnames(raw.data)
      expdata$metadata <- colnames(metadata)

      raw.data <- cbind(raw.data, metadata)
      rownames(raw.data) <- 1:nrow(raw.data)
      expdata$raw.data <- raw.data
    }
    else{
      expdata$raw.data <- raw.data
      expdata$markers.raw <- colnames(raw.data)
    }

    str(raw.data) %>% print

    updateSelectInput(session, "raw.dist.marker",
                      choices = as.vector(expdata[["markers.raw"]]),
                      selected = as.vector(expdata[["markers.raw"]])[1])

    updateSelectInput(session, "norm.dist.marker",
                      choices = as.vector(expdata[["markers.raw"]]),
                      selected = as.vector(expdata[["markers.raw"]])[1])

    updateSelectInput(session, "norm.scat.x",
                      choices = as.vector(expdata[["markers.raw"]]),
                      selected = as.vector(expdata[["markers.raw"]])[1])
    updateSelectInput(session, "norm.scat.y",
                      choices = as.vector(expdata[["markers.raw"]]),
                      selected = as.vector(expdata[["markers.raw"]])[1])

    expdata$cutoffs <- matrix(data = as.numeric(rep(NA, length(as.vector(expdata[["markers.raw"]])))), nrow=1, ncol = length(as.vector(expdata[["markers.raw"]])), dimnames=list(c(1),c(as.vector(expdata[["markers.raw"]]))))
    showSnackbar("uploadverb")
  })


  # Data Transformation
  ## Choose Marker to Plot Density Distribution of Raw Expression Values
  observeEvent(input$raw.dist.marker,{
    marker <- input$raw.dist.marker
    updateSliderInput(session, "raw.dist.range",
                      min = min(expdata[["raw.data"]][,marker]),
                      max = max(expdata[["raw.data"]][,marker]), step = 1,
                      value = c(min(expdata[["raw.data"]][,marker]),
                                max(expdata[["raw.data"]][,marker])))
    data.folder[["cut"]] <- 0
  })
  output$raw.dist <- renderPlot({
    ggplot(expdata[["raw.data"]], aes_string(x=input$raw.dist.marker)) +geom_density() +geom_vline(xintercept=as.vector(data.folder[["cut"]]), colour="red") +scale_x_continuous(limits=input$raw.dist.range)
  })

  ## Define Cutoff Value from Plot
  observeEvent(input$cutoff.click,{
    data.folder$cut <- input$cutoff.click$x
    expdata[["cutoffs"]][,input$raw.dist.marker] <- input$cutoff.click$x
  })

  ## Define Cutoff Value by Manually Entering in DataTable
  ## TODO make this DT show only 3 sig figs.
  output$cutoffs <- renderRHandsontable( rhandsontable( expdata[["cutoffs"]] ) )

  ## Run Transformation
  observeEvent(input$transform, {
    expdata[["cutoffs"]] <- hot_to_r(input$cutoffs)
    norm.data <- facsnorm(x=expdata[["raw.data"]][,colnames(expdata[["cutoffs"]])], cutoffs = as.numeric(expdata[["cutoffs"]][1,]), asinCofac = input$asincofac, method = "arcsin")

    norm.data <- cbind(norm.data, expdata[["raw.data"]][,expdata[["metadata"]]])
    rownames(norm.data) <- 1:nrow(norm.data)
    expdata$norm.data <- norm.data
    expdata$bin.defs <- matrix(nrow=2, ncol=length(expdata[["markers.raw"]]), dimnames = list(c("pos","neg"),c(expdata[["markers.raw"]])))
    showSnackbar("transformverb")
  })

  ## Check Transformed Data
  output$norm.dist <- renderPlot({
    if(!is.numeric(expdata[["bin.defs"]][,input$norm.dist.marker])){
      ggplot(expdata[["norm.data"]], aes_string(x=input$norm.dist.marker)) +geom_density()
    }else{
      ggplot(expdata[["norm.data"]], aes_string(x=input$norm.dist.marker)) +geom_density() +geom_vline(xintercept = expdata[["bin.defs"]][,input$norm.dist.marker], colour=c("red","red"))
    }

  })

  output$norm.scat <- renderPlotly({
    plot_ly(x = expdata[["norm.data"]][,input$norm.scat.x], y = expdata[["norm.data"]][,input$norm.scat.y], type="scattergl", mode = "markers",
            hoverinfo="skip", marker = list(size = 3, color = 'rgba(0, 0, 0, .5)')) %>%
      layout(yaxis = list(title=input$norm.scat.y),
             xaxis = list(title=input$norm.scat.x))

  })

  # Define Population Bins
  observeEvent(input$bin.click,{
    if(sign(input$bin.click$x) == 1){
      expdata[["bin.defs"]]["pos",input$norm.dist.marker] <- input$bin.click$x
    }
    if(sign(input$bin.click$x) == -1){
      expdata[["bin.defs"]]["neg",input$norm.dist.marker] <- input$bin.click$x
    }
  })

  # Choose Markers for MEM
  output$marker.select <- renderUI({
    checkboxGroupInput(inputId = "marker.select",label = "Select Markers", inline = TRUE,
                       choices = expdata[["markers.raw"]], selected = expdata[["markers.raw"]])
  })
  observe({
    expdata$markers.mem <- input$marker.select ## input$marker.select is a character vector of markers selected in the checkbox.
  })

  # Choose Conditional Column for MEM
  output$mem.groups <- renderUI({
    selectInput(inputId = "mem.groups", label = "Select Conditions", multiple = FALSE, width = "25%", choices = expdata[["metadata"]])
  })

  # Run MEM and plot
  observeEvent(input$run.mem, {
    showSnackbar("run.memverb")
    c("formatting data for MEM") %>% print

    ## BUG this now fails if there is only a single Cond column.
    mem.data <- expdata[["norm.data"]][,c(expdata[["markers.mem"]])] ## TODO once MEM function is rewritten, replace with: expdata[["norm.data"]][,c(expdata[["tsne.markers"]], input$mem.groups)]
    mem.data$cluster <- as.numeric(as.factor(expdata[["norm.data"]][,c(input$mem.groups)])) ## TODO once MEM function is rewritten change this so that the expdata is directly input into the call to MEM without changing cluster to a factor and numeric... etc

    c("Running MEM") %>% print
    if(is.na(input$mem.iqr))
    {
      expdata$mem.res <- MEM(exp_data = mem.data, transform = FALSE, choose.markers = FALSE, choose.ref = FALSE, IQR_thresh = "auto")
    }else{
      expdata$mem.res <- MEM(exp_data = mem.data, transform = FALSE, choose.markers = FALSE, choose.ref = FALSE, IQR_thresh = input$mem.iqr)
    }

    str(mem.data) %>% print

    str(expdata[["mem.res"]][["MAGpop"]][[1]]) %>% print

    c("Plotting MEM") %>% print
    output$mem.median <- renderPlot({
      pheatmap(mat=expdata[["mem.res"]][["MAGpop"]][[1]], border_color = NA, cluster_rows = TRUE, cluster_cols = TRUE,
               clustering_distance_rows = "euclidean", clustering_distance_cols = "euclidean")
    })
    output$mem.results <- renderPlot({
      pheatmap(mat=expdata[["mem.res"]][["MEM_matrix"]][[1]], border_color = NA, cluster_rows = TRUE, cluster_cols = TRUE,
               clustering_distance_rows = "euclidean", clustering_distance_cols = "euclidean")
    })
  })

  ## define tsne UI based on available pacakge (fftRtsne or Rtsne)
  output$tsne.ui <- renderUI({
    if(length(find.package("fftRtsne", quiet = T)) != 0)
    {
      box(
        h4("Set tSNE Parameters:"),
        checkboxGroupInput(inputId = "tsne.markers",label = "Select Markers", inline = TRUE,
                           choices = expdata[["markers.raw"]], selected = expdata[["markers.raw"]]),
        numericInput(inputId = "tsne.dim", label="tSNE Dimensions", value=2, min=1, step=1, width = "25%"),
        numericInput(inputId = "tsne.perp", label="Perplexity", value=30, min=1, step=1, width="25%"),
        numericInput(inputId = "tsne.iter", label="Max Iterations", value=1000, min=1, step=1, width="25%"),
        radioButtons(inputId = "tsne.mode", label = "tSNE Mode", inline = TRUE, c("FFT"=TRUE, "BH"=FALSE)),
        radioButtons(inputId = "tsne.tree", label = "tSNE NN Mode", inline = TRUE, c("Vantage-Point"=FALSE, "ANNOY"=TRUE)),
        numericInput(inputId = "tsne.stop.lying.iter", label="Early Exagg. Phase End", value = 250, min = 1, step=1, width="25%"),
        numericInput(inputId = "tsne.early.exag", label="Early Exagg. Coefficient", value = 12.0, min = 0, step=0.5, width="25%"),
        numericInput(inputId = "tsne.start.late.exag", label="Start Late Exag. At Iter:", value = -1, min = -1, step=1, width="25%"),
        numericInput(inputId = "tsne.late.exag", label="Late Exagg. Coefficient", value = 1.0, min = 0, step=0.5, width="25%"),
        numericInput(inputId = "seed", label="System Seed", value = 42, min = 0, step=1, width="25%"),
        actionButton(inputId = "run.tsne", label = "Run tSNE", icon = icon("magic", lib="font-awesome")), width=12, snackbar("runtsneverb", "Running tSNE ... be patient!")
      )

    }else{
      box(
        h4("Set tSNE Parameters:"),
        checkboxGroupInput(inputId = "tsne.markers",label = "Select Markers", inline = TRUE,
                           choices = expdata[["markers.raw"]], selected = expdata[["markers.raw"]]),
        numericInput(inputId = "tsne.dim", label="tSNE Dimensions", value=2, min=1, step=1, width = "25%"),
        numericInput(inputId = "tsne.perp", label="Perplexity", value=30, min=1, step=1, width="25%"),
        numericInput(inputId = "tsne.iter", label="Max Iterations", value=1000, min=1, step=1, width="25%"),
        numericInput(inputId = "tsne.stop.lying.iter", label="Early Exagg. Phase End", value = 250, min = 1, step=1, width="25%"),
        numericInput(inputId = "tsne.early.exag", label="Early Exagg. Coefficient", value = 12.0, min = 0, step=0.5, width="25%"),
        numericInput(inputId = "seed", label="System Seed", value = 42, min = 0, step=1, width="25%"),
        actionButton(inputId = "run.tsne", label = "Run tSNE", icon = icon("magic", lib="font-awesome")), snackbar("runtsneverb", message = "Running tSNE ... be patient")
      )

    }
  })

  observeEvent(input$run.tsne,{
    showSnackbar("runtsneverb")
    expdata$tsne.markers <- input$tsne.markers

    set.seed(input$seed)

    if(length(find.package("fftRtsne", quiet = T)) != 0){
      "fftRtsne" %>% print
      str(expdata[["norm.data"]]) %>% print
      tsne <- fftRtsne(X = expdata[["norm.data"]][,c(expdata[["tsne.markers"]])],
                       dims = input$tsne.dim, perplexity = input$tsne.perp, check_duplicates = FALSE, max_iter = input$tsne.iter,
                       fft_not_bh = input$tsne.mode, ann_not_vptree = input$tsne.tree, stop_lying_iter = input$tsne.stop.lying.iter,
                       exaggeration_factor = input$tsne.early.exag, no_momentum_during_exag = FALSE, start_late_exag_iter = input$tsne.start.late.exag,
                       late_exag_coeff = input$tsne.late.exag, rand_seed = input$seed)

      expdata$tsne <- data.frame(tsne1=tsne[,1], tsne2=tsne[,2])

      tsne <- fftRtsne(X = expdata[["norm.data"]][,c(expdata[["tsne.markers"]])],
                       dims = 1, perplexity = input$tsne.perp, check_duplicates = FALSE, max_iter = input$tsne.iter,
                       fft_not_bh = input$tsne.mode, ann_not_vptree = input$tsne.tree, stop_lying_iter = input$tsne.stop.lying.iter,
                       exaggeration_factor = input$tsne.early.exag, no_momentum_during_exag = FALSE, start_late_exag_iter = input$tsne.start.late.exag,
                       late_exag_coeff = input$tsne.late.exag, rand_seed = input$seed)

      expdata$tsne1d <- data.frame(tsne1=tsne[,1])

    }else{
      "Rtsne" %>% print
      tsne <- Rtsne(X = expdata[["norm.data"]][,c(expdata[["tsne.markers"]])], dims = input$tsne.dim, perplexity = input$tsne.perp, check_duplicates = FALSE, max_iter = input$tsne.iter,
                    stop_lying_iter = input$tsne.stop.lying.iter, exaggeration_factor = input$tsne.early.exag)

      expdata$tsne <- data.frame(tsne1=tsne$Y[,1], tsne2=tsne$Y[,2])

      tsne <- Rtsne(X = expdata[["norm.data"]][,c(expdata[["tsne.markers"]])], dims = 1, perplexity = input$tsne.perp, check_duplicates = FALSE, max_iter = input$tsne.iter,
                    stop_lying_iter = input$tsne.stop.lying.iter, exaggeration_factor = input$tsne.early.exag)

      expdata$tsne1d <- data.frame(tsne1=tsne$Y[,1])
    }

    "finished tsne" %>% print

    updateSelectInput(session, "tsne.col",
                      choices = as.vector(c(expdata[["tsne.markers"]], expdata[["metadata"]])),
                      selected = as.vector(c(expdata[["tsne.markers"]], expdata[["metadata"]]))[1])

    ## TODO implement or remove... what is this???
  #  updateSelectInput(session, "db.tsne.col",
  #                    choices = as.vector(c(expdata[["tsne.markers"]], expdata[["metadata"]])),
  #                    selected = as.vector(c(expdata[["tsne.markers"]], expdata[["metadata"]]))[1])

    output$reclust.markers <- renderUI({
      checkboxGroupInput(inputId = "reclust.markers",label = "Select Markers", inline = TRUE,
                         choices = expdata[["markers.raw"]], selected = expdata[["markers.raw"]])
    })

  })
  ## TODO Override legend plotting params in plotly (alpha and size are too low for categorical plotting.) Not currently possible??
  ## TODO Make plotting options for the 1D tsne
  observeEvent(input$tsne.col, {
    if(input$tsne.col %in% expdata[["tsne.markers"]]){
      output$tsne.plot <- renderPlotly({
        plot_ly(x = expdata[["tsne"]][,1], y = expdata[["tsne"]][,2], colors = viridis(100), alpha = 0.5, color = expdata[["norm.data"]][,input$tsne.col], type="scattergl", mode = "markers",
                hoverinfo="skip", marker = list(size = 3), width = 600, height = 600) %>%
          layout(xaxis = list(title="tSNE-1"), yaxis = list(title="tSNE-2"), legend=list(markers = list(size=6, alpha=1), font=list(size=12)), scene = list(aspectratio = list(x = 1, y = 1)))
      })
    }else{
      output$tsne.plot <- renderPlotly({
        plot_ly(x = expdata[["tsne"]][,1], y = expdata[["tsne"]][,2], alpha = 0.5,
                color = expdata[["norm.data"]][,input$tsne.col], type="scattergl", mode = "markers", hoverinfo="skip", marker = list(size = 3), width = 600, height = 600) %>%
          layout(xaxis = list(title="tSNE-1"), yaxis = list(title="tSNE-2"), legend=list(markers = list(size=6, alpha=1), font=list(size=12)), scene = list(aspectratio = list(x = 1, y = 1)))
      })
    }
  })

  observeEvent(input$db.knn.run, {
    showSnackbar("db.knnrunverb")
    output$db.knn.plot <- renderPlot({
      kNNdistplot(x = expdata[[input$db.tsne.dim]], k = 4)
    })
  })

  observeEvent(input$db.opt.run, {
    showSnackbar("dboptrunverb")
    expdata$db.opt <- dbscan.opt(data = expdata[[input$db.tsne.dim]], eps.start = input$db.opt.eps.start, eps.end = input$db.opt.eps.end,
                                 step.size = input$db.opt.eps.step, minPts.start = input$db.opt.mpts.start, minPts.end = input$db.opt.mpts.end)

    output$db.opt.plot <- renderPlot({
      ggplot(expdata[["db.opt"]], aes(x=eps, y=n.clust, colour=noise.pts)) +geom_point() +scale_colour_gradientn(colours=magma(100), trans="log") +facet_wrap(~minPts)
    })
  })

  observeEvent(input$db.scan.run, {
    showSnackbar("dbscanrunverb")
    expdata$dbscan <- dbscan(x=expdata[[input$db.tsne.dim]], eps = input$db.eps, minPts = input$db.mpts)

    # this just to make life a little easier later, not having to worry about factor weirdness.
    expdata[["dbscan"]]$cluster <- as.character(expdata[["dbscan"]]$cluster)
    expdata[["norm.data"]]$db.clust <- as.character(expdata[["dbscan"]]$cluster)

    ## plotly output for exploring the dbscan output.
    if(input$db.tsne.dim == "tsne"){
      output$db.clust.plot <- renderPlotly({
        plot_ly(x=expdata[["tsne"]][which(expdata[["dbscan"]]$cluster != 0),1], y=expdata[["tsne"]][which(expdata[["dbscan"]]$cluster != 0),2],
                color=expdata[["dbscan"]]$cluster[which(expdata[["dbscan"]]$cluster != 0)], key=expdata[["dbscan"]]$cluster[which(expdata[["dbscan"]]$cluster != 0)],
                hoverinfo="none", type = "scattergl", mode = "markers", marker = list(size = 3), width = 600, height = 600, source = "db.clust.plot") %>%
          layout(showlegend=FALSE, xaxis = list(title="tSNE-1"), yaxis = list(title="tSNE-2"), legend=list(markers = list(size=6, alpha=1), font=list(size=12)), scene = list(aspectratio = list(x = 1, y = 1)))
      })
    }else{
      output$db.clust.plot <- renderPlotly({
        ## TODO Make plotting options for 1D tsne
        ## TODO Convert to jitter boxplot without the box.
        plot_ly(x=expdata[["tsne1d"]][which(expdata[["dbscan"]]$cluster != 0),], y=1,
                color=expdata[["dbscan"]]$cluster[which(expdata[["dbscan"]]$cluster != 0)], key=expdata[["dbscan"]]$cluster[which(expdata[["dbscan"]]$cluster != 0)],
                hoverinfo="none", type="scattergl", mode = "markers", marker = list(size = 3), width = 600, height = 600, source="db.clust.plot") %>%
          layout(showlegend=FALSE, xaxis = list(title="tSNE-1"), yaxis = list(title="tSNE-2"), legend=list(markers = list(size=6, alpha=1), font=list(size=12)), scene = list(aspectratio = list(x = 1, y = 1)))
      })
    }
  })

  output$db.clust.detail.plot <- renderPlotly({
    coi <- event_data(event = "plotly_click", source = "db.clust.plot")$key[[1]]
    if(is.null(coi) == TRUE){return(NULL)}

    some.data <- expdata[["norm.data"]][which(expdata[["norm.data"]]$db.clust == coi),]
    some.data <- melt(some.data, measure.vars=expdata[["markers.raw"]])

    some.data$x.jitt <- vipor::offsetX(y = some.data$value, x = some.data$variable)
    some.data$tickval <- NA
    temp.markers <- unique(some.data$variable)
    for(i in 1:length(temp.markers)){
      some.data[which(some.data$variable == temp.markers[i]),"x.jitt"] <- some.data[which(some.data$variable == temp.markers[i]),"x.jitt"] + i
      some.data[which(some.data$variable == temp.markers[i]),"tickval"] <- i
    }

    ## TODO add title to plots
    ## HACK this implementation works, but the tick labels can be a bit messy.
    plot_ly(x=some.data[,"x.jitt"], y=some.data[,"value"], type="scattergl", mode="markers", marker=list(size=4, alpha=0.4), hoverinfo="skip") %>%
      layout(title = paste("Marker Expression in Cluster", coi, sep = " - "), xaxis=list(tickmode="array", ticktext=unique(some.data[,"variable"]), tickvals=unique(some.data[,"tickval"]), tickangle=-90), showlegend=FALSE)
  })


  observeEvent(input$reclust.run, {
    showSnackbar("reclustrunverb")
    ## TODO figure out what to do with the noise clusters in dbscan - need to recluster amongst the split clusts.
    c("Running binclust.it") %>% print
    expdata$split.merge <- binclust.it(expdata = cbind(expdata[["norm.data"]], data.frame(binclust = as.character(expdata[["dbscan"]]$cluster), stringsAsFactors = FALSE)),
                                       markers=input$reclust.markers, clust.col = "binclust", noise.clust.id = "0",
                                       minpts = input$reclust.minpts, alpha = input$reclust.alpha, maxit = input$reclust.iter)

    #expdata$clust.split <- clust.split(x = expdata[["norm.data"]], markers = input$reclust.markers, clusters = expdata[["dbscan"]]$cluster)
    #c("Running binmat") %>% print
    #expdata$bin.list <- binmat(data = expdata[["norm.data"]], cluster.col = "db.clust", markers = input$reclust.markers, split.list = expdata[["clust.split"]], thresh = 0) ## TODO alter bin mat to accept a cluster col that is not part of the data frame
    #c("Running split.merge") %>% print

    ## TODO currently has an issue where snlocation cannot find location param problem - occurs mostly with smaller data sets and some system seed values - need way to provide snlocation with predetermined sys.seed
    #expdata$split.merge <- splitmerge(x = expdata[["norm.data"]], markers = input$reclust.markers, clust.col = "db.clust", bin.list = expdata[["bin.list"]], noise.clust.id = "0")[,c("split.clusts","super.clusts")]

    c("plotting") %>% print

    if(input$db.tsne.dim == "tsne"){
      output$reclust.plot <- renderPlotly({
        plot_ly(x=expdata[["tsne"]][which(expdata[["split.merge"]]$id != "0"),1], y=expdata[["tsne"]][which(expdata[["split.merge"]]$id != "0"),2],
                color=expdata[["split.merge"]]$id[which(expdata[["split.merge"]]$id != "0")], key=expdata[["split.merge"]]$id[which(expdata[["split.merge"]]$id != "0")],
                hoverinfo="none", type = "scattergl", mode = "markers", marker = list(size = 3), width = 600, height = 600, source = "reclust.plot") %>%
          layout(showlegend=FALSE, xaxis = list(title="tSNE-1"), yaxis = list(title="tSNE-2"), legend=list(markers = list(size=6, alpha=1), font=list(size=12)), scene = list(aspectratio = list(x = 1, y = 1)))
      })
    }else{
      output$reclust.plot <- renderPlotly({
        ## TODO Make plotting options for 1D tsne
        ## TODO Convert to jitter boxplot without the box.
        plot_ly(hoverinfo="none", type = "scattergl", mode = "markers", source = "reclust.plot") %>%
          add_markers(x=jitter(x=expdata[["tsne1d"]][which(expdata[["split.merge"]]$id != "0"),1]), y=1,
                      color=expdata[["split.merge"]]$id[which(expdata[["split.merge"]]$id != "0")],
                      key=expdata[["split.merge"]]$id[which(expdata[["split.merge"]]$id != "0")],
                      marker = list(size = 3, alpha=0.4), hoverinfo = "none", showlegend = TRUE) %>%
          layout(xaxis = list(title="tSNE-1"))
      })
    }
  })

  output$reclust.detail.plot <- renderPlotly({
    coi <- event_data(event = "plotly_click", source = "reclust.plot")$key[[1]]
    if(is.null(coi) == TRUE){return(NULL)}

    some.data <- expdata[["norm.data"]][which(expdata[["split.merge"]]$id == coi),]
    some.data <- melt(some.data, measure.vars=expdata[["markers.raw"]], id.vars=expdata[["metadata"]])

    some.data$x.jitt <- vipor::offsetX(y = some.data$value, x = some.data$variable)
    some.data$tickval <- NA
    temp.markers <- unique(some.data$variable)
    for(i in 1:length(temp.markers)){
      some.data[which(some.data$variable == temp.markers[i]),"x.jitt"] <- some.data[which(some.data$variable == temp.markers[i]),"x.jitt"] + i
      some.data[which(some.data$variable == temp.markers[i]),"tickval"] <- i
    }

    ## HACK this implementation works, but the tick labels can be a bit messy.
    plot_ly(x=some.data[,"x.jitt"], y=some.data[,"value"], type="scattergl", mode="markers", marker=list(size=4, alpha=0.4), hoverinfo="skip") %>%
      layout(title = paste("Marker Expression in Cluster", coi, sep = " - "), xaxis=list(tickmode="array", ticktext=unique(some.data[,"variable"]), tickvals=unique(some.data[,"tickval"]), tickangle=-90), showlegend=FALSE)
  })

  ## TODO fix choice names for DB clust and super.clust/split.merge clusts
  output$enrich.clust <- renderUI({
    if(is.null(expdata[["split.merge"]])){
      selectInput(inputId = "enrich.clust", label = "Select Clustering", multiple = FALSE, width = "25%", choices = c("db.clust"))
    }else{
      selectInput(inputId = "enrich.clust", label = "Select Clustering", multiple = FALSE, width = "25%", choices = c("db.clust"="db.clust","binclust"="id"))
    }
  })

  output$enrich.category <- renderUI({
    checkboxGroupInput(inputId = "enrich.category", label = "Select Conditions to Perform Enrichment Testing On:", inline = TRUE,
                       choices = expdata[["metadata"]])
  })


  observeEvent(input$enrich.run, {
    showSnackbar("enrichrunverb")
    input$enrich.category %>% print
    conds <- input$enrich.category

    lapply(conds,FUN = function(x) {
      insertUI(
        selector = "#enrich_test_params",
        where = "afterEnd",
        ui = tagList(
          h4(paste(x,"Enrichment Results", sep=" ")),
          enrichTest.UI(paste0("enrich.test", x))
        )
      )

      ## TODO make if else controlling cbind of split merge
      ## TODO make if else controlling cbind of the correct tsne object in case 1D tsne is used instead of 2D
      ## BUG if clust.col param is changed, the whole thing breaks... interactive plots will continue to show the first elements selected in the first run of the analysis
      ## also, the first analysis ui's will go blank.
      ## HACK What is the callModule returning in df? How can I access the enrichment results tables and return to the user as download?
      ## TODO return enrichment tables to expdata values (one per cond test), return any hierarchical clustering data.
      df <- callModule(enrichTest.module,
                       paste0("enrich.test", x),
                       data=reactive(cbind(expdata[["norm.data"]], expdata[["tsne"]], expdata[["split.merge"]])),
                       cats = x,
                       clust.col = input$enrich.clust,
                       ## tsne.dim = input$tsne.dim,
                       equal.props = input$enrich.equal,
                       markers = expdata[["markers.raw"]]
      )

      expdata[[paste0("enrich.test.", x)]] <- df()

    })

  })


}

# Run the application
shinyApp(ui = ui, server = server)
JulianSpagnuolo/FACkit documentation built on June 24, 2019, 12:18 p.m.