I am creating a shiny app where I would like to offer users two ways to give two input values: Either directly via two input sliders, or via two data tables on which the app performs some computation with two values as output.
The output values depend on each other, and should sum to one.
I get both these methods working separately: Slider 1 is updated when the value of slider 2 is changed and vice versa, and I managed to compute the output values using the data (See MWE).
Now I would like to combine them such that each slider is updated when a.) the other slider is updated manually; or b.) the data table is changed.
I would like to use these updated slider values for computations in another function.
Any suggestions to change the value of updateSliderInput() when two sources of input are available?
MWE:
library(shiny)
library(rhandsontable)
#### Initialization ####
# Initial data
datA <- datB <- data.frame(success=c(1,1,2), failure=c(1,1,2), total=c(2,2,4))
colnames(datA) <- colnames(datB) <- c(expression(x[T*","*2]==1), expression(x[T*","*2]==0)," " )
rownames(datA) <- rownames(datB) <- c(expression(x[T*","*1]==1), expression(x[T*","*1]==0)," " )
# Dummy function to perform some computation on input data
ComputeWeights <- function(datA,datB){
c(sum(datA)/sum(datA,datB),sum(datB)/sum(datA,datB))
}
#### Server ####
server <- shinyServer(function(input, output, session) {
# Allow users to provide slider input via data table
prevA <- reactive({datA})
prevB <- reactive({datB})
changeA <- reactive({
if(is.null(input$hotA)){return(prevA())}
else{
datA <- as.data.frame(hot_to_r(input$hotA))
datA[c(1,2),3] <- rowSums(datA[c(1,2),c(1,2)])
datA[3,c(1,2)] <- colSums(datA[c(1,2),c(1,2)])
datA[3,3] <- sum(datA[c(1,2),3])
datA
}
})
changeB <- reactive({
if(is.null(input$hotB)){return(prevB())}
else{
datB <- as.data.frame(hot_to_r(input$hotB))
datB[c(1,2),3] <- rowSums(datB[c(1,2),c(1,2)])
datB[3,c(1,2)] <- colSums(datB[c(1,2),c(1,2)])
datB[3,3] <- sum(datB[c(1,2),3])
datB
}
})
output$hotA <- renderRHandsontable({rhandsontable(changeA(), width=375,
rowHeaders=c(expression(x[T*","*2]==1), expression(x[T*","*2]==0)," " ),
colHeaders=c(expression(x[T*","*1]==1), expression(x[T*","*1]==0)," " )) %>%
hot_table(rowHeaderWidth=50) %>%
hot_cols(colWidths = c(50,50,50), format="0", allowInvalid=FALSE)%>%
hot_col(3, readOnly=TRUE)%>%
hot_row(3, readOnly=TRUE)})
output$hotB <- renderRHandsontable({rhandsontable(changeB(), width=375,
rowHeaders=c(expression(x[T*","*2]==1), expression(x[T*","*2]==0)," " ),
colHeaders=c(expression(x[T*","*1]==1), expression(x[T*","*1]==0)," " )) %>%
hot_table(rowHeaderWidth=50) %>%
hot_cols(colWidths = c(50,50,50), format="0", allowInvalid=FALSE)%>%
hot_col(3, readOnly=TRUE)%>%
hot_row(3, readOnly=TRUE)})
# Slider updated with input from slider
observe({
updateSliderInput(session, "w2", value = 1-input$w1,
min = 0, max = 1, step=0.01)
})
observe({
updateSliderInput(session, "w1", value = 1-input$w2,
min = 0, max = 1, step=0.01)
})
output$weights <- renderPrint({
ComputeWeights(changeA(),changeB())
})
})
#### UI ####
ui <- shinyUI(fluidPage(
# First input source: sliders
sidebarLayout(sliderInput("w1", "W1",
min = 0, max = 1, value = 0.5, step=0.01),
sliderInput("w2", "W2",
min = 0, max = 1, value = 0.5, step=0.01)),
mainPanel(
# Second input source: data
rHandsontableOutput("hotA", width="100%"),
rHandsontableOutput("hotB", width="100%"),
textOutput("weights")))
)
shinyApp(ui=ui, server=server)
Would it be possible to just add an observe for changeA and changeB which are reactive?
observe({
wts <- ComputeWeights(changeA(),changeB())
updateSliderInput(session, "w1", value = wts[1], min = 0, max = 1, step=0.01)
updateSliderInput(session, "w2", value = wts[2], min = 0, max = 1, step=0.01)
})
Then you could set the slider input to each value of your returned vector from ComputeWeights.
Let me know if this is what you had in mind.
Related
I tried to achieve when with a chosen split percentage, it returns the train set and then with a sampling method to resample train set and calculate its class freq and perc.
The error I got: object 'split.df' not found when I choose check box 'over'.
Should I use eventReactive or other syntax to achieve? The final return the table with either freq or perc should be dependent on 'split', 'sample' and dropdown 'freq' or 'perc'.
Here is portion that relates in ui:
sidebarLayout(
sidebarPanel(
h3("Train/test set"),
tags$br(),
selectInput(
"trainset",
"Select train component",
choices = list('freq'='freq', 'percentage'='perc'),
),
sliderInput(
"split",
label = "split percentage",
min = 0,
max = 1,
value = 0,
step = 0.1
),
h3("resampling train set"),
checkboxGroupInput('sample', label = "sampling method",
choices = list('original'='original','over'='over', 'under'='under', 'both'='both','ROSE'='ROSE'),
selected = list('original'='original'))
),
Here is a code relates for server:
split.df <- reactive({
index <- createDataPartition(df$class, p=input$split, list=FALSE)
Training_Data <- df[index,]
return(Training_Data)
})
train_set <- reactive({
if(input$sample == 'original')
Training_Data_class <- data.frame(class = split.df()$class)
return(Training_Data_class)
})
over_train_set <- reactive({
split.df <- split.df()
if(input$sample == 'over'){
over <- ovun.sample(class~., data = split.df, method = 'over')$data
Training_Data_class_over <- data.frame(class = over$class)
return(Training_Data_class_over)}
})
trainset_df <- reactive({
freq.df.train <- data.frame(table(train_set()))
colnames(freq.df.train) <- c('class', 'freq')
perc.df.train.=data.frame(prop.table(table(train_set()))*100)
colnames(perc.df.train) <- c('class','perc')
if(input$trainset == 'freq')
return(freq.df.train)
if(input$trainset == 'perc')
return(perc.df.train)
})
over_trainset_df <- reactive({
freq.df.train.over <- data.frame(table(over_train_set()))
colnames(freq.df.train.over) <- c('class', 'freq')
perc.df.train.over=data.frame(prop.table(table(over_train_set()))*100)
colnames(perc.df.train.over) <- c('class','perc')
if(input$trainset == 'freq')
return(freq.df.train.over)
if(input$trainset == 'perc')
return(perc.df.train.over)
})
output$trainsetdistr <- DT::renderDataTable({
if(input$sample == 'over'){
return(over_trainset_df())
}
if(input$sample == 'original'){
return(trainset_df())
}
}
)
I have a dataset with variables for the ID of patients, different tests (MMT), and the treatment.
ID
MMT_II_week15_change
MMT_II_Week20_change
MMT_Tot_week15_change
MMT_Tot_Week20_change
Treatment
As you can see, we have two different tests (MMT_II_change and MMT_Tot_change), for two different timepoints (week15, week20).
What I want is the user to be able to select, first, the test, and then, the timepoint.
In reality, he would be picking just one of the variables, but in two different steps.
Something like:
**Select test:**
MMT_II
MMT_III
**Select timepoint:**
Week15
Week20
And after this, the variable selected would be:
e.g: MMT_II_Week20_change
I though of using regex for this, but it seems quite complicated and coulnd't find of a way of doing it.
Any help really appreciated, as I've been stuck with this for a while.
Would something like this work?
VAR = paste0(test,"_",timepoint,"_change")
...
# then later to use the variable...
.data[[VAR]]
You can wrap the checking of changess occured in a single reactive function in the server section of your code.
uptodateChoice <- reactive({
paste0(input$firstcontrol, "_", input$secondcontrol, "_change")
})
This function will be called once any of the two controls state change.
You can also add any validate(need(...)) checks inside the function if required or simply return() if some conditions are not satisfied.
You can access the string value calling uptodateChoice().
I'm thinking about pivoting the data to longer format, filter it and then pivot again to wider. This way we can filter using filter function directly.
library(tidyverse)
library(shiny)
# create some data
df <- tibble(
ID = 1:5, MMT_II_week15_change = sample(seq(0.01, 0.2, 0.01), 5), MMT_II_week20_change = sample(seq(0.01, 0.2, 0.01), 5),
MMT_Tot_week15_change = sample(seq(0.01, 0.2, 0.01), 5), MMT_Tot_week20_change = sample(seq(0.01, 0.2, 0.01), 5)
)
# pivot wider capturing MMT_* for the first column and the number of week in the second.
df_pivot <- pivot_longer(df, -ID, names_to = c("test", "week"), values_to = "change", names_pattern = "(MMT_.*)_week(\\d+)_change$")
## APP
library(shiny)
ui <- fluidPage(
selectInput("test", "Select Test", choices = unique(df_pivot$test)),
selectInput("timepoint", "Select Timepoint", choices = NULL),
tableOutput("table")
)
server <- function(input, output, session) {
table <- reactiveVal(NULL)
observeEvent(input$test, {
choices <- filter(.data = df_pivot, test == input$test) %>%
{
unique(.$week)
}
updateSelectInput(inputId = "timepoint", choices = choices)
})
# this could also be a reactive.
observe({
table(filter(df_pivot, test == input$test, week == input$timepoint) %>%
pivot_wider(names_from = "test", values_from = "change"))
})
output$table <- renderTable({
table()
})
}
shinyApp(ui, server)
I have written a script which makes use of 2 functions in order to calculate the duration required for a test to run, eg power analysis.
Inputs and code as follows;
## RUN POWER CALCULATION
average_daily_traffic <- 3515/30
control <- 0.47
uplift <- 0.02
num_vars <- 2
sample_size_calculator <- function(control, uplift){
variant <- (uplift + 1) * control
baseline <- ES.h(control, variant)
sample_size_output <- pwr.p.test(h = baseline,
n = ,
sig.level = 0.05,
power = 0.8)
if(variant >= 0)
{return(sample_size_output)}
else
{paste("N/A")}
}
## RUN DAYS CALCULATOR FUNCTION
days_calculator <- function(sample_size_output, average_daily_traffic){
days_required <- c((sample_size_output)*num_vars)/(average_daily_traffic)
if(days_required >= 0)
{paste0("It will take ", round(days_required, digits = 0)*num_vars, " days for this test to reach significance, with a daily average of " , round(average_daily_traffic, digits = 0), " visitors to this page over a 30 day period.")}
else
{paste("N/A")}
}
## RUN FUNCTIONS AND OUTPUT ANSWER
sample_size_calculator <- sample_size_calculator(control, uplift)
sample_size_output <- sample_size_calculator$n
answer <- days_calculator(sample_size_output, average_daily_traffic)
answer
This code is performant and is fit for my purpose in a standalone R script.
However, I need to make these functions executable from within a Shiny app. My attempt is as follows;
library(shiny)
ui <- fluidPage(
actionButton("exe", "Run",
style="color: #fff; background-color: #337ab7; border-color: #2e6da4"),
mainPanel(
textOutput("answer")
))
server <- function(input, output, session) {
sample_size_calculator <- eventReactive(input$exe,{
average_daily_traffic <- 3515/30
control <- 0.47
uplift <- 0.02
num_vars <- 2
variant <- (uplift + 1) * control
baseline <- ES.h(control, variant)
sample_size_output <- pwr.p.test(h = baseline,
n = ,
sig.level = 0.05,
power = 0.8)
if(variant >= 0)
{return(sample_size_output)}
else
{paste("N/A")}
})
days_calculator <- eventReactive (input$exe,{
days_required <- c((sample_size_output)*num_vars)/(average_daily_traffic)
if(days_required >= 0)
{paste0("It will take approximately ", round(days_required, digits = 0)*num_vars, " days or ", round((round(days_required, digits = 0)*num_vars)/365, digits = 1) ," years for this test to reach significance, based on a daily average of " , round(average_daily_traffic, digits = 0), " users to this page in the last 30 days.")}
else
{paste("N/A")}
})
outputs_ <- eventReactive( input$exe, {
req(sample_size_calculator())
req(days_calculator())
sample_size_calculator <- sample_size_calculator(control, uplift)
sample_size_output <- sample_size_calculator$n
answer <- days_calculator(sample_size_output, average_daily_traffic)
output$answer <- renderText(outputs_$answer)
})
}
shinyApp(ui = ui, server = server)
When I run this code, I see the execute button but no output is displayed.
This is very likely due to a limitation in my understanding of how Shiny invokes functions so if there is a better way I would be very grateful to hear it.
Thanks in advance.
* EDITING TO INCLUDE FULL FUNCTIONALITY CODE *
The objective of the code is to use Mark Edmonson's googleAnalyticsR and googleAuthR to enable retrieval of web visit data to a particular URL/page from the Google Analytics account for last 30days and show a trend of this data. This works fine, once the user enters the URL and hits 'Run'.
There is an additional GA call which retrieves additional data for a particular conversion action (see other_data). This is required in order to derive the conversion rate that is used later in the power calculation.
The calculation is cvr <- aeng$users/totalusers
#options(shiny.port = 1221)
## REQUIRED LIBS
library(shiny)
library(googleAnalyticsR)
library(plotly)
library(googleAuthR)
library(markdown)
library(pwr)
gar_set_client(scopes = c("https://www.googleapis.com/auth/analytics.readonly"))
daterange <- function(x) {
as.Date(format(x, "%Y-%m-01"))
}
## DATE PARAMETERS
date_start <- as.Date(Sys.Date(),format='%d-%B-%Y')-31
date_end <- as.Date(Sys.Date(),format='%d-%B-%Y')-1
date_range <- c(date_start, date_end)
## UI SECTION
ui <- fluidPage(
googleAuth_jsUI("auth"),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dur_calc.css")
),
tags$br(),
sidebarLayout(
sidebarPanel(
code("To begin, select from 'Accounts' and enter URL of page to be tested:"),
tags$p(),
column(width = 12, authDropdownUI("auth_dropdown",
inColumns = FALSE)),
textInput("url", label = h5(strong("Page to be tested")), value = "Enter full page URL..."),
hr(),
fluidRow(column(3, verbatimTextOutput("value")
)
),
actionButton("exe", "Run Calculator",
style="color: #fff; background-color: #337ab7; border-color: #2e6da4"),
),
mainPanel(
plotlyOutput("trend_plot"),
textOutput("page"),
textOutput("answer")
)
)
)
## SERVER SECTION
server <- function(input, output, session) {
auth <- callModule(googleAuth_js, "auth")
## GET GA ACCOUNTS
ga_accounts <- reactive({
req(auth()
)
with_shiny(
ga_account_list,
shiny_access_token = auth()
)
})
view_id <- callModule(authDropdown, "auth_dropdown",
ga.table = ga_accounts)
ga_data <- eventReactive( input$exe, {
x <- input$url
#reactive expression
output$page <- renderText({
paste("You have selected the page:", input$url) })
filterPageurl <- dim_filter("dimension97" , "REGEX", x ,not = FALSE)
filts <- filter_clause_ga4(list( filterPageurl))
req(view_id())
req(date_range)
with_shiny(
google_analytics,
view_id(),
date_range = date_range,
dimensions = "date",
metrics = "users",
dim_filters = filts,
max = -1,
shiny_access_token = auth()
)
})
other_data <- eventReactive( input$exe, {
x <- input$url
filterPageurl <- dim_filter("dimension97" , "REGEX", x ,not = FALSE)
filts <- filter_clause_ga4(list( filterPageurl))
seg_id <- "gaid::uzKGvjpFS_Oa2IRh6m3ACg" #AEUs
seg_obj <- segment_ga4("AEUs", segment_id = seg_id)
req(view_id())
req(date_range)
#req(filts)
with_shiny(
google_analytics,
view_id(),
date_range = date_range,
dimensions = "date",
metrics = "users",
dim_filters = filts,
segments = seg_obj,
max = -1,
shiny_access_token = auth()
)
})
outputly <- eventReactive( input$exe, {
req(other_data())
req(ga_data())
aeng <- other_data()
ga_data <- ga_data()
totalusers <<- sum(ga_data$users)
cvr <- aeng$users/totalusers
average_daily_traffic <- totalusers/30
control <- cvr
uplift <- 0.02
num_vars <- 2
})
sample_size_calculator <- eventReactive(input$exe,{
variant <- (uplift + 1) * control
baseline <- ES.h(control, variant)
sample_size_output <- pwr.p.test(h = baseline,
n = ,
sig.level = 0.05,
power = 0.8)
if(variant >= 0)
{return(sample_size_output)}
else
{paste("N/A")}
})
days_calculator <- eventReactive (input$exe,{
days_required <- c((sample_size_output)*num_vars)/(average_daily_traffic)
if(days_required >= 0)
{paste0("It will take approximately ", round(days_required, digits = 0)*num_vars, " days or ", round((round(days_required, digits = 0)*num_vars)/365, digits = 1) ," years for this test to reach significance, based on a daily average of " , round(average_daily_traffic, digits = 0), " users to this page in the last 30 days.")}
else
{paste("N/A")}
})
output$trend_plot <- renderPlotly({
req(ga_data())
ga_data <- ga_data()
plot_ly(
x = ga_data$date,
y = ga_data$users,
type = 'scatter',
mode = 'lines') %>%
layout(title = "Page Visitors by Day (last 30 days)",
xaxis=list(title="Date", tickformat='%Y-%m-%d', showgrid=FALSE, showline=TRUE),
yaxis=list(title = "Users", showgrid=FALSE, showline=TRUE)
)
})
calc_answer <- eventReactive(input$exe, {
req(outputly)
outputly <- outputly()
sample_size_calculator <- sample_size_calculator()
sample_size_output <- sample_size_calculator$n
days_calculator(sample_size_output, average_daily_traffic)
})
output$answer <- renderText(calc_answer())
}
shinyApp(ui = ui, server = server)
A few suggestions that may help.
Would start with a simplified shiny app before adding all of the calculations, may be easier to work with for now
Would avoid putting output statements inside of eventReactive. See below for example.
Consider having only one observeEvent or eventReactive for the button press instead of multiple, especially since some function results depend on others.
Right now there are no inputs, so don't need additional reactive expressions. When you add inputs, though, you probably will.
If you haven't already, review the R Studio Shiny tutorial on Action Buttons and Reactivity.
Hope this is helpful in moving forward.
library(shiny)
library(pwr)
ui <- fluidPage(
actionButton("exe", "Run", style="color: #fff; background-color: #337ab7; border-color: #2e6da4"),
mainPanel(
textOutput("answer")
)
)
server <- function(input, output, session) {
average_daily_traffic <- 3515/30
control <- 0.47
uplift <- 0.02
num_vars <- 2
sample_size_calculator <- function() {
variant <- (uplift + 1) * control
baseline <- ES.h(control, variant)
sample_size_output <- pwr.p.test(h = baseline,
n = ,
sig.level = 0.05,
power = 0.8)
if(variant >= 0)
{return(sample_size_output)}
else
{return(NA)}
}
days_calculator <- function (sample_size_output, average_daily_traffic) {
days_required <- c((sample_size_output)*num_vars)/(average_daily_traffic)
if(days_required >= 0)
{paste0("It will take approximately ", round(days_required, digits = 0)*num_vars, " days or ", round((round(days_required, digits = 0)*num_vars)/365, digits = 1) ," years for this test to reach significance, based on a daily average of " , round(average_daily_traffic, digits = 0), " users to this page in the last 30 days.")}
else
{paste("N/A")}
}
calc_answer <- eventReactive(input$exe, {
sample_size_calculator <- sample_size_calculator()
sample_size_output <- sample_size_calculator$n
days_calculator(sample_size_output, average_daily_traffic)
})
output$answer <- renderText(calc_answer())
}
shinyApp(ui = ui, server = server)
This is the code I have on my server file:
shinyServer(function(input, output) {
output$P = renderText(input$Slider1)
output$n = renderText(input$numeric1)
output$r = renderText(input$numeric2/100)
futureValue <- reactive({
principal <- output$P
numberOfPeriods <- output$n
rate <- output$r
fvalue <- principal*(((1+rate)^numberOfPeriods-1)/rate)
return(fvalue)
})
output$fv <- renderText(futureValue)
})
Code for main panel on UI File:
sidebarPanel(
h4("Select Monthly Investment Amount:"),
sliderInput("Slider1","Select Monthly Investment Amount:", 100, 1000,
100),
numericInput("numeric1", "Select Number of Payments:", value = 12, min
= 6, max = 60, step = 1),
numericInput("numeric2", "Select Interest Rate Percentage:", value =
3.0, min = 0.1, max = 5.0, step = 0.1)
mainPanel
(
h4("Monthly Investment Amount:"),
textOutput("P"),
h4("Number of Periods:"),
textOutput("n"),
h4("Interest Rate:"),
textOutput("r"),
h4("Under the given circumstances, the future value of your
investment is:"),
textOutput("fv")
)
Everything works except for the last part where I'm performing the calculations for future value. Would anyone be able to tell me what I'm doing wrong?
Hi reactivs are functions not variables
output$fv <- renderText(futureValue() )
and change the reactive function like this
futureValue <- reactive({
principal <- input$Slider1
numberOfPeriods <- input$numeric1
rate <- input$numric2
fvalue <-principal*(((1+rate)^numberOfPeriods-1)/rate)
return(fvalue) })
should solve it.
Hope this helps!
My data is a matrix with a variable number of columns. Also , the range of values within the matrix is variable, too.
I want to build a variable number of sliderInput, each corresponding to one column in the matrix.
The higher limit of each slider should correspond to the maxRange within the matrix.
Any suggestion how to do it in one shot?
lapply(1:ncol, function(i) {
sliderInput(
paste0('a', i),
paste0('SelectA', i),
min = min(c(1:maxRange)),
max = max(c(1:maxRange)),
value = c(1, maxRange),
step =1
)
}
)
I had broadly similar problem when I wanted to create a set of input elements corresponding to values derived from the data set (min, max list of options, etc.). Broadly speaking, I would source all the required data via global.R and then reference the concepts in the elements, so on the lines of getting min/max in a slider:
global.R
# Get dates for the slider
## Delete pointless month
dta.nom$DATE_NAME <- sub("February ", replacement = "", x = dta.nom$DATE_NAME)
## Convert to number and get min/max
dta.nom$DATE_NAME <- as.numeric(x = dta.nom$DATE_NAME)
yr.min <- min(dta.nom$DATE_NAME)
yr.max <- max(dta.nom$DATE_NAME)
Then in the slider
ui.R
# Select the dates for the data
sliderInput("sliderYears", label = h5("Years"), min = yr.min,
max = yr.max, value = c(2000, 2010), sep = "",
step = 1, animate = FALSE),
Full code is on GitHub. I'm not sure if I understood you correctly but if you are interested in dynamically connecting elements of your interface in Shiny then you can make use of the updateSelectInput. Any other problems with respect to referencing the data should be solvable with use of global code and referencing the values in your interface elements.
In case someone might bump into the same problem, here is my solution:
ui.R
....
sidebarPanel(
selectInput(
inputId = "dataName",
label = "Select your data",
choices = c("data1", "data2", "data3", "data4")
),
uiOutput(outputId = "sliders")
),
.....
server.R
.....
output$sliders <- renderUI({
numSliders <- numCols(input$dataName)
lapply(1:numSliders, function(i) {
sliderInput(
inputId = paste0('column', i),
label = paste0('Select the range for column ', i),
min = min(selectRange(input$dataName)),
max = max(selectRange(input$dataName)),
value = c(min(selectRange(input$dataName)), max(selectRange(input$dataName))),
step =1)
})
})
........
selectRange is a another function in global.R:
global.R
selectRange <- function(x){
if(x == "data1"){choices = c(1:100)}
if(x == "data2"){choices = c(1:50)}
if(x == "data3"){choices = c(1:75)}
if(x == "data4"){choices = c(1:150)}
return(choices)
}