shinyreact-leafletshiny-reactivityr-leafletselectinput

How to make a reactive Leaflet with a selectInput in Shiny?


I am trying to create a map using leaflet in which interacting with a selectInput modifies the data loaded in the map. I think I'm doing the filtering right and using the reactive variables but I keep getting this error:

Error in colnames(sum_crimes()) <- c("area_numbe", "total_count") : 
  invalid (NULL) left side of assignment

It seems that 'filtered_crimes' or 'sum_crimes' are always empty and I can't understand why. I would be grateful if you could help me.

This is the code of the application:

library(shiny)
library(leaflet)
library(leaflet.extras)
library(geojsonio)
library(dplyr)
library(shinycssloaders)

# Carga el archivo Chicago_Crimes_2015-2021.csv
crimes <- read.csv("Chicago_Crimes_2015-2021.csv")

# Load Polygons
community <- geojson_read("CommAreas.geojson", what = "sp")
community$Bold <- paste0('<strong>', community$community, '</strong>') %>% 
  lapply(htmltools::HTML)

ui <- fluidPage(
  title = "Chicago Map",
  headerPanel(h4("Mapa de crímenes en Chicago")),
  sidebarLayout(
    sidebarPanel(
      selectInput("crime_type", "Crime Type:", c("All", unique(crimes$Primary.Type)), selected = "All")
      #style = "overflow-y:scroll; max-height: 70vh; position:relative;"
    ),
    mainPanel(withSpinner(leafletOutput("map", height = "90vh")))
    
  )
)

server <- function(input, output) {
  
  filtered_crimes <- reactive({
    if (input$crime_type == "All") {
      crimes
    } else {
      crimes %>% filter(Primary.Type == input$crime_type)
    }
  })
  
  sum_crimes <- reactive({
    filtered_crimes() %>% group_by(Community.Area) %>% 
    summarise(total_count=n(),
              .groups = 'drop')
  })
  
  colnames(sum_crimes()) <- c("area_numbe", "total_count")
  
  max_crimes <- max(sum_crimes()$total_count)
  min_crimes <- min(sum_crimes()$total_count)
  
  # Creating a sequential color map 
  pal <- colorNumeric("YlOrBr", sum_crimes()$total_count)
  
  output$map <- renderLeaflet({
    leaflet(options = leafletOptions(minZoom = 10, preferCanvas = TRUE)) %>% 
      addProviderTiles("CartoDB.DarkMatter", group = "Dark Theme") %>%
      addProviderTiles("CartoDB.Positron", group = "Light Theme") %>%
      setView(lng = -87.654231, lat = 41.877562, zoom = 5) %>%
      setMaxBounds(lng1 = -87.455032, lat1 = 41.6, lng2 = -87.955673, lat2 = 42.040927) %>%
      addPolygons(data = community, fillOpacity = 0.8, color = "#0e80c2", fillColor = ~pal(sum_crimes()[area_numbe,]$total_count), 
                  label = paste0('<strong>', community$community, '</strong><br/>Num. crimes: ', sum_crimes()[community$area_numbe,]$total_count) %>% 
                    lapply(htmltools::HTML), 
                  highlightOptions = highlightOptions(color = "#A9A9A9", opacity = 1, weight = 8, stroke = 8), 
                  labelOptions = labelOptions(textsize = "13px"))%>%
      
      # creating a legend
      addLegend(data = sum_crimes, pal = pal, values = ~total_count, opacity = 1.0, title = "Number of crimes")
  })
}

shinyApp(ui, server)

I tried with an if-else to initialise the data with a defined value in case it is empty at the beginning but it didn't work either.

This is the structure of crimes. Since it has a large amount of data, it only paints the equivalent of head(crimes):

structure(list(X = 0:5, ID = c(10224738L, 10224739L, 11646166L, 
10224740L, 10224741L, 10224742L), Case.Number = c("HY411648", 
"HY411615", "JC213529", "HY411595", "HY411610", "HY411435"), 
    Date = c("09/05/2015 01:30:00 PM", "09/04/2015 11:30:00 AM", 
    "09/01/2018 12:01:00 AM", "09/05/2015 12:45:00 PM", "09/05/2015 01:00:00 PM", 
    "09/05/2015 10:55:00 AM"), Block = c("043XX S WOOD ST", "008XX N CENTRAL AVE", 
    "082XX S INGLESIDE AVE", "035XX W BARRY AVE", "0000X N LARAMIE AVE", 
    "082XX S LOOMIS BLVD"), IUCR = c("0486", "0870", "0810", 
    "2023", "0560", "0610"), Primary.Type = c("BATTERY", "THEFT", 
    "THEFT", "NARCOTICS", "ASSAULT", "BURGLARY"), Description = c("DOMESTIC BATTERY SIMPLE", 
    "POCKET-PICKING", "OVER $500", "POSS: HEROIN(BRN/TAN)", "SIMPLE", 
    "FORCIBLE ENTRY"), Location.Description = c("RESIDENCE", 
    "CTA BUS", "RESIDENCE", "SIDEWALK", "APARTMENT", "RESIDENCE"
    ), Arrest = c("False", "False", "False", "True", "False", 
    "False"), Domestic = c("True", "False", "True", "False", 
    "True", "False"), Beat = c(924L, 1511L, 631L, 1412L, 1522L, 
    614L), District = c(9, 15, 6, 14, 15, 6), Ward = c(12, 29, 
    8, 35, 28, 21), Community.Area = c(61, 25, 44, 21, 25, 71
    ), FBI.Code = c("08B", "06", "06", "18", "08A", "05"), X.Coordinate = c(1165074, 
    1138875, NA, 1152037, 1141706, 1168430), Y.Coordinate = c(1875917, 
    1904869, NA, 1920384, 1900086, 1850165), Year = c(2015L, 
    2015L, 2018L, 2015L, 2015L, 2015L), Updated.On = c("02/10/2018 03:50:01 PM", 
    "02/10/2018 03:50:01 PM", "04/06/2019 04:04:43 PM", "02/10/2018 03:50:01 PM", 
    "02/10/2018 03:50:01 PM", "02/10/2018 03:50:01 PM"), Latitude = c(41.815117282, 
    41.895080471, NA, 41.937405765, 41.881903443, 41.744378879
    ), Longitude = c(-87.669999562, -87.765400451, NA, -87.716649687, 
    -87.755121152, -87.658430635), Location = c("(41.815117282, -87.669999562)", 
    "(41.895080471, -87.765400451)", "", "(41.937405765, -87.716649687)", 
    "(41.881903443, -87.755121152)", "(41.744378879, -87.658430635)"
    )), row.names = c(NA, 6L), class = "data.frame")

And this about community:

{
  "type": "FeatureCollection",
  "features": [
    {"type":"Feature","properties":{"community":"DOUGLAS","area":"0","shape_area":"46004621.1581","perimeter":"0","area_num_1":"35","area_numbe":"35","comarea_id":"0","comarea":"0","shape_len":"31027.0545098"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-87.60914087617894,41.84469250265398],[-87.60914874757808,41.84466159842403],[-87.6091611204126,41.84458961193954],

area_numbe and Community.Area is the column shared by both datasets. Previously I tried to merge sum_crimes and community but when I did it community was no longer a spatial dataframe and addPolygons failed.


Solution

  • Without an extract of your data, it is difficult to make sure where the errors are but I think it is caused by altering a reactive outside of a reactive environment.

    Instead of

    sum_crimes <- reactive({
        filtered_crimes() %>% group_by(Community.Area) %>% 
        summarise(total_count=n(),
                  .groups = 'drop')
      })
      
     colnames(sum_crimes()) <- c("area_numbe", "total_count")
    

    You should do

      sum_crimes <- reactive({
        filtered_crimes() %>% group_by(Community.Area) %>% 
          summarise(total_count=n(),
                    .groups = 'drop') %>%
          rename(area_numbe = Community.Area)
      })
    # delete the line colnames(sum_crimes()) <- ...
    

    Same goes for

      max_crimes <- max(sum_crimes()$total_count)
      min_crimes <- min(sum_crimes()$total_count)
    

    Those 2 lines should go in a reactive environment if you want to use max_crimes and min_crimes later. You could do something like this :

    # initialize reactiveValues
    rv <- reactiveValues(max_crimes = NA, min_crimes = NA)
    
    observeEvent(sum_crimes(), {
    rv$max_crimes <- max(sum_crimes()$total_count)
    rv$min_crimes <- min(sum_crimes()$total_count)
    }) 
    

    And then use rv$max_crimes and rv$min_crimes later in your code when needed.

    But once again, without data I cannot run your code so this is maybe not accurate to your needs.

    To add your data to your post, you can do dput(crimes) in R console and copy paste the result in your original post.

    EDIT : same goes for your line pal <- colorNumeric("YlOrBr", sum_crimes()$total_count), you should put this line at the beginning of the renderLeaflet for example