Integrando en una aplicación

Introducción

Ya tenemos todos los elementos que necesitamos, en las secciones Serie de Tiempo, Diagrama de dispersión y Mapa desarrollamos cada elemento de la aplicación, ahora lo que tenemos que hacer es integrar todo en un flujo interactivo.

Como vimos antes, las aplicaciones de Shiny estan compuestas de dos piezas: la interfaz de usuario y el servidor. La interfaz de usuario en nuestro caso es relativamente sencilla, contiene cuatro elementos fundamentales: el selector de delito y las salidas de los tres componentes.

En una aplicación Web normal, la estructura de la interfaz de usuario se define utilizando una combinación de html y css. Shiny nos provee una seri de funciones de R que generan los componentes Web que necesitamos. Por ejemplo, digamos que queremos hacer un radio button utilizando Shiny:

radioButtons("est", "Estación:",
    c("Radio Universal" = "ru",
      "La pantera" = "590")
)

La función radioButtons nos regresa el código de html para que en la página aparezcan nuestros botones.

Regresando a nuestra apliacación, generar un selector para los diferentes tipos de delitos de la base de datos es muy fácil:

 selectInput("id_delito","Selecciona un delito",
            c("Robo a casa habitación",
               "Robo a negocio",                   
               "Robo a transeúnte",                
               "Robo a transportista",
               "Robo en transporte público colectivo",
               "Robo en transporte público individual",
               "Robo de vehículo automotor",
               "Extorsión",                         
               "Feminicidio",                   
               "Homicidio doloso",
               "Secuestro")
    )

Servidor

Lo que nos falta ahora es la lógica del servidor, es decir, las funciones que se van a encargar de tomar el input del usuario y regresar las gráficas. En la Sección Mi primera aplicación vimos cómo hacer funciones que regresen elementos visuales para acomodarlos donde queremos. Ahora vamos a hacer lo mismo pero con nuestras gráficas, por ejemplo, para construir la serie de tiempo y ponerla en la columna que le toque:

output$serie <- renderPlot({
base_ts() %>% 
    filter(subtipo_de_delito==input$id_delito) %>%
    ggplot(aes(fecha,Total)) +
    geom_line() +
    geom_point(data = . %>% 
                filter(Total==max(Total)),
                size=3,col="red"
    ) +
    geom_point(data = . %>% 
                filter(fecha==max(fecha)),
                size=3
    ) +
    theme_classic()
})

Aquí estamos suponiendo que en la columna que queremos tenemos un elemento plotOutput(serie). Lo único especial de este código es la llamada a la función base_ts() en la gráfica original base_ts son los datos con los que construimos la gráfica, en este caso base_ts() es una expresión reactiva de Shini que transforma la base de datos en una función que sólo se consulta en el momento en el que se necesita, esto permite desarrollar la interactividad de forma más rápida y eficiente:

base_ts <- reactive({
    base_ts
})

Entonces, la función completa del servidor, suponiendo que en las columnas correspondientes tenemos los elementos leafletOutput (para el mapa) y plotOutput para las gráficas, se vería así:

server <- function(input, output) {
  
  #Reactividad  de los mapas
  b_mapa <- reactive({
    base_mapa %>% 
      filter(fecha==as.Date("2022-08-01"))
  })
  # Reactividad de la serie
  b_ts <- reactive({
    base_ts
  })
  # Reactividad del scatter
  b_scatter <- reactive({
    base_mapa
  })
  # Mapa leaflet
  output$mapa <- renderLeaflet({
    base <- b_mapa()[b_mapa()$subtipo_de_delito == input$id_delito,]
    pal <- colorNumeric(palette = "inferno",domain = b_mapa()[b_mapa()$subtipo_de_delito == input$id_delito,]$Total)    
    
    leaflet() %>% 
      addProviderTiles(providers$OpenStreetMap) %>% 
      addPolygons(data = base$geometry,
                  fillColor = pal(base$Total),
                  color = "black",
                  weight = 1,
                  opacity = 1,
                  fillOpacity = .7,
                  label = base$NOMGEO,
                  group = "Entidades")%>% 
      addLegend(pal = pal,values = base$Total) %>% 
      addLayersControl(overlayGroups = "Entidades")
  })
  # Mapa serie de tiempo
  output$serie <- renderPlot({
    b_ts() %>% 
      filter(subtipo_de_delito == input$id_delito) %>%
      ggplot(aes(fecha,Total))+
      geom_line()+
      geom_point(data = . %>% 
                   filter(Total == max(Total)),
                 size = 3,col = "red"
      )+
      geom_point(data = . %>% 
                   filter(fecha == max(fecha)),
                 size = 3
      )+
      theme_classic()
  })
  
  #Scatter PLot 
  
  output$scatter <- renderPlot({
    base <- b_scatter() %>% 
      filter(subtipo_de_delito == input$id_delito)
    
    ejes <- base %>% filter(Total == max(Total))
    
    base %>% 
      filter(subtipo_de_delito == input$id_delito) %>% 
      st_drop_geometry() %>%
      pivot_wider(id_cols = c('clave_ent','entidad'),
                  names_from = 'fecha',
                  values_from = 'Total') %>% 
      ggplot(aes(`2022-07-01`,`2022-08-01`)) +
      geom_point() +
      scale_y_continuous(limits = c(0,ejes$Total)) +
      scale_x_continuous(limits = c(0,ejes$Total)) +
      geom_abline() +
      theme_classic() +
      ggrepel::geom_text_repel(aes(label=entidad))
    
  })
}

Como pueden ver, sólo se trata de empaquetar el flujo con el que hacemos cada una de las gráficas en la función de Shiny correspondiente y asignar el resultado al elemento de output que queramos. Hacer una aplicación de Shiny es sólo un poco más copmplicado que hacer cada una de las gráficas y, justo esa, es la gran ventaja de Shiny!

Código final

Finalmente, ya sólo necesitamos integrar todo el código en una sola aplicación bajo el modelo de Shiny. Eso implica crear dos funciones: un shinyUI y un server, luego ponemos las dos funciones en un archivo único app.R y llamamos a la función shinyApp(ui = ui, server = server) para levantar la aplicación. El código completo lo puedes ver a continuación:

Sys.setlocale("LC_ALL", "Spanish")

# Librerías ----
library(shiny)
library(tidyverse)
library(leaflet)
library(sf)
library(ggrepel)

# Bases input ----
# Encoding de latin 1 para que reconozca los asentos 

# Base_ ts es el histórico por delito desdel 2015 al 2022
# con el total a nivel nacional de Carpetas O víctimas 
base_ts <- read_rds("01_input/ts_delitos_prioritarios.rds")
Encoding(base_ts$subtipo_de_delito) <- "latin1"

# BAse Mapa:
# Incluye los últimos dos meses de información del SESNSP
# con el total a nivel nacional de Carpetas O víctimas por entidad.
# y su geometría del Marco Geoestadístico.
base_mapa <- read_rds("01_input/mapa_simplificado.rds")
base_mapa <- sf::st_as_sf(base_mapa)
Encoding(base_mapa$subtipo_de_delito) <- "latin1"
Encoding(base_mapa$entidad) <- "latin1"


# Ui 
ui <- shinyUI(
  navbarPage(
    title = "CentroGeo,Taller de shiny",
    fluid = TRUE,
    collapsible = TRUE,
    tabPanel("General",#General
             fluidRow(
               tagList(
                 div(class = "container",
                     h1("Información General",class = "title fit-h1"),
                     p(" La información presentada corresponde a los datos públicados por Secretariado Ejecutivo del Sistema Nacional de Seguridad Pública.
                             Con información reportada por las Procuradurías Generales de Justicia o Fiscalías Generales de las 32 entidades federativas actualización.
                              La información está al més de agosto 2022 y fue actualizada el 21 de septiembre del 2022 con datos del SESNSP")))),
             column(12,
                    h4("Tipo de delito"),
                    selectInput("id_delito",
                                "Selecciona un delito",
                                c("Robo a casa habitación",
                                  "Robo a negocio",                   
                                  "Robo a transeúnte",                
                                  "Robo a transportista",
                                  "Robo en transporte público colectivo",
                                  "Robo en transporte público individual",
                                  "Robo de vehículo automotor",
                                  "Extorsión",                         
                                  "Feminicidio",                   
                                  "Homicidio doloso",
                                  "Secuestro"))),
             br(),
             column(7,
                    leafletOutput("mapa",
                                  width = 700, height = 600)),
             column(5,
                    plotOutput("serie",
                               height = 300)),
             column(5,
                    plotOutput("scatter",
                               height = 300))
    ))
)

# Define server logic required to draw a histogram
server <- function(input, output) {
  
  #Reactividad  de los mapas
  b_mapa <- reactive({
    base_mapa %>% 
      filter(fecha==as.Date("2022-08-01"))
  })
  # Reactividad de la serie
  b_ts <- reactive({
    base_ts
  })
  # Reactividad del scatter
  b_scatter <- reactive({
    base_mapa
  })
  # Mapa leaflet
  output$mapa <- renderLeaflet({
    base <- b_mapa()[b_mapa()$subtipo_de_delito==input$id_delito,]
    pal <- colorNumeric(palette = "inferno",domain =b_mapa()[b_mapa()$subtipo_de_delito==input$id_delito,]$Total)    
    
    leaflet() %>% 
      addProviderTiles(providers$OpenStreetMap) %>% 
      addPolygons(data=base$geometry,
                  fillColor = pal(base$Total),
                  color="black",
                  weight = 1,
                  opacity = 1,
                  fillOpacity = .7,
                  label = base$NOMGEO,
                  group = "Entidades")%>% 
      addLegend(pal = pal,values = base$Total) %>% 
      addLayersControl(overlayGroups = "Entidades")
  })
  # Mapa serie de tiempo
  output$serie <- renderPlot({
    b_ts() %>% 
      filter(subtipo_de_delito==input$id_delito) %>%
      ggplot(aes(fecha,Total))+
      geom_line()+
      geom_point(data=. %>% 
                   filter(Total==max(Total)),
                 size=3,col="red"
      )+
      geom_point(data=. %>% 
                   filter(fecha==max(fecha)),
                 size=3
      )+
      theme_classic()
  })
  
  #Scatter PLot 
  
  output$scatter <- renderPlot({
    base <- b_scatter() %>% 
      filter(subtipo_de_delito==input$id_delito)
    
    ejes <- base %>% filter(Total==max(Total))
    
    base %>% 
      filter(subtipo_de_delito==input$id_delito) %>% 
      st_drop_geometry() %>%
      pivot_wider(id_cols = c('clave_ent','entidad'),
                  names_from = 'fecha',
                  values_from = 'Total') %>% 
      ggplot(aes(`2022-07-01`,`2022-08-01`))+
      geom_point()+
      scale_y_continuous(limits = c(0,ejes$Total))+
      scale_x_continuous(limits = c(0,ejes$Total))+
      geom_abline()+
      theme_classic()+
      ggrepel::geom_text_repel(aes(label=entidad))
    
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

Ahora, pega este código en un archivo nuevo, guárdalo como app.R (conservando la estructura de archivos de la Introducción) y listo, ya puedes correr la aplicación utilizando el botón Run App de Rstudio.

Para ver la aplicación funcionando en vivo:

Visualizador de delitos