2

Please I am trying to convert the graphs in my shiny app into ggplot2 graphs. I use a reactive function to generate the values of financial ratios, which are plotted in bar graphs and pie charts, e.g. Profit/Turnover. When you load the app, by default, each input field (profit input fields and turnover input fields) contains values, whose ratios are plotted in the graphs. The ggplot2 graph displays when the app is loaded. But, when I clear an input field, say in profit, so that I can enter a new value, I get the following error message in my console:

Warning in profit/turnover :

  longer object length is not a multiple of shorter object length

Warning: Error in data.frame: arguments imply differing number of rows: 5, 4

  131: stop

  130: data.frame

  129: <reactive:profitability.df> [C:\Users\Idiaye\Documents\R data\mavisanalytic/app.R#446]

  113: profitability.df

   99: renderPlotly [C:\Users\Idiaye\Documents\R data\mavisanalytic/app.R#451]

   98: func

   95: func

   82: origRenderFunc

   81: output$bar

    1: shiny::runApp

The app is designed to compute and plot financial ratios over just five years; hence five data input fields each for profit and turnover. Nevertheless, I want a user to be able to analyse financial performance over any number of years they choose. But, when an input field is cleared, I get the above error message. I didn't experience this problem prior to converting my plots to ggplot2. Below is my reproducible code:

library(shiny)
library(ggplot2)
library(plotly)

ui<-navbarPage(
    windowTitle="Reprex",fluid=TRUE, title=strong("Reprex"),
    tabPanel(title="Reprex App",
        sidebarLayout(
            sidebarPanel(width=2,
                actionButton("delete",strong("Clear Fields"),icon=icon("broom")),br(),br(),  
                textInput("pro","Profit 1",value="100000",width=150),
                textInput("prof","Profit 2",value="150000",width=150),
                textInput("profs","Profit 3",value="200000",width=150),
                textInput("profit","Profit 4",value="250000",width=150),
                textInput("profits","Profit 5",value="300000",width=150),
                hr(),
                h4(strong("Turnover figures:")),
                actionButton("remove",strong("Clear Fields"),icon=icon("broom")),br(),br(),
                textInput("turn","Turnover 1",value="350000",width=150),
                textInput("turno","Turnover 2",value="300000",width=150),
                textInput("turnov","Turnover 3",value="420000",width=150),
                textInput("turnover","Turnover 4",value="600000",width=150),
                textInput("turnovers","Turnover 5",value="550000",width=150),
                actionButton("go",strong("Calculate Ratio"),icon=icon("caret-right"))
                
            ),mainPanel(
                plotlyOutput("plot")
            )
        )
    )
)

server<-function(input,output,session){
    
    observeEvent(input$delete,{
        
        updateTextInput(session,"pro",value="",placeholder="0")
        updateTextInput(session,"prof",value="",placeholder="0")
        updateTextInput(session,"profs",value="",placeholder="0")
        updateTextInput(session,"profit",value="",placeholder="0")
        updateTextInput(session,"profits",value="",placeholder="0")
    })
    
    
    observeEvent(input$remove,{
        
        updateTextInput(session,"turn",value="",placeholder="0")
        updateTextInput(session,"turno",value="",placeholder="0")
        updateTextInput(session,"turnov",value="",placeholder="0")
        updateTextInput(session,"turnover",value="",placeholder="0")
        updateTextInput(session,"turnovers",value="",placeholder="0")
    })
    
    profits<-reactive({
        as.numeric(c(
            if(input$pro>0){input$pro},
            if(input$prof>0){input$prof},
            if(input$profs>0){input$profs},
            if(input$profit>0){input$profit},
            if(input$profits>0){input$profits}
        ))
    })
    
    turnover<-reactive({
        as.numeric(c(
            if(input$turn>0){input$turn},
            if(input$turno>0){input$turno},
            if(input$turnov>0){input$turnov},
            if(input$turnover>0){input$turnover},
            if(input$turnovers>0){input$turnovers}
        ))
    })
    
    year<-reactive({
        c(
            if(input$pro>0){"Year 1"},
            if(input$prof>0){"Year 2"},
            if(input$profs>0){"Year 3"},
            if(input$profit>0){"Year 4"},
            if(input$profits>0){"Year 5"}
        )
    })
    profit.ratio<-function(profits,turnover){
        profitability<-round(profits/turnover*100,1)
        return(profitability)
    }
  profitability<-reactive({profit.ratio(profits(),turnover())}) 
  
  
  
  profit.df<-reactive({data.frame(profitability(),year())})
  
  output$plot<-renderPlotly({
      input$go
      isolate(profit.df()%>%
          ggplot()+aes(x=year(),y=profitability(),fill=year())+geom_bar(stat="identity")+
          labs(x="Years",y="Profitability ratios (%)",fill="Years")+scale_fill_brewer(palette="Set1")+
              geom_text(label=profitability(),position=position_stack(vjust=0.8)))
  })
}
shinyApp(ui=ui,server=server)

Thanks in anticipation of your suggestions.

2 Answers 2

2

You can use req to make sure that all input fulfill the conditions needed for further processing.
This will avoid the warning message in the console.

Try:

 profits<-reactive({
    req(input$pro>0,
        input$prof>0,
        input$profs>0,
        input$profit>0,
        input$profits>0)
    
    as.numeric(c(input$pro,
                 input$prof,
                 input$profs,
                 input$profit,
                 input$profits
    ))
})

Another option is to use validate in order to display an useful information message as long as the conditions aren't met:

  profits<-reactive({
    validate(need(input$pro>0 &
                  input$prof>0 &
                  input$profs>0 &
                  input$profit>0 &
                  input$profits>0,'Please fill all coefs'))
    
    as.numeric(c(input$pro,
                 input$prof,
                 input$profs,
                 input$profit,
                 input$profits
    ))
  })

enter image description here

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, but what I am trying to achieve is for a user to be able to clear any field(s) they want and enter their own data for analysis with the default graph still in place in the main panel until the user hits the "Calculate Ratio" button to generate a new graph. Your suggestion about req() did eliminate the error message but it didn't run a new analysis unless all five input fields are filled. What do you suggest?
I would use an eventReactive(input$go,...) to render the plot. but I don't have time right now to look further into this.
0

You were not handling the missing values when you cleared the profits or turnover. I created a new reactive dataframe where it is easier to check if all fields are missing for profit or turnover. Try this code.

ui<-navbarPage(
  windowTitle="Reprex",fluid=TRUE, title=strong("Reprex"),
  tabPanel(title="Reprex App",
           sidebarLayout(
             sidebarPanel(width=2,
                          actionButton("delete",strong("Clear Fields"),icon=icon("broom")),br(),br(),  
                          textInput("pro","Profit 1",value="100000",width=150),
                          textInput("prof","Profit 2",value="150000",width=150),
                          textInput("profs","Profit 3",value="200000",width=150),
                          textInput("profit","Profit 4",value="250000",width=150),
                          textInput("profits","Profit 5",value="300000",width=150),
                          hr(),
                          h4(strong("Turnover figures:")),
                          actionButton("remove",strong("Clear Fields"),icon=icon("broom")),br(),br(),
                          textInput("turn","Turnover 1",value="350000",width=150),
                          textInput("turno","Turnover 2",value="300000",width=150),
                          textInput("turnov","Turnover 3",value="420000",width=150),
                          textInput("turnover","Turnover 4",value="600000",width=150),
                          textInput("turnovers","Turnover 5",value="550000",width=150),
                          actionButton("go",strong("Calculate Ratio"),icon=icon("caret-right"))
                          
             ),mainPanel(
               plotlyOutput("plot")
             )
           )
  )
)

server<-function(input,output,session){
  
  DF1 <- reactiveValues(data=NULL)
  
  observeEvent(input$delete,{
    
    updateTextInput(session,"pro",value="",placeholder="0")
    updateTextInput(session,"prof",value="",placeholder="0")
    updateTextInput(session,"profs",value="",placeholder="0")
    updateTextInput(session,"profit",value="",placeholder="0")
    updateTextInput(session,"profits",value="",placeholder="0")
  })
  
  observeEvent(input$remove,{
    
    updateTextInput(session,"turn",value="",placeholder="0")
    updateTextInput(session,"turno",value="",placeholder="0")
    updateTextInput(session,"turnov",value="",placeholder="0")
    updateTextInput(session,"turnover",value="",placeholder="0")
    updateTextInput(session,"turnovers",value="",placeholder="0")
  })

  
  findata <- reactive({
    years <- c("Year 1", "Year 2", "Year 3", "Year 4", "Year 5")
    profits <- c(input$pro, input$prof, input$profs, input$profit, input$profits)
    turnovers <- c(input$turn,input$turno,input$turnov,input$turnover,input$turnovers)
    fdata <- data.frame(profitability=c(),year=c())
    chkp <- sum(is.na(as.numeric(profits)))
    chkt <- sum(is.na(as.numeric(turnovers)))
    if (chkp==5 | chkt==5) {
      fdata <- NULL
    }else{
      lapply(1:5, function(i){
        if (!is.null(profits[i]) & !is.null(turnovers[i]) ) {
          if (profits[i]>0 & turnovers[i]>0) {
            pfy <- round(as.numeric(profits[i])/as.numeric(turnovers[i])*100,1)
            fdata <<- rbind(fdata,data.frame(profitability=pfy,year=years[i]))
          }
          
        }
        
      })
    }
    
    fdata
  })
  
  observe({DF1$data <- findata()})
  
  #observeEvent(input$go, {
    output$plot<-renderPlotly({
      input$go
     
      df <- DF1$data
      
      chkpfy <- sum(is.na(as.numeric(df$profitability)))
      if (is.null(df)) return(NULL)
      if (chkpfy==5) { 
        return(NULL)
      }else{
        p <-  ggplot(data=df, aes(x=year, y=profitability, fill=year )) +
          geom_bar(stat="identity") +
          labs(x="Years",y="Profitability ratios (%)",fill="Years") +
          scale_fill_brewer(palette="Set1") +
          geom_text(label=df$profitability, position=position_stack(vjust=0.8))
                        
        ggplotly(p)
      }
      
    })
  #})

}
shinyApp(ui=ui,server=server)

output

enter image description here

2 Comments

Hi, thanks but your code above wouldn't run. Is there any other way to make ggplot2 take input values? Validat() worked to eliminate the error message, but it wouldn't run a new analysis unless all five input fields are filled.
You might be missing some library. The reactive dataframe findata is created with only rows which have both profit and turnover. Are you sure you are not able to get the above output?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.