A new self-learner for Shiny apps, still trying to explore the structure of Shiny apps. I have a piece of code which I want to convert into Shiny apps. I wonder if someone can walk me through how the process goes. My goal is to make the figure title/subtitle dynamic based on inputs (i.e., lnHR0, p_ctl0, tfu) to function. Thanks!!!
R code:
library(tidyverse)
library(ggplot2)
WR_sim_OC <- function(n_trt, n_ctl, lnHR0, p_trt0, med_ctl, tfu, enroll, dur_boost){
#n_trt=60: number of subjects (treatment)
#n_ctl=30: number of subjects (control)
#lnHR0=log(1): Overall Survival Log Hazard Ratio
#p_trt0 = seq(0.46, 0.66, 0.01): Response Rate (Treatment)
#med_ctl=21.8: Median Overall Survival (Control)
#tfu=9: Minimum Follow-up Time
#enroll=19: Enrollment Time
#dur_boost=0: Durability Boost (percentage)
## insert real simulation code here ##
## Fake Results
results <- tibble(ORR_trt = p_trt0, avg_HR = rep(0.774, times = 4), maturity = rep(36.7, times = 4), ORR = c(20.71, 38.87, 60.61, 78.95),
WR = c(46.8, 56.0, 64.3, 72.8), WO = c(46.0, 55.0, 63.7, 71.9), OS = rep(55.0, times = 4))
## Create Operating Characteristic Figure
dat <- results %>% pivot_longer(c(ORR, WR, WO, OS), names_to = 'Method', values_to = 'POS')
out1 <- ggplot(data = dat, aes(x = ORR_trt, y = POS, group = Method)) +
geom_line(aes(color = Method), size = 1) +
geom_point(aes(color = Method), size = 2.5) +
theme(legend.position = 'bottom') +
labs(title = 'HR=1.0 Treatment vs. Control', subtitle = 'ORR in Control Arm=46%, 9mo follow-up', color = 'Method') +
ylab('Probability of Incorrect Go') +
xlab('ORR in Treatment Arm') +
ylim(0, 100)
## Output OC Table + Figure to shiny app
list(results, out1)
}
WR_sim_OC(n_trt=60, n_ctl=30, lnHR0=log(1), p_ctl0=0.46, p_trt0 = seq(0.46, 0.66, 0.06), med_ctl=21.8, tfu=9, enroll=19, dur_boost=0)
I tried writing the ui.R as follows (suppose the suv_plot is the output name), which I know is wrong. The server.R part is too hard for me... Can someone help?
fluidPage(
numericInput("lnHRO",
label = h3("ln(HRO)"),
value = log(1)),
numericInput("pctl",
label = h3("Response Rate (Control)"),
value = 0.46),
numericInput("tfu",
label = h3("Minimum Follow-up Time (Month)"),
value = 9),
hr(),
plotOutput("suv_plot")
)
My first suggestion is just to look at tutorials on shiny, they give a great overview on how to start a project: https://shiny.rstudio.com/tutorial/
I didn't know a thing about programming a few years back, so I understand it can be hard figuring out where to start, so I wanted to give you an idea of how to implement a function, and use shiny inputs to make the resulting table/plot be dynamic.
I switched up your code to be easier to reproduce for myself. I hope this gives you the starting point you need:
library(tidyverse)
library(ggplot2)
library(shiny)
WR_sim_OC <- function(MPG, CYL, DISP){
results <- mtcars%>% #Function to make a table
filter(cyl > CYL,
mpg > MPG,
disp > DISP)
out1 <- ggplot(data = results, aes(x = mpg, y = disp, group = cyl)) +
geom_line(aes(color = hp), size = 1) #Function to make a plot
list(results, out1) #List to create table and function
}
ui <- fluidPage(
numericInput("MilesPerGallon", "mpg", value = 15),
numericInput("Cylinders", "cyl", value = 4),
numericInput("Displacement", "disp", value = 200),
tableOutput("TABLE"),
plotOutput("PLOT")
)
server <- function(input, output, session) {
output$TABLE<-renderTable({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the table
WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[1] #Only pulling the table from the function
})
output$PLOT<-renderPlot({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the plot
WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[2] #Only pulling the plot from the function
})
}
shinyApp(ui, server)
Essentially on the server side where you render the plot or table, you use those inputs from the ui as the dynamic points in your function. I used req() for both of the renderTable and renderPlot to make sure the inputs are filled out before it makes the table plot. Best of luck!
Related
I'm trying to make a flexdashboard using IMDb data, that has an interactive jitter plot where you can change the x and y for visualizing hierarchical clustering result. The code that I've already made can change only the x and number of k. I think I should use reactive function but I don't really understand in using that. I've already tried many other ways from youtube and some documentary but still can't change the y. Here is layout of my dashboard, The y stuck at the runtime variable
data=df %>%
select(Rating, Votes, Gross, Runtime, Metascore)
selectInput("x", label = "X : ",choices = names(data))
selectInput("y", label = "Y : ",choices = names(data))
sliderInput('k',"Cluster",min = 2,max = 10, value = 6)
selectedData=reactive({
data %>% select(input$x, input$y)
})
data_scaled=scale(data)
dist_data=dist(data_scaled, method='euclidean')
hc_data=hclust(dist_data, method = "average")
renderPlot({
ggplot(selectedData(),
aes(x=!!rlang::sym(input$x), y=!!rlang::sym(input$y),
col=factor(cutree(hc_data, k=input$k))))+
geom_jitter(size=5, alpha=0.5 )+
labs(col="Cluster")
})
Here is an alternative example that seems to work, using the diamonds dataset from ggplot2. My guess is that the scaling and clustering steps take so long to run that the the y reactive only appears not to work. I would suggest pre-processing your data if app run times are a problem.
data=diamonds[1:1e3,] %>%
dplyr::select(where(is.numeric))
selectInput("x", label = "X : ",choices = names(data))
selectInput("y", label = "Y : ",choices = names(data))
sliderInput('k',"Cluster",min = 2,max = 10, value = 6)
data_scaled=scale(data)
dist_data=dist(data_scaled, method='euclidean')
hc_data=hclust(dist_data, method = "average")
renderPlot({
ggplot(data,
aes(x=!!rlang::sym(input$x), y=!!rlang::sym(input$y),
col=factor(cutree(hc_data, k=input$k))))+
geom_jitter(size=5, alpha=0.5 )+
labs(col="Cluster")
})
For this particular shiny example I am trying to apply a circular model and display and summarize it within the ggplot and a summary table. This is straightforward up until trying to add in reactive 'brushplot' capabilities. Each of the data points represent a date and the point of the selective graph is to be able to discard undesirable dates. As far as I've figured out, this requires the filtering and model fitting to be within a renderPlot which then leads to complications (unable to find the data/model) trying to call the filtered data and the circular model's statistical outputs outside the function and/or within another reactive function. This yields the Error: object 'k_circ.lm' not found So my questions are:
How can I read the filtered data from the renderPlot function
to the summarytable matrix?
How could I similarly add the fitted model values and residuals from k_circ.lm?
Is there a better or simpler way to arrange app to avoid this?
Alternatative code lines are commented out for a working (if poorly formatted) summary table.
library(dplyr) # For data manipulation
library(ggplot2) # For drawing plots
library(shiny) # For running the app
library(plotly) # For data manipulation
library(circular) # For Circular regressions
library(gridExtra)
# Define UI ----
ui <- fluidPage(
# App title ----
titlePanel("Circular Brushplot Demo"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
sidebarPanel(
actionButton("exclude_toggle", "Toggle points"),
actionButton("exclude_reset", "Reset")
),
# Main panel for displaying outputs ----
mainPanel(
#reactive plot output with point and 'brush' selection
fluidRow(plotOutput("k", height = 400,
click = "k_click",
brush = brushOpts(
id = "k_brush" ))),
plotOutput("s", height = 400)
)
)
)
# Define server logic
server <- function(input, output) {
psideg <- c(356,97,211,232,343,292,157,302,335,302,324,85,324,340,157,238,254,146,232,122,329)
thetadeg <- c(119,162,221,259,270,29,97,292,40,313,94,45,47,108,221,270,119,248,270,45,23)
## Data in radians then to "circular format"
psirad <- psideg*2*pi/360
thetarad <- thetadeg*2*pi/360
cpsirad <- circular(psirad)
cthetarad <- circular(thetarad)
cdat <- data.frame(cpsirad, cthetarad)
###### reactive brush plot ########
# For storing which rows have been excluded
vals <- reactiveValues(
keeprows = rep(TRUE, nrow(cdat)))
output$k <- renderPlot({
# Plot the kept and excluded points as two separate data sets
keep <- cdat[ vals$keeprows, , drop = FALSE]
exclude <- cdat[!vals$keeprows, , drop = FALSE]
## Fits circular model specifically for 'keeprows' of selected data
k_circlm <- lm.circular(type = "c-c", y = keep$cthetarad, x = keep$cpsirad, order = 1)
k_circlm
ggplot(keep, aes(cthetarad, cpsirad)) +
geom_point(aes(cthetarad, cpsirad, colour = keep$Vmag, size = 5))+
scale_colour_gradient(low ="blue", high = "red")+
geom_smooth(method = lm, fullrange = TRUE, color = "black") +
geom_point(data = exclude, shape = 13, size = 5, fill = NA, color = "black", alpha = 0.25) +
annotate("text", x = min(keep$cthetarad), y = Inf, hjust = .1, vjust = 1,
label = paste0("p value 1 = ", round(k_circlm$p.values[1], 2)), size = 7)+
annotate("text", x = min(keep$cthetarad), y = Inf, hjust = .1, vjust = 2.5,
label = paste0("p value 2 = ", round(k_circlm$p.values[2], 2)), size = 7)+
annotate("text", x = min(keep$cthetarad), y = Inf, hjust = .1, vjust = 4,
label = paste0("rho = ", round(k_circlm$rho, 2)), size = 7)+
xlab("Lighthouse Direction (radians)")+ ylab("ADCP site direction (radians)")+
theme(axis.title.x = element_text(size = 20), axis.title.y = element_text(size = 20))
})
# Toggle points that are clicked
observeEvent(input$k_click, {
res <- nearPoints(cdat, input$k_click, allRows = TRUE)
vals$keeprows <- xor(vals$keeprows, res$selected_)})
# Toggle points that are brushed, when button is clicked
observeEvent(input$exclude_toggle, {
res <- brushedPoints(cdat, input$k_brush, allRows = TRUE)
vals$keeprows <- xor(vals$keeprows, res$selected_)})
# Reset all points
observeEvent(input$exclude_reset, {
vals$keeprows <- rep(TRUE, nrow(cdat))})
output$s <- renderPlot({
# Create Summary table
summarytable <- data.frame(matrix(ncol = 4, nrow = nrow(keep)))
colnames(summarytable) <- c( "Psi_dir", "Theta_dir", "Fitted_values", "Residuals")
# Un-comment lines below to read from non-reactive data for working summary table
#summarytable$Psi_dir <- round(cdat$cpsirad, 2)
#summarytable$Theta_dir <- round(cdat$cthetarad, 2)
# attempting to pull from circlm within render plot
# comment out for summarytable to work
summarytable$Psi_dir <- round(keep$cpsirad, 2)
summarytable$Theta_dir <- round(keep$cthetarad, 2)
summarytable$Fitted_values <- round(k_circ.lm$fitted)
summarytable$Residuals <- round(k_circ.lm$residuals)
# outputing table with minimal formatting
summarytable <-na.omit(summarytable)
t <- tableGrob(summarytable)
Q <- grid.arrange(t, nrow = 1)
Q
}
)
}
shinyApp(ui = ui, server = server)
Here's a few ideas - but there are multiple approaches to handling this, and you probably want to restructure your server function a bit more after working with this further.
First, you probably want a reactive expression that will update your model based on vals$keeprows as this changes with your clicks. Then, you can access the model results from this expression from both your plot and data table.
Here is an example:
fit_model <- reactive({
## Keep and exclude based on reactive value keeprows
keep = cdat[ vals$keeprows, , drop = FALSE]
exclude = cdat[!vals$keeprows, , drop = FALSE]
## Fits circular model specifically for 'keeprows' of selected data
k_circlm <- lm.circular(type = "c-c", y = keep$cthetarad, x = keep$cpsirad, order = 1)
## Returns list of items including what to keep, exclude, and model
list(k_circlm = k_circlm, keep = keep, exclude = exclude)
})
It will return a list that you can access from the plot:
output$k <- renderPlot({
exclude <- fit_model()[["exclude"]]
keep <- fit_model()[["keep"]]
k_circlm <- fit_model()[["k_circlm"]]
ggplot(keep, aes(cthetarad, cpsirad)) +
...
And can access the same from your table (though you have as renderPlot?):
output$s <- renderPlot({
keep = fit_model()[["keep"]]
k_circ.lm <- fit_model()[["k_circlm"]]
# Create Summary table
summarytable <- data.frame(matrix(ncol = 4, nrow = nrow(keep)))
...
Note that because the table length changes with rows kept, you might want to use nrow(keep) as I have above, rather than nrow(cdat), unless I am mistaken.
I also loaded gridExtra library for testing this.
I suspect there are a number of other improvements you could consider, but thought this might help you get to a functional state first.
This is my first attempt at using Shiny.
I have a simulated patient-level dataset with 4 variables:
group: Categorical, takes on values A, B and C. Represents 3 different treatment types that were used in the study.
week: Numeric variable, takes on values 1, 4, 8.Represents follow-up week.
painscore: Numeric variable, score on scale of 1-10, with 1 indicating no pain, 10 indicating extreme pain.
dependscore: Numeric variable, score on scale of 1-10, with 1 indicating no dependency on pain meds, 10 indicating extreme dependency.
Trying to build a simple app that accepts two inputs: the week, and the variable, and provides two outputs:
A boxplot of distribution of scores for the selected variable for the selected week. The x axis would represent the 3 levels of group (A, B and C).
A summary table the shows the number of observations, median, 25th percentile, 75th percentile and number of missing.
I was able to create the interactive boxplot, but I am unable to create the summary table. I was able to create static versions of this table in RMarkdown using the summaryBy function from doBy, but I am not able to implement it in Shiny. Tried following the advice here and here but I'm missing something.
Here's my code for reproducibility. Excuse the extensive annotations, (I'm a complete beginner) they are more for myself than for anyone else.
#libraries--------------------
library(shiny)
library(tidyverse)
library(knitr)
library(doBy)
#----------------------------
#input data
set.seed(123)
mydf <- data.frame( group = rep(rep(c("A","B","C"), each = 3), times = 3),
week = rep(rep(c(1,4,8), each = 9)),
painscore = sample(1:10, 27, replace = TRUE),
dependscore = sample(1:10, 27, replace = TRUE) )
#--------------------------
#define custom function to calculate summary statistics for column of interest.
#function explained in a little more detail when applied in the server function.
fun <- function(x) {
c( n = length(x),
m = median(x),
firstq = round(quantile(x)[2], 1),
lastq = round(quantile(x)[4], 1),
missing = mean(is.na(x)))
}
#-------------------------
#UI
ui <- fluidPage(
titlePanel("Shiny Boxplot and Table"),
#User can provide two different inputs
sidebarLayout(
sidebarPanel(
#1. allow user to pick week using radiobuttons
radioButtons(inputId = "pickedwk",
label = "week you want to display",
choices = c(1,4,8),
selected = 1),
#2. user can pick variable to visualize using dropdownboxes
selectInput(inputId = "var",
label = "variable to visualize",
list("How much pain did you feel today?" = "painscore",
"How dependent are you on medication?" = "dependscore")),
#helpertext
helpText("Enter week/variable choices here")
),
#Spaceholders for output
mainPanel(
plotOutput("boxplot"), #boxplot placeholder
htmlOutput("descriptives") #kable html table placeholder
)
)
)
#-------------------------
#Server
server <- function(input, output) {
#create dataset that subsets down to the week picked by user.
weeksub <- reactive({
mydf %>% filter(week == input$pickedwk[1])
})
#1. use reactive datasubset to render boxplot.
output$boxplot <- renderPlot({
ggplot(weeksub(), aes_string(x = "group", y = input$var)) + #input$var works here
geom_boxplot(fill = "red", outlier.shape = 15, outlier.color = "black") +
scale_y_continuous(name = as.character(input$var)) +
scale_x_discrete(name = "group") +
ggtitle(paste("Distribution of", as.character(input$var), "by treatment group"))
})
#2. use same reactive datasubset to render kable descriptive statistics for the variable picked.
output$descriptives <- renderText({
kable(summaryBy(input$var ~ group, data = as.data.frame(weeksub()), FUN = fun),
#note: here, I'm using the summaryBy function from package doBy. It takes the form var~ categoricalvar
# so, either painscore ~ group, or dependscore ~ group depending on what the user picked, and uses
#my custom function to return a table of count, median, 25th percentile, 75th percentile and missing count for
#the 3 levels of the group variable (for A, B, and C)
col.names = c("Number", "Median", "1Q", "3Q", "Missing"))
})
}#server function ends
# Run the application
shinyApp(ui = ui, server = server)
There are a couple of problems in your code:
The formula notation doesn't know how to deal with input$var. summaryBy supports an alternate syntax that works better. (You could also use as.formula and paste to build a formula.)
You are missing the "Group" column in col.names
You have to generate HTML from kable and pass it as HTML to the UI.
Change your table output to this:
output$descriptives <- renderUI({
HTML(kable(summaryBy(list(input$var, 'group'), data = as.data.frame(weeksub()), FUN = fun),
col.names = c('Group', "Number", "Median", "1Q", "3Q", "Missing"),
format='html'
))
})
I have tried all solutions recommended in How to display only integer values on an axis using ggplot2. Unfortunately, I could not solve the issue with any of them.
I have created a Shiny app that produces line graphs of annual data on a variety of variables. This works out nicely for most parameterizations:
No non-integer breaks
However, if I choose certain time spans on the slider, it produces graphs that have non-integer breaks on the x-axis, which makes no sense for a yearly data.
With non-integer breaks
Edit: Here a minimal reproducible version of the application
library(tidyverse)
library(shiny)
options(scipen = 999)
# Data
data1<-data.frame(values = c(15500, 16300, 18200, 28300, 23500, 23700,
31500, 35800, 34700, 36900, 40000, 44700,
53300, 55800, 69800, 89500, 1.13E+5,
1.53E+5, 1.77E+5, 1.83E+5, 1.99E+5),
year = seq(1990, 2010, 1))
#Shiny app
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("period", "Year:", min = 1990, max = 2010, value = c(1990, 2010), sep = "")),
mainPanel(plotOutput("ggplot2"))))
server <- function(input, output) {
data1_subset <- reactive({
filter(data1, year >= input$period[1] & year <= input$period[2])
})
output$ggplot2 <- renderPlot({
ggplot(data = data1_subset(), aes(x = year, y = values)) +
geom_line(aes(color = "red")) +
scale_x_continuous(name = "Year") +
scale_color_discrete(guide=FALSE)+
theme_minimal()
})
}
shinyApp(ui = ui, server = server)
To see the problem, select e.g. time span 2000-2010
Is there any way to suppress non-integer breaks as there are clearly nonsensical with annual data?
Thanks a lot in advance for your help!
Thanks for your help! It seems that the answer was much simpler as I thought. Putting breaks = function(x) unique(floor(pretty(x))) in my scale_x_continuous() function produced integer-only breaks, even without transforming the data into Date format. Removing the unique() does not change the behavior in my case, but it might do in other cases.
You should take care to use the correct class for your date column year (something like Date or POSIXct). As the comment suggests, you need to pick a particular date, like Jan 1st. This will inform ggplot2 about what's probably your intention and it will make it easier for you to steer the display format to something meaningful.
It will work out of the box, you can tune it more with ggplot's date/time scales.
The reprex with the presented idea implemented (and one more glitch regarding the line colour fixed):
library(tidyverse)
library(shiny)
# Data
tibble(values = c(15500,16300,18200,28300,23500,23700,
31500,35800,34700,36900,40000,44700,
53300,55800,69800,89500,1.13E+5,
1.53E+5,1.77E+5,1.83E+5,1.99E+5),
year = seq(1990, 2010, 1)) %>%
mutate(year = lubridate::ymd(year, truncated = 2L)) ->
data1
# Shiny app
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("period", "Year:",
min = 1990, max = 2010,
value = c(1990, 2010), sep = "")),
mainPanel(plotOutput("ggplot2"))))
server <- function(input, output) {
data1_subset <- reactive({
filter(data1,
year >= lubridate::ymd(input$period[1], truncated = 2L),
year <= lubridate::ymd(input$period[2], truncated = 2L))
})
output$ggplot2 <- renderPlot({
ggplot(data = data1_subset(), aes(x = year, y = values)) +
geom_line(color = "red") +
xlab("Year") +
theme_minimal()
})
}
shinyApp(ui = ui, server = server)
I am trying to use ggplot2 with a reactive object in shiny.
So, I understand that reactive gives a reactive object that needs to be changed to be a value, but I tried to assign it to a different object both in the reactive part and in the renderPlot part and got no results...
So, if I don't assign it to a different variable, I get the message:
ggplot2 doesn't know how to deal with data of class
reactiveExpr/reactive
I know there are at least 2 questions (here and here) that talk about this problem. I did do my homework and try to use those answers and it didn't work. As far as I can tell, those answers suggest to assign the reactive object to another variable, which I did, and got the message:
Error: object is not a matrix
The answers delineated there do not really explain how to fix the problem in general, they just provide the code to fix that particular example (one answer reads " I think this particular problem should be solved with the following code:" but doesn't point out what part of the code solves the issue).
Can you please explain, how to fix this in this example in the more general case?
here is a Minimal example:
server.R
library(shiny)
shinyServer(function(input, output) {
library(ggplot2)
library(lme4)
model1 <- lmList((conc) ~ time | Subject, data = Indometh)
newpoints <- reactive({data.frame("Subject" = c(1, 4, 2, 5, 6, 3),
"conc" = predict(model1, newdata = data.frame(time=input$sliderTime)),
"time" = rep(input$sliderTime, 6))
})
output$myPlot <- renderPlot({
newpoints2 <- newpoints()
g <- ggplot(Indometh, aes(time, (conc), color= Subject)) + geom_point() +
stat_smooth(method="lm",fullrange=TRUE, fill=NA)
newg <- g + geom_point(data = newpoints2, mapping =
aes(x = time, y = (conc), color= factor(Subject)))
print(newg)
})
})
and the ui.R file:
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Concentration of Indomethacin"),
sidebarLayout(
sidebarPanel(
sliderInput("slidertime",
"Time:",
min = 8,
max = 15,
value = 8)),
mainPanel(
plotOutput("myPlot")
)
)
))
The problem with your example is that you are calling the input with the wrong name it is not sliderTime it is slidertime i change the server to this and it works perfect.
How I fix it? just use browser() inside the reactive expression and found that input$sliderTime was NULL so I check the name.
library(shiny)
shinyServer(function(input, output) {
library(ggplot2)
library(lme4)
model1 <- lmList((conc) ~ time | Subject, data = Indometh)
newpoints <- reactive({
data.frame("Subject" = c(1, 4, 2, 5, 6, 3),
"conc" = predict(model1, newdata = data.frame(time=input$slidertime)),
"time" = rep(input$slidertime, 6))
})
output$myPlot <- renderPlot({
newpoints2 <- newpoints()
g <- ggplot(Indometh, aes(time, (conc), color= Subject)) + geom_point() +
stat_smooth(method="lm",fullrange=TRUE, fill=NA)
newg <- g + geom_point(data = newpoints2, mapping =
aes(x = time, y = (conc), color= factor(Subject)))
print(newg)
})
})