Saving values during an R shiny session - r

I have a single-cell gene x cell expression data that I'd like to explore using a shiny app. The cells come from samples and are clustered according to a prior clustering run.
Here is a toy example data set:
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(plotly))
suppressPackageStartupMessages(library(shiny))
set.seed(1)
mat <- matrix(rnorm(1000*1000),nrow=1000,dimnames = list(paste0("gene",1:1000),paste0("cell",1:1000)))
meta.df <- data.frame(cell=colnames(mat),
sample=sample(paste0("sample",1:10),1000,replace=T),
cluster=sample(paste0("cluster",1:5),200,replace=T),
stringsAsFactors = F)
There are two functionalities that I'd like to enable:
View the tSNE 2D embedding of the entire data allowing to choose a gene where the points in the tSNE 2D embedding scatter plot will be colored by its expression values.
Be able to select a specific set of genes and clusters and/or samples on which tSNE will be re-run, and again view this 2D embedding coloring the points according to the expression levels of a chosen gene (in this case obviously the selection options is subsetted to be one of the selected genes).
Here's the tSNE embedding on all the data:
all.data.tsne.df <- data.frame(Rtsne::Rtsne(t(mat))$Y) %>%
rename(tSNE1=X1,tSNE2=X2) %>% cbind(meta.df)
For the second functionality, since one might like to color code many genes using the same tSNE embedding, and since a Rtsne run can take a while to run, I thought I'd save any gene and cell subset tSNE embedding in a list named by the chosen genes and cells, and always check if this subset already exists before running the Rtsne on it.
So at the top of the shiny code I create the options for subsetting samples and clusters and an empty tSNE list:
samples <- c("all",unique(meta.df$sample))
samples.choices <- 1:length(samples)
names(samples.choices) <- samples
clusters <- c("all",unique(meta.df$cluster))
clusters.choices <- 1:length(clusters)
names(clusters.choices) <- clusters
color.vec <- c("lightgray","darkred")
subset.tsne.map <- NULL
Here's my server code:
server <- function(input, output)
{
chosen.samples <- reactive({
validate(
need(input$samples.choice != "",'Please choose at least one of the sample checkboxes')
)
samples.choice <- input$samples.choice
if("all" %in% samples.choice) samples.choice <- samples[-which(samples == "all")]
samples.choice
})
chosen.clusters <- reactive({
validate(
need(input$clusters.choice != "",'Please choose at least one of the cluster checkboxes')
)
clusters.choice <- input$clusters.choice
if("all" %in% clusters.choice) clusters.choice <- clusters[-which(clusters == "all")]
clusters.choice
})
output$gene <- renderUI({
if(input$plotType == "Gene-Subset tSNE"){
selectInput("gene", "Color by Gene", choices = unique(input$subset.genes))
} else{
selectInput("gene", "Color by Gene", choices = rownames(mat))
}
})
scatter.plot <- reactive({
if(!is.null(input$gene)){
row.idx <- which(rownames(mat) == input$gene)
col.idx <- which(colnames(mat) %in% filter(meta.df,cluster %in% chosen.clusters(),sample %in% chosen.samples())$cell)
#col.idx <- which(colnames(mat) %in% filter(meta.df,cluster %in% "cluster4",sample %in% unique(meta.df$sample))$cell)
if(input$plotType != "Gene-Subset tSNE"){
# subset of data
gene.tsne.df <- left_join(all.data.tsne.df %>% filter(cluster %in% chosen.clusters(),sample %in% chosen.samples()),data.frame(cell=colnames(mat)[col.idx],value=mat[row.idx,col.idx]),by=c("cell"="cell"))
scatter.plot <- plot_ly(marker=list(size=6),type='scatter',mode="markers",color=~gene.tsne.df$value,x=~gene.tsne.df$tSNE1,y=~gene.tsne.df$tSNE2,showlegend=F,colors=colorRamp(color.vec)) %>%
layout(xaxis=list(title="tSNE1",zeroline=F,showticklabels=F),yaxis=list(title="tSNE2",zeroline=F,showticklabels=F)) %>%
colorbar(limits=c(min(gene.tsne.df$value,na.rm=T),max(gene.tsne.df$value,na.rm=T)),len=0.4,title="Expression")
break
} else{
subset.genes <- sort(unique(input$subset.genes))
subset.row.idx <- which(rownames(mat) %in% subset.genes)
if(!is.null(subset.tsne.map)){
idx <- which(names(subset.tsne.map) == paste0(paste(subset.row.idx,collapse="_"),":",paste(col.idx,collapse="_")))
if(length(idx) > 0){
subset.tsne.df <- subset.tsne.map[[idx]] %>% mutate(value=mat[row.idx,col.idx])
scatter.plot <- plot_ly(marker=list(size=6),type='scatter',mode="markers",color=~subset.tsne.df$value,x=~subset.tsne.df$tSNE1,y=~subset.tsne.df$tSNE2,showlegend=F,colors=colorRamp(color.vec)) %>%
layout(xaxis=list(title="tSNE1",zeroline=F,showticklabels=F),yaxis=list(title="tSNE2",zeroline=F,showticklabels=F)) %>%
colorbar(limits=c(min(subset.tsne.df$value,na.rm=T),max(subset.tsne.df$value,na.rm=T)),len=0.4,title="Expression")
}
} else{
subset.tsne.df <- data.frame(t(mat[subset.row.idx,col.idx]),cell=colnames(mat)[col.idx]) %>% left_join(meta.df %>% filter(cell %in% colnames(mat)[col.idx]) %>% select(cell,cluster),by=c("cell"="cell"))
tsne.perplexity <- 10*length(subset.row.idx)
while(tsne.perplexity >= 1){
set.seed(1)
tsne.obj <- try(Rtsne::Rtsne(subset.tsne.df %>% select(-cell,-cluster),perplexity=tsne.perplexity),silent=T)
if(class(tsne.obj)[1] != "try-error"){
subset.tsne.df <- cbind(subset.tsne.df,data.frame(tsne.obj$Y) %>% rename(tSNE1=X1,tSNE2=X2))
subset.tsne.map[[length(subset.tsne.map)+1]] <- subset.tsne.df
names(subset.tsne.map)[length(subset.tsne.map)] <- paste0(paste(subset.row.idx,collapse="_"),":",paste(col.idx,collapse="_"))
subset.tsne.df <- subset.tsne.df %>% mutate(value=mat[row.idx,col.idx])
scatter.plot <- plot_ly(marker=list(size=6),type='scatter',mode="markers",color=~subset.tsne.df$value,x=~subset.tsne.df$tSNE1,y=~subset.tsne.df$tSNE2,showlegend=F,colors=colorRamp(color.vec)) %>%
layout(xaxis=list(title="tSNE1",zeroline=F,showticklabels=F),yaxis=list(title="tSNE2",zeroline=F,showticklabels=F)) %>%
colorbar(limits=c(min(subset.tsne.df$value,na.rm=T),max(subset.tsne.df$value,na.rm=T)),len=0.4,title="Expression")
} else{
tsne.perplexity <- tsne.perplexity-2
}
}
}
}
scatter.plot
}
})
output$Embedding <- renderPlotly({
scatter.plot()
})
output$save <- downloadHandler(
filename = function() {
paste0("../shiny/",input$gene,".",input$plotType,".pdf")
},
content = function(file) {
plotly::export(scatter.plot(),file=file)
}
)
}
And here's my UI code:
ui <- fluidPage(
# App title ----
titlePanel("Results Explorer"),
# Sidebar layout with a input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
## custom CSS for 3 column layout (used below for mechanics filter options)
tags$head(
tags$style(HTML(".multicol {-webkit-column-count: 3; /* Chrome, Safari, Opera */-moz-column-count: 3; /* Firefox */column-count: 3;}"))),
## use the css, assuming your long list of vars comes from global.R
wellPanel(tags$div(class="multicol",checkboxGroupInput("samples.choice", "Samples",choices = names(samples.choices),selected="all"))),
wellPanel(tags$div(class="multicol",checkboxGroupInput("clusters.choice", "Clusters",choices = names(clusters.choices),selected="all"))),
# select plot type
selectInput("plotType", "Plot Type", choices = c("tSNE","Gene-Subset tSNE")),
#in case Gene Subset tSNE was chose select the genes
conditionalPanel(condition="input.plotType=='Gene-Subset tSNE'",
selectizeInput(inputId="subset.genes",label="Subset Genes for tSNE",choices=rownames(mat),selected=rownames(mat)[1],multiple=T)),
# select gene
uiOutput("gene"),
# save plot as html
downloadButton('save', 'Save as PDF')
),
# Main panel for displaying outputs ----
mainPanel(
# The plot is called Embedding and will be created in ShinyServer part
plotlyOutput("Embedding")
)
)
)
shinyApp(ui = ui, server = server)
It doesn't seem to be updating subset.tsne.map and each gene that is selected, even for the same sets of cells and genes, it run Rtsne again.
Is it possible to update subset.tsne.map with previously selected subsets at all? and if so am I doing it correctly?

Related

Filtering 2 tables in R, based on selected choices and displaying graph in a shinny app

I am new to r and shinny, and can't figure out how to fix my code. I have 2 dfs (df and historical), and I filter the df to display results selected from SelectInput (col, and col2, "Market" and "Month"). At the same time, I want to filter historical by the same values choosen for "Market" and "Month", and display below the table, a histogram of the filtered price_vector - that is, "average_price" from "historical" but filtered by chosen "Market" and "Month".
Any feedback is appreciated, and by the way, if you have a solution that uses reticulate, I dont mind it (no problem for me filtering a df using python/pandas, but I am teaching myself shinny and can't figure this out)
library(shiny)
library(reticulate)
df <- read.csv(file = 'scores.csv')
historical <- read.csv('TRAIN.csv')
price_vector <- historical$average_price
lmkt <- unique(df$market)
mth <- unique(df$month)
ui <- fluidPage(
selectInput('col','Market',lmkt),
selectInput('col2','Month',mth),
dataTableOutput('table')
)
server <- function(input,output)
output$table <- renderDataTable({
df <- df
{
df = df[df[["market"]] == input$col,]
df = df[df[["month"]] == input$col2,]
}
})
shinyApp(ui = ui, server = server)
You can combine the two statements into one using & operator.
df <- read.csv('https://raw.githubusercontent.com/lmsanch/pyABS/master/scores.csv')
historical <- read.csv('https://raw.githubusercontent.com/lmsanch/pyABS/master/TRAIN.csv')
price_vector <- historical$average_price
lmkt <- unique(df$market)
mth <- unique(df$month)
ui <- fluidPage(
selectInput('col','Market',lmkt),
selectInput('col2','Month',mth),
dataTableOutput('table'),
plotOutput('plot')
)
server <- function(input,output) {
output$table <- renderDataTable({
df[df$market == input$col & df$month == input$col2, ]
})
output$plot <- renderPlot({
hist(price_vector[df$market == input$col & df$month == input$col2])
})
}
shinyApp(ui, server)

Rendering dynamically loaded UI in a loop only shows last value

I have a Shiny app which dynamically loads any number of outputPlot UIs. In the following example I am simply iteration over the first three letters of the alphabet.
To render the plot in the dynamically loaded UIs, I call renderPlot in a loop as such:
for (a in LETTERS[1:3]) {
output[[paste0('p',a)]] <- renderPlot(plot.df(df, a))
}
but the result is that all three outputPlots (pA, pB and pC) are all rendered with plot.df(df, 'C')). Seems to me renderPlot is rendered after the loop has completed and a = 'C'. Instead, the output UIs pA, pB and pC should have been rendered with plot.df(df, 'A'), plot.df(df, 'B') and plot.df(df, 'C'), respectively. But this is clearly not the case when viewing the output.
I have previous had success with this, if the output UI was a module and in the loop calling callModule, which somehow forced the evaluation of the arguments. But I for now am trying to avoid making a separate module for my output UI.
Full reproducible example
library(shiny)
library(dplyr)
# Define UI for application that draws a histogram
ui <- fluidPage(
plotOutput('pltA'),plotOutput('pltB'),plotOutput('pltC'),
tags$hr(),
tags$div(id='placeholder')
)
col <- c(A='#66bd63', B='#fdae61', C='#74add1')
plot.df <- function(df, a) {
#browser()
df <- filter(df, letter==a)
if (nrow(df) == 0) return()
plot(df$i, df$y, type='p', col=col[a], pch=19, main=a)
}
# Define server logic required to draw a histogram
server <- function(input, output, session) {
my_data <- reactiveVal(data.frame())
autoInvalidate <- reactiveTimer(2000)
# Generate some random data and assign to one of three different letters:
observe({
autoInvalidate()
a <- sample(LETTERS[1:3], 1)
data.frame(y=rnorm(5), letter=a) %>%
bind_rows(isolate(my_data())) %>%
group_by(letter) %>%
mutate(i=seq_along(y)) %>%
my_data
})
# Proof of function making a plot.
output$pltA <- renderPlot(plot.df(my_data(), 'A'))
output$pltB <- renderPlot(plot.df(my_data(), 'B'))
output$pltC <- renderPlot(plot.df(my_data(), 'C'))
# Dynamically load output UIs.
observe({
let <- unique(my_data()$letter)
if (is.null(let)) return()
for (l in let) {
if (is.null(session$clientData[[paste0('output_p',l,'_hidden')]])) {
insertUI('#placeholder', 'beforeEnd', ui=plotOutput(paste0('p',l)))
}
}
})
# Update dynamically loaded plots
observe({
df <- my_data()
if (nrow(df) == 0) return()
for (a in LETTERS[1:3]) {
cat('Updating ', a, '\n')
output[[paste0('p',a)]] <- renderPlot(plot.df(df, a))
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
You have to use local (see here).
for (a in LETTERS[1:3]) {
local({
aa <- a
output[[paste0('p',aa)]] <- renderPlot(plot.df(df, aa))
})
}

Warning handling with if/else blocks in Shiny

I am developing a shiny app. which selects nominal (factors) and ordinal(numeric) variables for a given dataset. Then it transform nominal variable(s) (male,female) to dummy variable(s). And eventually merges ordinal variables with dummy variables. The app works fine if I select both nominal and ordinal variables. But if I only want to choose nominal OR ordinal variables,I get warnings: "No character or factor columns found. Please use select_columns to choose columns." Here are my server.r and ui.R
library(fastDummies)
library(data.table)
data=data.frame(A=c(5,2,4),B=c('male','male','female'),C=c(1,3,5))
shinyServer(
function(input, output){
#Select Explanatory Nominal Variables
output$ColumnSelector_dummy <- renderUI({
selectInput("SelectedDummy","Select Nominal Variables (values: male,female)",
choices = as.list(names(data)),multiple=TRUE, selected = NULL)
})
#
df_subset_dummy <- reactive({
a <- subset(data, select = input$SelectedDummy)
return(a)
})
#Convert Nominal variables to Dummy variables
df_subset_dummy_tranformed <- reactive({
df1 <- lapply( df_subset_dummy(), factor)
df2 <- fastDummies::dummy_cols(df1)
drops <- names(df1)
df3 <- df2[, !(names(df2) %in% drops)]
return(df3)
})
#Select Explanatory Ordinal Variables
output$ColumnSelector_ordinal<- renderUI({
selectInput("SelectedOrdinal","Select Ordinal Variables (values: 1,2,3,4,5,6)",
choices = as.list(names(data)), multiple=TRUE,selected = NULL )
})
df_subset_ordinal <- reactive({
a <- subset(data, select = input$SelectedOrdinal)
return(a)
})
#Join Ordinal and Nominal dataframes
df_nominal_ordinal_bind <- reactive({
df <- cbind(df_subset_dummy_tranformed(),df_subset_ordinal())
return(df)
})
output$table_ordinal_nominal <- renderTable(head(df_nominal_ordinal_bind()))
})
shinyUI(
fluidPage(
tabsetPanel(
tabPanel("Data", fluid = TRUE,
sidebarLayout(
sidebarPanel(
uiOutput("ColumnSelector_dummy"),
uiOutput("ColumnSelector_ordinal")
),
mainPanel(
tabsetPanel(
tabPanel('Subsets',
tableOutput('table_ordinal_nominal')
) )) ) ))))
question: How can I use tryCatch and if/else blockes, so that if df_subset_dummy_tranformed() OR df_subset_ordinal() does not exist, I still get result for df_nominal_ordinal_bind and it also can be shown as a table in output$table_ordinal_nominal. Any help would be appreciated.
Without changing your code / approach too much, I guess you could start by adding a check on df_subset_dummy():
df_subset_dummy_tranformed <- reactive({
res <- df_subset_dummy()
if (length(res) == 0) return(res)
df1 <- lapply(res, factor)
df2 <- fastDummies::dummy_cols(df1)
drops <- names(df1)
df3 <- df2[, !(names(df2) %in% drops)]
return(df3)
})
At least this should address the warning you mention.

Update other menus based on selection from another menu

I currently have a Shiny app with 3 menus (more to be added once the bugs are worked out).
I have found examples online of a top down menu filtering approach. Meaning the user must select from the first menu, then the second menu, and so on, but in order. If they select from the 2nd menu first then it does not filter the first menu, only the ones below it and obviously that is a problem.
I want my users to be able to jump around to the menus in any order and have them filter.
In my example there are 3 menus, and what I am trying to do is if observeEvent on any menu (user makes a selection from any menu) then:
Filter the data based on the selection made
updateSelectInput for any menus that have no input selected yet
This will ensure that the menus are up to date with what is actually in the data and ensures that the user doesn't slice down to something that does not actually exist in the data.
Also, note that step #2 is very important - only update menus with no selection made, I have had issues with this because if I just update all other menus then it clears the user selected input, which is still the wrong behavior.
I know what I need to do but I have not been able to pull it off yet, so the help is appreciated.
Update
I updated my code to work with the one answer posted below but it still does not quite work correctly.
Now it does filter down the menus, however, once the subset has been created, it does not allow for it to "filter" back up.
What I mean by this is that If I select the value 3 from the first menu TreeNumber then the last menu filters down to just the value 300 - that is good. BUT if I then go back to the first menu and also select the value 4, I expect that the Circumference menu will now show the values: 300 and 400, however, it still only shows the value 300.
Updated Code:
d <- data.frame("TreeNumber" = c(replicate(7, 1), replicate(7, 2),
replicate(7, 3), replicate(7, 4)),
"TreeAge" = c(1:28),
"Circumference" = c(replicate(7, 100), replicate(7, 200),
replicate(7, 300), replicate(7, 400)))
col_names <- names(d)
# TODO - change these to: "Tree Number", "Tree Age", "Circumference"
user_friendly_names <- c('TreeNumber', 'TreeAge', 'Circumference')
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
h3("Filters:"),
uiOutput("filters"),
# Plot button
fluidRow(column(2, align = "right",
actionButton("plot_graph_button", "Plot")))
),
mainPanel(tableOutput("summary"))
)
)
server <- function(input, output, session) {
#### Create the filter lists for UI ####
output$filters <- renderUI({
if(is.null(col_names)) return(NULL)
lapply(1:length(col_names), function(i) {
col <- paste0(col_names[i])
alias <- user_friendly_names[i]
# Populate input with unique values from column
pickerInput(inputId = alias, label = paste(alias,':'),
choices = unique(d[[col]]), multiple = T)
})
})
# lapply(X = vars, FUN = function(x) {
# vals <- sort(unique(data[[x]]))
# updatePickerInput(session = session, inputId = x, choices = vals)
# })
my_filter <- function(data, var) {
# TODO - Need to convert from user_friendly_names --> col_names in here
if (length(input[[var]]) == 0) return(data)
data %>% subset(data[[var]] %in% input[[var]])
}
subsettedData <- reactive({
d %>% my_filter("TreeNumber") %>% my_filter("TreeAge") %>%
my_filter("Circumference")
# TODO - get into for loop versus hard coding this step:
# for(z in 1:length(col_names)){
# d %>% my_filter(col_names[z])
# }
})
observeEvent(subsettedData(), {
lapply(col_names, function(var) {
selections <- unique(subsettedData()[[var]])
if (length(input[[var]]) == 0)
updatePickerInput(session = session, inputId = var, choices = selections)
})
})
observeEvent(input$plot_graph_button, {
for (j in seq_along(d)) {
updateSelectInput(session = session, inputId = user_friendly_names[j],
choices = c("All", unique(d[[j]])), selected = "All")
}
})
output$summary <- renderTable({
# Do not show a plot when the page first loads
# Wait until the user clicks "Plot" button
if (input$plot_graph_button == 0){
return()
}
# Update code below everytime the "Plot" button is clicked
input$plot_graph_button
isolate({
# Fresh copy of the full data set every time "Plot" button is clicked
d <- copy(Orange)
# Filter data based on UI
for(f in 1:length(col_names)){
if(eval(parse(text = paste0('is.null(input$',user_friendly_names[f],')')))){
# Default to "All" - do not filter
print("All")
}else{
d <- d[d[[col_names[f]]] ==
unlist(eval(parse(text =
paste0('input$',user_friendly_names[f])))), ]
}
}
final_summary_table <<- d
})
})
}
shinyApp(ui = ui, server = server)
Here is an app that applies filtering based on all inputs. I'm not sure how intuitive it is to give a selection called "all" in a selectInput with multiple = TRUE. Maybe It would be better to add a reset button for each selection instead.
I replaced the dataset Orange with tips to get more factor variables. Also, I didn't use data.table in the example since it seems irrelevant for your problem.
library(shiny)
library(dplyr)
data(tips, package = "reshape2")
filter_vars <- c("sex", "smoker", "day", "time")
ui <- fluidPage(
lapply(filter_vars, function(var) {
selectInput(var, var, unique(tips[[var]]), multiple = TRUE)
}),
tableOutput("table")
)
server <- function(input, output, session) {
my_filter <- function(data, var) {
if (length(input[[var]]) == 0) return(data)
data %>% subset(data[[var]] %in% input[[var]])
}
subsettedData <- reactive({
tips %>% my_filter("sex") %>% my_filter("smoker") %>%
my_filter("day") %>% my_filter("time")
})
observeEvent(subsettedData(), {
lapply(filter_vars, function(var) {
selections <- unique(subsettedData()[[var]])
if (length(input[[var]]) == 0)
updateSelectInput(session, var, choices = selections)
})
})
output$table <- renderTable({ subsettedData() })
}
shinyApp(ui, server)

How can I change the input dataset in Shiny R?

I would like to select some rows of a dataset given an input defined by the user. In this case I would like to run different classification trees depending on the different positions in the field. I have a selectInput() in Shiny R which expands a menu where you can select for which field position you want to run the tree. However, for the moment it only runs for all the positions but not for each one individually. I'd appreciate some help on how to switch between datasets depending on user selection.
server.R
library(shiny)
library("rpart")
library("party")
library("partykit")
setwd("~/UNIVERSIDAD/EUR/Block 2/Machine Learning/Shiny App Both")
dat <- read.csv("Voetballers.csv")
dat <- dat[,3:20]
shinyServer(function(input, output, session) {
observe({
if(input$position=="All"){
dat <- dat
}
else if(input$position=="Forward"){
dat <- dat[dat$Position=="Forward",]
}
else if(input$position=="Midfielder"){
dat <- dat[dat$Position=="Midfielder",]
}
else if(input$position=="Defender"){
dat <- dat[dat$Position=="Defender",]
}
else if(input$position=="Goalkeeper"){
dat <- dat[dat$Position=="Goalkeeper",]
}
})
output$independent <- renderUI({
checkboxGroupInput("independent", "Independent Variables:",names(dat)[!names(dat) %in% input$dependent],names(dat)[!names(dat) %in% input$dependent])
})
runRegression <- reactive({
ctree(as.formula(paste(input$dependent," ~ ",paste(input$independent,collapse="+"))),data=dat)
})
runRegression2 <- reactive({
rpart(as.formula(paste(input$dependent," ~ ",paste(input$independent,collapse="+"))),data=dat)
})
output$plot1 <- renderPlot({
plot(runRegression())})
output$plot2 <- renderPlot({
plot(as.party(runRegression2()))})
})

Resources