I have an application where the user selects the stocks she want's to analyze. Depending on the number of stocks chosen, the app will render equal amounts of UIOuputs where the user can choose the weight for each stock. So for example, if you choose 6 stocks to analyze, 6 uioutputs will render each asking to select a weight.
The problem I am having is, I would like to create a data frame with the inputs. So if a user selects AAPl and MSFT with weights .50 and .50. I would like to create a df:
Ticker Weight
AAPL .50
MSFT .50
However, when I try and create the dataframe I get an error inputs are not of the same length. I believe this is because of how shiny reactivity works (not ordered). Any inputs would be greatly appreciated. Below is the app.
library(shiny)
library(purrr)
library(tidyverse)
library(DT)
tickers = c("SPY", "IWM", "QQQ", "TLT", "AGG", "GLD", "SLV")
ui <- fluidPage(
# Application title
titlePanel("Portfolio Builder"),
#select the stocks you want to analyze
mainPanel(
selectizeInput("mult", "chose stock", choices = tickers, selected = "SPY", multiple = T),
uiOutput("plo"),
dataTableOutput("dataTab")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$plo = renderUI({
z = length(input$mult)
name = input$mult
map2(seq(z), name, ~ numericInput(inputId = paste0("hey",.x), label = paste("weight", .y), value = 10))
})
weights = reactiveValues()
observe({weights$current = map(seq(length(input$mult)),~input[[paste0("hey",.x)]]) %>% unlist()})
mat = reactive({
#if(length(weights$current) == length(input$mult)){
df = data.frame(ticker = input$mult, weight = weights$current) %>% mutate(weightPct = weights$current/sum(weights$current))
# }else{NULL}
})
output$dataTab = renderDataTable({
mat()
})
observe(print(weights$current))
observe(print(input$mult))
}
I converted your observe() and reactiveValues() to a single reactive() object. This way it reacts to changes without the complexity you had. The other big difference is that I converted the weights object to a list, but I think it should still be easy to follow. The data frame error persisted as the length of the user inputs and weights momentarily mismatch, so I returned the length check you already had:
library(shiny)
library(purrr)
library(tidyverse)
library(DT)
tickers = c("SPY", "IWM", "QQQ", "TLT", "AGG", "GLD", "SLV")
suppressWarnings()
ui <- fluidPage(
# Application title
titlePanel("Portfolio Builder"),
#select the stocks you want to analyze
mainPanel(
selectizeInput("mult", "chose stock", choices = tickers, selected = "SPY", multiple = T),
uiOutput("plo"),
dataTableOutput("dataTab")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$plo = renderUI({
z = length(input$mult)
name = input$mult
map2(seq(z), name, ~ numericInput(inputId = paste0("hey",.x), label = paste("weight", .y), value = 10))
})
weights = reactive({
req(input$mult)
list(current = map(seq(length(input$mult)),~input[[paste0("hey",.x)]]) %>% unlist())
})
mat = reactive({
req(weights()$current)
if(length(weights()$current) == length(input$mult)){
df = data.frame(ticker = input$mult, weight = weights()$current) %>% mutate(weightPct = weights()$current/sum(weights()$current))
}
})
output$dataTab = renderDataTable({
req(mat())
mat()
})
}
shinyApp(ui, server)
Related
this is my first time asking on stack overflow so sorry for mistakes.
I am making a project where I should create a Shiny app in R. The app should download the data from a certain domain and after that there are several things it should allow the user to do:
download the latest data from the EUROSTAT website;
select the set of countries whose data will be presented;
select the years of data to be presented;
selection of genders for which data will be presented;
presentation of selected data in tabular form in the format:
COUNTRY; TRIBE; WEEK; NUMBER;
aggregation of data on the map of EUROPE;
total for the indicated period, for the indicated genders, within the country;
visualization of selected data in the form of time series;
one time series for selected genders, for each country separately;
Right now I am focusing on the first 5 points. I know how to get the data downloaded and how to filter and prepare it properly but I think I am doing something wrong when it comes to the Shiny environment, because when I run the app it opens up but the table created has no data inside of it. I am having problems understading shiny since this is my first time doing anything in it. Any help is welcome.
I did 2 codes and tried to run them but as I said the outcome was and empty table no matter what year or week I chose.
This is how my server.R file looks:
library(ggplot2)
library(shiny)
library(dplyr)
library(data.table)
library(googleVis)
shinyServer(function(input, output) {
outVar <- reactiveValues(
selectYearVar = "2021"
)
outVar1 <- reactiveValues(
selectWeekVar = "1"
)
outVar2 <- reactiveValues(
selectSexVar = "f"
)
outVar3 <- reactiveValues(
selectCountryVar = "PL"
)
observeEvent(input$selectCountry,{
outVar3$selectCountryVar <- input$selectCountry
})
observeEvent(input$selectSex,{
outVar2$selectSexVar <- input$selectSex
})
observeEvent(input$selectYear,{
outVar$selectYearVar <- input$selectYear
})
observeEvent(input$selectWeek,{
outVar1$selectWeekVar <- input$selectWeek
})
dataIn <- reactive({
try({
options(width=250)
rm(list=ls())
dataDir <- getwd()#file.path(getwd(),"data")
download.file(url="https://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing?file=data/demo_r_mwk_ts.tsv.gz",
destfile=file.path(dataDir,"demo_r_mwk_ts.tsv.gz"),method="curl")
d <- read.table(file=file.path(dataDir,"demo_r_mwk_ts.tsv.gz"),sep="\t",dec=".",header=T)
x <- as.data.frame(rbindlist(lapply(c("AD","AL","AM","AT","BE","BG","CH","CY","CZ","DE","DK","EE","EL","ES","FI","FR","GE","HR","HU","IE","IS","IT","LI","LT",
"LU","LV","ME","MT","NL","NO","PL","PT","RO","RS","SE","SI","SK","UK"),function(country){
x <- t(d[grep(country,d[,1]),])
x <- x[-1,]
options(warn=-1)
x <- data.frame(
week = gsub("X","",rownames(x)),
f = as.integer(gsub(" p","",x[,1])),
m = as.integer(gsub(" p","",x[,2])),
t = as.integer(gsub(" p","",x[,3])),
c = country
)
options(warn=0)
rownames(x) <- NULL
x <- x[order(x$week),]
return(x)
})))
rownames(x) <- NULL
x[, "year"] <- as.integer(substr(x[, "week"], 0, 4))
x[, "week"] <- as.integer(substr(x[, "week"], 6, 7))
x <- x[week, year, c, outVar2$selectSexVar]
x <- x[(as.character(x$year)==as.character(outVar$selectYearVar)) & (as.character(x$week)==as.character(outVar1$selectWeekVar)) & (as.character(x$c)==as.character(outVar3$selectCountryVar)),]
return(x)
},silent=T)
return(data.frame())
})
output$dataSample <- DT::renderDataTable({
DT::datatable(
dataIn(),
rownames = FALSE,
options = list(
scrollX = TRUE,
pageLength = 16,
lengthMenu = seq(from=2,by=2,to=16)
)
)
})
})
and here is the ui.R file
library(shiny)
library(data.table)
library(googleVis)
shinyUI(fluidPage(
titlePanel("PiWD/shiny/sgh/umieralnosc"),
sidebarLayout(
sidebarPanel(
selectInput("selectYear",
label = "Rok danych",
choices = as.vector(as.character(2023:2000),mode="list")
),
selectInput("selectWeek",
label = "Tydzień danych",
choices = as.vector(as.character(53:1),mode="list")
),
selectInput("selectSex",
label = "Płeć",
choices = as.vector(as.character(c("f","m","t")),mode="list")
),
selectInput("selectCountry",
label = "Rok danych",
choices = as.vector(as.character(c("AD","AL","AM","AT","BE","BG","CH","CY","CZ","DE","DK","EE","EL","ES","FI","FR","GE","HR","HU","IE","IS","IT","LI","LT",
"LU","LV","ME","MT","NL","NO","PL","PT","RO","RS","SE","SI","SK","UK")),mode="list")
)
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Moja tabela", DT::dataTableOutput("dataSample")),
)
)
)
))
I have a table, in which the user will give as input some groups. As a result, I want another column to automatically update and show the frequency (or replicate) of each group:
This code creates this app:
library(shiny)
library(rhandsontable)
library(tidyverse)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Automatic data rhandsontable"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
),
# Show a plot of the generated distribution
mainPanel(
rhandsontable::rHandsontableOutput('ed_out'),
shiny::actionButton('start_input', 'save final table')
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
# This has to be reactive
data <- reactive({
df <- data.frame(Animal = c('Dog', 'Cat', 'Mouse', 'Elephant', 'Tiger'),
Group = ' ',
replicate = as.numeric(' '))
})
output$ed_out <- rhandsontable::renderRHandsontable({
df <- data()
rhandsontable(
df,
height = 500,
width = 600) %>%
hot_col('replicate', format = '0a', readOnly = TRUE) %>%
hot_col('Animal', readOnly = TRUE)
})
# This is just to save the table when the user has finished, can be ignored
group_finals <- reactiveValues()
observeEvent(input$start_input, {
group_finals$data <- rhandsontable::hot_to_r(input$ed_out)
print(group_finals$data)
}
)
}
# Run the application
shinyApp(ui = ui, server = server)
So the idea is that the user, inputs the groups and the replicate is automatically updated: (here the user gives as input B, B, A, A, B.
I am able to count the replicates of each group, but I'm not sure how where to implement this part to calculate them and display them at the same time after the user inputs each group.
df <- df %>%
group_by(Group) %>%
mutate(replicate = 1:n())
Not sure if this is the best approach, I tried a bit with the hot_to_col renderer to use javascript but I'm unfamiliar with that language.
Sorry but I'm not familiar with the tidyverse - so I switched to data.table.
hot_to_r is the right way to go:
library(shiny)
library(rhandsontable)
library(data.table)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Automatic data rhandsontable"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
),
# Show a plot of the generated distribution
mainPanel(
rhandsontable::rHandsontableOutput('ed_out'),
shiny::actionButton('start_input', 'save final table')
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
# This has to be reactive
data <- reactive({
data.frame(Animal = c('Dog', 'Cat', 'Mouse', 'Elephant', 'Tiger'),
Group = '',
replicate = NA_integer_)
})
myData <- reactiveVal()
observeEvent(data(),{
myData(data())
})
output$ed_out <- rhandsontable::renderRHandsontable({
rhandsontable(
myData(),
height = 500,
width = 600) %>%
hot_col('replicate', format = '0a', readOnly = TRUE) %>%
hot_col('Animal', readOnly = TRUE)
})
observeEvent(input$ed_out, {
userDT <- rhandsontable::hot_to_r(input$ed_out)
setDT(userDT)
userDT[, replicate := seq_len(.N), by = Group][is.na(Group) | Group == "", replicate := NA_integer_]
myData(userDT)
})
# This is just to save the table when the user has finished, can be ignored
group_finals <- reactiveValues()
observeEvent(input$start_input, {
group_finals$myData <- rhandsontable::hot_to_r(input$ed_out)
print(group_finals$myData)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I am having two columns in my data frame, one is "all_pass" which contains numeric values and other is "st_name" which contains string values name of states
The requirement of the plot is , when user give input of the state it will show the plot of that particular state which contains all_pass numbers
Following is the code in which I am trying to plot, where the user will input the name of the state and as per the input of the state name, the graph will plot as per the all_pass as per the related pass scores to that particular state. Kindly help in the following code, will be of great help.
Code is as mentioned below :
library(ggplot2)
library(plotly)
library(dplyr)
library(shiny)
ui <- basicPage(
h1("Total bills passed by state delegation , 110th Congress"),
selectizeInput(inputId = "bins",label = "Choose State",
choices = list("AK","AL","AR","AS","AZ","CA","CO","CT","DC","DE","FL","GA","GU","HI","IA","ID","IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","NE","ND","NH","NJ","NM","NV","NY","OH","OK","OR","PA","PR","RI","SC","SD","TN","TX","UT","VA") ,multiple = TRUE ,plotOutput("plot"))
)
server <- function(input, output) {
data <- reactive({
require(input$bins)
df <- df7 %>% filter(st_name %in% input$bins)
})
output$plot <- renderPlot({
ggplot(df(), aes(y= all_pass,x=st_name ))+geom_bar(stat = "sum")
})
}
shinyApp(ui = ui, server = server)
in the UI definition you have plotOutput("plot") as an argument to selectizeInput() instead of basicPage(). Reformatting your code (Ctrl+Shift+A) would have made that more visible.
You can simplify the server code, as the renderPlot() already creates a reactive dependence on input$bins.
You can use the object datasets::state.abb to get a vector of US state abbreviations. This is loaded automatically in every R session.
Please see some working code below. I am using some mock data for df as you did not provide any data in your question.
library(ggplot2)
library(plotly)
library(dplyr)
library(shiny)
ui <- basicPage(
h1("Total bills passed by state delegation, 110th Congress"),
selectizeInput(inputId = "bins",
label = "Choose State",
choices = state.abb,
multiple = TRUE),
plotOutput("plot")
)
server <- function(input, output) {
df <-
tibble(all_pass = sample(1:500, 350),
st_name = rep(state.abb, 7))
output$plot <- renderPlot({
req(input$bins)
df |>
filter(st_name %in% input$bins) |>
ggplot(aes(y = all_pass,x=st_name )) +
geom_bar(stat = "sum")
})
}
shinyApp(ui = ui, server = server)
I have a shiny app where I want the user to be able to select which variables to keep in the final data frame and then also select which variables to scale into a percent. I have this working, but I am running into a little puzzle. The problem is if the user decides they want to add an additional variable (or remove one), they have to redo the scaling. This could be a problem if my users have many columns they are working on. How can I keep the scaling work the user has already done, while allowing for the addition or removal of variables from the final data frame?
library(shiny)
library(tidyverse)
library(DT)
# Define UI
ui <- fluidPage(
checkboxGroupInput("select_var", label = "Select Variables"),
selectInput("scalescore", label = NULL, choices = c("")),
actionButton("scale", "Scale Scores"),
DT::dataTableOutput("table")
)
# Define server
server <- function(session, input, output) {
# define the reactive values
values <- reactiveValues(df_final = NULL)
# dynamically generate the variable names
observe({
vchoices <- names(mtcars)
updateCheckboxGroupInput(session, "select_var", choices = vchoices)
})
# dynamically generate the variables to scale
observe({
vchoices <- names(values$df_final)
updateSelectInput(session, "scalescore", choices = vchoices)
})
# select the variables based on checkbox
observe({
req(input$select_var)
df_sel <- mtcars %>% select(input$select_var)
values$df_final <- df_sel
})
observeEvent(input$scale, {
name <- rlang::sym(paste0(input$scalescore, "_scaled"))
values$df_final <- values$df_final %>% mutate(!!name := round(!!rlang::sym(input$scalescore)/max(!!rlang::sym(input$scalescore), na.rm = TRUE)*100, 1))})
output$table <- DT::renderDataTable(values$df_final)
}
# Run the application
shinyApp(ui = ui, server = server)
We will need to maintain a vector which tracks whether a variable was scaled or not. Here is how it's done,
library(shiny)
library(tidyverse)
library(DT)
# Define UI
ui <- fluidPage(
checkboxGroupInput("select_var", label = "Select Variables"),
selectInput("scalescore", label = NULL, choices = c("")),
actionButton("scale", "Scale Scores"),
DT::dataTableOutput("table")
)
server = function(input,output,session){
#Column names are static
names = colnames(mtcars)
# data scructure to store if the variable is scaled
is_scaled = logical(length(names))
names(is_scaled) = names #Set the names of the logical vector to the column names
#Update the checkbox with the column names of the dataframe
observe({
updateCheckboxGroupInput(session, "select_var", choices = names)
})
# Update the list of choices but dont include the scaled vaiables
observe({
vchoices <- names(data())
vchoices = vchoices[vchoices %in% names]
updateSelectInput(session, "scalescore", choices = vchoices)
})
#When the scle button is pressed, the vector which contains the list of scaled variables is updated
observeEvent(input$scale,{
if(is_scaled[[input$scalescore]]){
is_scaled[[input$scalescore]] <<- FALSE
}else{
is_scaled[[input$scalescore]] <<- TRUE
}
})
#Function to scale the variables
scale = function(x){
return(round(x/max(x,na.rm = T)*100,1))
}
data = reactive({
req(input$select_var)
input$scale #simply to induce reactivity
#Select the respective columns
df = mtcars%>%
select(input$select_var)
if(any(is_scaled[input$select_var])){
temp_vec = is_scaled[input$select_var] #Get a list of variables selected
true_vec = temp_vec[which(temp_vec)] #Check which ones are scaled
true_vec_names = names(true_vec) #Get the names of the variables scales
#Scale the variables respectively
df = df%>%
mutate_at(.vars = true_vec_names,.funs = funs(scaled = scale(.)))
}
return(df)
})
output$table = DT::renderDataTable(data())
}
# Run the application
shinyApp(ui = ui, server = server)
is_scaled tracks whether a particular column is scaled or not. When it is later selected, it is scaled if the value is TRUE in this vector.
Additional functionality is also added where if the scale button is pressed twice the scale column is removed.
I am running into trouble using the tapply function. I am pulling two vectors from the same data frame which was created from a reactive variable. The first I am calling from a user inputted selection, and the second is one that I have created to keep my code generalisable and to use in my sort function. My sample code is shown below using the r-bloggers example. The data is here.
https://redirect.viglink.com/?format=go&jsonp=vglnk_150821851345614&key=949efb41171ac6ec1bf7f206d57e90b8&libId=j8v6cnh201021u9s000DAhzunvtas&loc=https%3A%2F%2Fwww.r-bloggers.com%2Fbuilding-shiny-apps-an-interactive-tutorial%2F&v=1&out=http%3A%2F%2Fdeanattali.com%2Ffiles%2Fbcl-data.csv&ref=https%3A%2F%2Fduckduckgo.com%2F&title=Building%20Shiny%20apps%20%E2%80%93%20an%20interactive%20tutorial%20%7C%20R-bloggers&txt=here
The error it throws is that they are not the same length, even though their attribute and class print outs are exactly the same.
I know that this is not the best code in the world, but I just threw together a quick example.
library(shiny)
library(tidyverse)
bcl <- read.csv("bcl-data.csv", stringsAsFactors = FALSE)
ui <- fluidPage(titlePanel("Sampling Strategies"),
sidebarLayout(
sidebarPanel(
selectInput("XDATA","xdata",
choices = c(names(bcl))),
selectInput("YDATA","ydata",
choices = c(names(bcl)))
),
mainPanel(
tabsetPanel(
tabPanel("The table",tableOutput("mytable"))
))
))
server <- function(input, output, session) {
filtered <- reactive({
bcl <- bcl %>% mutate(ID = 1:nrow(bcl))
})
output$mytable <- renderTable({
dataset <- filtered() %>% mutate(sampled = "white")
sample.rows <- sample(dataset$ID, 5, replace = FALSE)
dataset$sampled[sample.rows] <- "black"
final <- tapply(dataset[input$XDATA], list(dataset$sampled),mean)[["black"]]
return(final)
})
}
shinyApp(ui = ui, server = server)
Cheers
Edit* Sorry my bad, forgot to change over the drop list codes. All I am interested is one generic xdata vector that can be selected from the loaded data set. I then sample it, and want to find the mean value from the sampled indices.
One of the problems is in the subsetting. the [ still returns a data.frame. So, we need [[. If we look at ?tapply
tapply(X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
where
X is an atomic object, typically a vector
ui <- fluidPage(titlePanel("Sampling Strategies"),
sidebarLayout(
sidebarPanel(
selectInput("XDATA","xdata",
choices = c(names(bcl)[5:7])),
selectInput("YDATA","ydata",
choices = c(names(bcl)))
),
mainPanel(
tabsetPanel(
tabPanel("The table",tableOutput("mytable"))
))
))
server <- function(input, output, session) {
filtered <- reactive({
bcl <- bcl %>% mutate(ID = row_number())
})
output$mytable <- renderTable({
dataset <- filtered() %>% mutate(sampled = "white")
sample.rows <- sample(dataset$ID, 20, replace = FALSE)
dataset$sampled[sample.rows] <- "black"
final <- tapply(dataset[[input$XDATA]], list(dataset$sampled),mean, na.rm = TRUE, simplify = TRUE)
return(final)
})
}
shinyApp(ui = ui, server = server)
-output