I've been working on a visualization project in shiny. I'm trying to filter a data set by given input - number of state and range of the slider. Unfortunately, r 'omits' the the code part and outputs the entire data set. I also get warnings: 'data' is not a graphical parameter.
library(shiny)
library(Ecdat)
u <- shinyUI(pageWithSidebar(
headerPanel("Social benefits"),
sidebarPanel(
selectInput("variable", "Variable:",
list("Unemployment",
"Max benefit"
)),
#Specification of state
textInput("state", "State:", value = "93"),
# Specification of range within an interval
sliderInput("range", "Range:",
min = 1, max = 100, value = c(20,100))
),
mainPanel(
plotOutput("mpgPlot")
)
))
s <- shinyServer(function(input, output)
{
#filter by state -ERROR
p <- reactive({ Benefits[Benefits$state == input$state,]})
#filter by slider range - ERROR
dataX <- reactive({ p()[input$range[1]:input$range[2],,drop = FALSE] })
variable <- reactive({
switch(input$variable,
"Unemployment" = stateur,
"Max benefit" = statemb
)
})
caption <- reactive({
paste(input$variable)
})
output$mpgPlot <- renderPlot({
plot(variable(), data = dataX(), type = "l",ylab = caption())
})
})
shinyApp(u,s)
All that was actually needed was to specify the data set name before the variable, since the data set from the environment was overshadowing the filtered one.
output$urPlot <- renderPlot({
plot(dataX()$stateur, data = dataX(), type = "l",ylab = "Unemployment")
})
output$mbPlot <- renderPlot({
plot(dataX()$statemb, data = dataX(), type = "l",ylab = "Max benefit")
})
Related
I am struggling with getting the code to work for this log widget I want to add to my interactive plot in shiny. I am able to modify the graphs x and y axis to a log scale by adding log(dat()[[input$yvrbl]]) to the server coder
server <- function(input, output) {
x <- reactive({
log(dat()[[input$yvrbl]])
})
y <- reactive({
log(dat()[[input$yvrbl]])
})
I was able to create the widgets on the ui code as well. I am still unable to transform the data to the log version based on whether or not the widget is checked. I tried making a separate reactive expression to host the changed log version of the x and y axis depending on an if statement. Please let me know what else I can do.
library(shiny)
library(plotly)
library(tibble)
library(tidyverse)
library(tidyr)
library(readr)
library(dplyr)
library(ggplot2)
# set working directory
setwd("~/BDSWD")
#read data
gm <- read_csv("gapminder_clean.csv")
# Define UI ----
ui <- fluidPage(
column(3,offset = 4, titlePanel("Explore Gapminder Data with Shiny")),
headerPanel('Graphs'),
mainPanel(
plotlyOutput('plot')
),
sidebarPanel(
#variable selection for x-axis
selectInput(inputId ='xvrbl', #The input slot that will be used to access the value.
label = 'X-Axis Variable', #Display label for the control, or NULL for no label.
choices = colnames(gm), #List of values to select from
selected = 'CO2 emissions (metric tons per capita)'
),
checkboxInput(inputId = "LogX",
label = "Log Transform",
value = FALSE),
#variable selection for y-axis
selectInput(inputId ='yvrbl', #The input slot that will be used to access the value.
label = 'Y-Axis Variable', #Display label for the control, or NULL for no label.
choices = colnames(gm), #List of values to select from
selected = 'gdpPercap'
),
checkboxInput(inputId = "LogY",
label = "Log Transform",
value = FALSE),
#date range - slider
sliderInput(inputId = "time",
label = "Years",
min = min(gm$Year),
max = max(gm$Year),
step = 5,
value = c(min(gm$Year),max(gm$Year)))
)
)
server <- function(input, output) {
x <- reactive({
dat()[[input$xvrbl]]
})
y <- reactive({
dat()[[input$yvrbl]]
})
dat <- reactive({
subset(gm, Year %in% input$time)
})
lgrthmc <- reactive({
if(isTRUE(input$LogY)) {
y <- reactive({
log(dat()[[input$yvrbl]])
})
} else {}
if(isTRUE(input$LogX)) {
x <- reactive({
log(dat()[[input$xvrbl]])
})
} else {}
})
output$plot <- renderPlotly({
plot_ly(
x = x(),
y = y(),
type = "scatter",
mode = "markers",
color = dat()$continent
) %>%
layout(
title = 'Gapminder Dataset',
plot_bgcolor = "#e5ecf6",
xaxis = list(title = input$xvrbl),
yaxis = list(title = input$yvrbl),
legend = list(title=list(text='<b> Continent </b>'))
)
})
}
# Run the app
shinyApp(ui = ui, server = server)
Instead of wrapping reactives inside a reactive you could achieve your desired result by adding an if inside your reactives, e.g.
Note: I slightly adjusted the subsetting of your data to take the sliderInput into account.
x <- reactive({
x <- dat()[[input$xvrbl]]
if (input$LogX) x <- log(x)
return(x)
})
library(gapminder)
library(shiny)
library(plotly)
library(tidyverse)
gm <- gapminder |> rename(Year = year)
# Define UI ----
ui <- fluidPage(
column(3, offset = 4, titlePanel("Explore Gapminder Data with Shiny")),
headerPanel("Graphs"),
mainPanel(
plotlyOutput("plot")
),
sidebarPanel(
# variable selection for x-axis
selectInput(
inputId = "xvrbl", # The input slot that will be used to access the value.
label = "X-Axis Variable", # Display label for the control, or NULL for no label.
choices = colnames(gm), # List of values to select from
selected = "lifeExp"
),
checkboxInput(inputId = "LogX",
label = "Log Transform",
value = FALSE),
# variable selection for y-axis
selectInput(
inputId = "yvrbl", # The input slot that will be used to access the value.
label = "Y-Axis Variable", # Display label for the control, or NULL for no label.
choices = colnames(gm), # List of values to select from
selected = "gdpPercap"
),
checkboxInput(
inputId = "LogY",
label = "Log Transform",
value = FALSE
),
# date range - slider
sliderInput(
inputId = "time",
label = "Years",
min = min(gm$Year),
max = max(gm$Year),
step = 5,
value = range(gm$Year)
)
)
)
server <- function(input, output) {
x <- reactive({
x <- dat()[[input$xvrbl]]
if (input$LogX) x <- log(x)
return(x)
})
y <- reactive({
y <- dat()[[input$yvrbl]]
if (input$LogY) y <- log(y)
return(y)
})
dat <- reactive({
subset(gm, Year >= input$time[[1]], Year <= input$time[[2]])
})
output$plot <- renderPlotly({
plot_ly(
x = x(),
y = y(),
type = "scatter",
mode = "markers",
color = dat()$continent
)
})
}
# Run the app
shinyApp(ui = ui, server = server)
#>
#> Listening on http://127.0.0.1:6593
Below is a code I've been working where the goal is to sort and compare data. However when I try to add multiple factors to compare the data appears to get confused. My expectation is that when I run my app and begin to select multiple PetalWidth options it should show me more data, however the filter doesn't seem to be broadening based on my selection. What am I doing wrong?
library(shinydashboard)
library(tidyverse)
library(plotly)
library(shiny)
#______________________________________________________________________________#
server <- function(input, output, session) {
df <- reactive({
subset(iris, Petal.Width == input$Petalw)
})
# Extract list of Petal Lengths from selected data - to be used as a filter
p.lengths <- reactive({
unique(df()$Petal.Length)
})
# Filter based on Petal Length
output$PetalL <- renderUI({
selectInput("PetalLengthSelector", "PetalLength", as.list(p.lengths()))
})
# Subset this data based on the values selected by user
df_1 <- reactive({
foo <- subset(df(), Petal.Length == input$PetalLengthSelector)
return(foo)
})
output$table <- DT::renderDataTable(
DT::datatable(df_1(), options = list(searching = FALSE,pageLength = 25))
)
output$correlation_plot <- renderPlotly({
plot1 <- plot_ly(data=df_1(),
x = ~Petal.Length,
y = ~Petal.Width,
type = 'scatter',
mode = 'markers'
)
})
}
#______________________________________________________________________________#
ui <- navbarPage(
title = 'Select values in two columns based on two inputs respectively',
fluidRow(
column(width = 12,
plotlyOutput('correlation_plot')
)
),
fluidRow(
column(width = 3,
selectInput("Petalw","PetalWidth", choices = unique(iris$Petal.Width),multiple = T),
uiOutput("PetalL")
),
column(9,
tabPanel('Table', DT::dataTableOutput('table'))
)
)
)
shinyApp(ui, server)
I am currently trying to a shiny app that outputs the summary of a simple linear regression and a graph. For both scenarios I would like the user to select the independent and dependent variables from the columns of the table and use those same inputs to run the regression and graph. Right now I can't figure out how to pass the user selected input into plotly for a display. Can anyone assist, thanks in advance!
Here is the sample data:
AvgIR SumCount AvgLTV AvgGFEE AvgRTC Date
1: 0.04106781 180029 0.753180543134717 0.002424778 319.6837 2015-10-01
2: 0.04036154 160061 0.738038310394162 0.002722529 312.6314 2015-11-01
3: 0.04001407 145560 0.739287372904644 0.002425912 313.0351 2015-12-01
4: 0.04034078 147693 0.739693214979721 0.002600640 315.0238 2016-01-01
5: 0.04055688 142545 0.734515977410642 0.002449523 310.3950 2016-02-01
6: 0.04007467 176344 0.735780463185592 0.002459228 309.9615 2016-03-01
Here is the ui:
ui <- fluidPage(
headerPanel("Regression and Time Series Analysis"),
sidebarPanel(
p("Select a Dependent Variable"),
selectInput(inputId = "DepVar", label = "Dependent Variables", multiple = FALSE, choices = names(RegData2)),
p("Select input(s) for the Independent Variable(s)"),
selectInput(inputId = "IndVar", label = "Independent Variables", multiple = FALSE, choices = list( "SumCount", "AvgIR", "AvgLTV", "AvgGFEE", "AvgRTC", "Date"), selected = "AvgLTV"),
p("Summary of Regression"),
verbatimTextOutput(outputId = "RegSum")
),
mainPanel(
verbatimTextOutput(outputId = "IndPrint"),
verbatimTextOutput(outputId = "DepPrint"),
verbatimTextOutput(outputId = "test"),
verbatimTextOutput(outputId = "xaxis"),
verbatimTextOutput(outputId = "yaxis"),
tableOutput("table"),
plotlyOutput("graph")
)
)
Here is the server:
server <- function(input, output) {
lm1 <- reactive({lm(reformulate(input$IndVar, input$DepVar), data = RegData2)})
Ind <- reactive({input$IndVar})
Dep <- reactive({input$DepVar})
plotdata <- reactive({as.data.frame(RegData2[, c(which(names(RegData2) == Ind()), which(names(RegData2) == Dep())), with = FALSE])})
xaxis <- reactive({names(RegData2)[which(names(RegData2) == Ind())]})
yaxis <- reactive({names(RegData2)[which(names(RegData2) == Dep())]})
# xaxisN <- reactive({names(xaxis())})
# yaxisN <- reactive({names(yaxis())})
output$table <- renderTable({
x<-plotdata()
#RegData2[, c(which(names(RegData2) == Ind()), which(names(RegData2) == Dep())), with = FALSE]
})
output$graph <- renderPlotly({
#xaxis <- paste(input$IndVar)
#yaxis <- paste(input$DepVar)
#THIS ONE WORKS, but isn't reactive
#plot<-plot_ly(plotdata(), x = ~AvgLTV, y = ~AvgIR, mode = "markers", type = "scatter")
#THIS ONE DOESN'T WORK, is reactive
plot<-plot_ly(plotdata(), x = ~input$IndVar, y = ~input$DepVar, mode = "markers", type = "scatter")
})
output$IndPrint <- renderPrint({str(Ind())})
output$test <- renderPrint({str(plotdata())})
output$xaxis <- renderPrint({xaxis()})
output$yaxis <- renderPrint({yaxis()})
output$DepPrint <- renderPrint({input$DepVar})
output$RegSum <- renderPrint({summary(lm1())})
}
shinyApp(ui = ui, server = server)
I think the problem is you can't use variable selectors in plotly, like the aes_string function would do for you in ggplot2 - at least the way you tried.
There may be a way to pass character names in plotly, but the docs are really not great and I could find nothing.
However I did make this work - which could be acceptable.
put the plot dataframe into a local variable df.
created two new variables xx and yy with the variables to be plotted
overrode the xaxis and yaxis labels with the layout command.
This made output$graph look like this:
output$graph <- renderPlotly({
df <- plotdata()
df$xx <- df[[input$IndVar]]
df$yy <- df[[input$DepVar]]
plot<-plot_ly(df, x = ~xx, y = ~yy, mode = "markers", type = "scatter") %>%
layout( xaxis = list( title=input$IndVar),
yaxis = list( title=input$DepVar ) )
plot
})
Yielding:
Note: Here is how I reformatted and entered the data in case someone wants a repro - took about 5 minutes:
AvgIR <- c(0.04106781,0.04036154,0.04001407,0.04034078,0.04055688,0.04007467 )
SumCount <-c(180029 ,160061 ,145560 ,147693 ,142545 ,176344 )
AvgLTV <-c(0.753180543134717 ,0.738038310394162 ,0.739287372904644 ,0.739693214979721 ,0.734515977410642 ,0.735780463185592 )
AvgGFEE<-c(0.002424778 ,0.002722529 ,0.002425912 ,0.002600640 ,0.002449523 ,0.002459228 )
AvgRTC <-c(319.6837,312.6314 ,313.0351 ,315.0238 ,310.3950 ,309.9615 )
Date <- c("2015-10-01","2015-11-01","2015-12-01","2016-01-01","2016-02-01","2016-03-01")
RegData2 <- data.frame(AvgIR=AvgIR,SumCount=SumCount,AvgLTV=AvgLTV,AvgGFEE=AvgGFEE,AvgRTC=AvgRTC,Date=Date)
RegData2$Date <- as.POSIXct(RegData2$Date)
Here's a sample code where I generate random vector and plot its histogram. In addition, there's a numericInput field that I use to clip data, i.e. to assign values lower than a threshold to that threshold. The initial value of the numericInput field is assigned based on data.
The problem is that when I press the button to generate data, the plot is evaluated twice, which I want to avoid. I emphasise this by adding sleep routine in the plotting function.
It seems to me that I'm updating the numericInput incorrectly. When I simply hard-code initial field value of that field, the issue is gone and the plot is evaluated once.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("test data clipping"),
sidebarLayout(
sidebarPanel(
actionButton('inDataGen', 'Generate dataset'),
br(),
br(),
uiOutput('resetable_input_clip'),
actionButton('inDataClipReset', 'Reset data clipping')
),
mainPanel(plotOutput("plotHist", width = "100%"))
)
))
server <- shinyServer(function(input, output) {
rValues <- reactiveValues(dataIn = NULL,
dataMin = -10e10)
# generate random dataset
userDataGen <- observeEvent(input$inDataGen, {
cat(file = stderr(), '\nuserDataGen\n')
# assign result to shared 'dataIn' variable
x <- rnorm(1000)
rValues$dataIn = x
rValues$dataMin = min(x)
})
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(rValues$dataIn))
return(NULL)
else {
# Data clipping
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
return(dm)
}
})
output$resetable_input_clip <- renderUI({
cat(file = stderr(), 'output$resetable_input_clip\n')
times <- input$inDataClipReset
div(
id = letters[(times %% length(letters)) + 1],
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
)
})
output$plotHist <- renderPlot({
cat(file = stderr(), 'plotHist \n')
if (is.null(rValues$dataIn))
return(NULL)
else {
plot(hist(userDataProc()))
Sys.sleep(2)
}
})
})
shinyApp(ui = ui, server = server)
The flow after pressing the button to generate data involves two evaluations of plotHist:
output$resetable_input_clip
plotHist
userDataGen
plotHist
userDataProc
output$resetable_input_clip
plotHist
userDataProc
SOLVED ELSWHERE
This issue has been solved on Shiny Google group. The final solution is available here and is a combination of changing observeEvent + reactiveValues to reactive(), and using freezeReactiveValue.
I believe your issue is occurring in
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(df))
return(NULL)
else {
# Data clipping
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
return(dm)
}
})
Since input$inDataClipMin is dependent on the reactive value rValues$dataMin, you end up rendering this for the initial value of rValues$dataMin, the rValues$dataMin is being reevaluated, which triggers a reevaluation of input$inDataClipMin.
If you replace this snippet with what I have below, you should get your desired behavior.
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(df))
return(NULL)
else {
# Data clipping
dm[dm < rValues$dataMin] <-
rValues$dataMin
return(dm)
}
})
As an alternative, you could put the following in your ui
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
And then use updateNumericInput to replace it's value. This would require a lot more tinkering in your current code, however, and depending on what else is happening in your app, may not be the ideal solution anyway.
Here's what I came up with. The key difference is introduction of a shared reactive variable rValues$dataClip that stores clipped data. Previously, data modification was achieved by a reactive function userDataProc. The output of that function was used for plotting which, as suggested by #Benjamin, was the culprit of double evaluation of plotting. In this version, the userDataProc is turned into observeEvent that monitors input$inDataClipMin numeric input field.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("test data clipping"),
sidebarLayout(
sidebarPanel(
actionButton('inDataGen', 'Generate dataset'),
br(),
br(),
uiOutput('resetable_input_clip'),
actionButton('inDataClipReset', 'Reset data clipping')
),
mainPanel(plotOutput("plotHist", width = "100%"))
)
))
server <- shinyServer(function(input, output, session) {
rValues <- reactiveValues(dataIn = NULL,
dataClip = NULL,
dataMin = -10e10)
# generate random dataset
userDataGen <- observeEvent(input$inDataGen, {
cat(file = stderr(), '\nuserDataGen\n')
# assign result to shared 'dataIn' variable
x <- rnorm(1000)
rValues$dataIn = x
rValues$dataMin = min(x)
})
# modify data
userDataProc <- observeEvent(input$inDataClipMin, {
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(rValues$dataIn))
rValues$dataClip = NULL
else {
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
rValues$dataClip <- dm
}
})
output$resetable_input_clip <- renderUI({
cat(file = stderr(), 'output$resetable_input_clip\n')
times <- input$inDataClipReset
div(
id = letters[(times %% length(letters)) + 1],
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
)
})
output$plotHist <- renderPlot({
cat(file = stderr(), 'plotHist \n')
if (is.null(rValues$dataClip))
return(NULL)
else {
plot(hist(rValues$dataClip))
Sys.sleep(2)
}
})
})
shinyApp(ui = ui, server = server)
Now, there's only one evaluation of plotHist after pressing the button to generate data:
output$resetable_input_clip
plotHist
userDataProc
userDataGen
output$resetable_input_clip
userDataProc
plotHist
Could anyone can tell me why I get an error when I change a dataset in first selectInput widget? When I change a dataset from diamonds to mtcars I get an error Could not find 'carat' in input$bins and in the plot just for one second and after that everything works fine. Why it happened?
library(shiny)
library(ggplot2)
data(diamonds)
data(mtcars)
ui <- fluidPage(
column(3,
selectInput("data", "", choices = c('mtcars', 'diamonds')),
uiOutput('server_cols'),
uiOutput('server_bins')
),
column(9,
plotOutput("plot")
)
)
server <- function(input, output) {
data <- reactive({
switch(input$data,
diamonds = diamonds,
mtcars = mtcars)
})
output$server_cols <- renderUI({
data <- data()
nam <- colnames(data)
selectInput('cols', "Choose numeric columns:", choices = nam[sapply(data, function(x) is.numeric(x))])
})
output$server_bins <- renderUI({
if (!is.null(input$cols)) {
df <- data()
x <- eval(input$cols)
max_value <- max(df[,x])
sliderInput('bins','Choose number of bins:', min = 0.1,
max = max_value,
value = max_value/2)
}
})
output$plot <- renderPlot({
if (!is.null(input$cols) & !is.null(input$bins)) {
basicData <- data()
var <- eval(input$cols)
ggplot(basicData, aes_string(var)) +
geom_histogram(binwidth = input$bins, color = 'white', fill = 'red')
}
})
}
shinyApp(ui, server)
Your respective output objects respond to any changes of your input variables. Thus, when you change your dataset via input$data, the plot rebuilds itself, although input$cols did not yet adjust. Actually, try inserting some print("a") inside the output$plot to see that it is called up to three times if you change input$data.
The fix is to rethink your reaction logic and let your elements respond only to specific changes, to get some kind of response "thread".
For example, input$data should only trigger output$server_cols. And output$server_bins should only be triggered by input$cols (because this already implies that input$data changed earlier). Ultimately, output$plot just has to listen to changes of input$bins (because changes in input$cols and input$data always result in changes of input$bins since it is at the end of the thread).
Here is my suggestion using isolate.
library(shiny)
library(ggplot2)
data(diamonds)
data(mtcars)
ui <- fluidPage(
column(3,
selectInput("data", "", choices = c('mtcars', 'diamonds')),
uiOutput('server_cols'),
uiOutput('server_bins')
),
column(9,
plotOutput("plot")
)
)
server <- function(input, output) {
data <- reactive({
switch(input$data, diamonds = diamonds, mtcars = mtcars)
})
output$server_cols <- renderUI({
data <- data()
nam <- colnames(data)
selectInput('cols', "Choose numeric columns:", choices = nam[sapply(data, function(x) is.numeric(x))])
})
output$server_bins <- renderUI({
if (!is.null(input$cols)) {
df <- isolate(data())
x <- eval(input$cols)
max_value <- max(df[,x])
sliderInput('bins','Choose number of bins:', min = 0.1, max = max_value, value = max_value/2)
}
})
output$plot <- renderPlot({
if (!is.null(isolate(input$cols)) & !is.null(input$bins)) {
basicData <- isolate(data())
var <- eval(isolate(input$cols))
ggplot(basicData, aes_string(var)) +
geom_histogram(binwidth = input$bins, color = 'white', fill = 'red')
}
})
}
shinyApp(ui, server)
You might also want to look into updateSelectInput and updateSliderInput if you want to alter Input Elements depending on other input.