Article Image
Article Image
read

R has long ago stopped to be a language only for statistical analysis. It’s also a great tool for communicating the results of data scientist’s work. With RMarkdown and Shiny it’s very easy to quickly design great reports and applications. We want to share few tricks to make your reports and apps more interactive and show you how components can talk to each other.

What is crossfiltering?

Let’s take a look at crossfiltering, which is filtering for coordinated views. Simply by interacting with one component the related element, like chart or table, can update automatically. For example you can zoom a map or select subrange in a plot and get your whole interface reflect that immediately as in Business Intelligence tools.

Htmlwidgets and Crosstalk

First let’s have a look at a crosstalk package that allows us to crossfilter between different htmlwidgets. Currently only available on Github.

We need to install crosstalk:

devtools::install_github("rstudio/crosstalk")

We use htmlwidgets from leaflet and DT packages that are compatible with crosstalk.

devtools::install_github("rstudio/DT")
devtools::install_github("rstudio/leaflet")

We need developer versions for components to enable crosstalk.

In our example we use marine data for Baltic Sea area that contains ship name, speed and its location (latitude and longitude). We display the ships coordinates on a leaflet map and provide additional information in a DT table. Using the crop button on the map we can filter the data in the table.

## Classes 'SharedData', 'R6' <SharedData>
##   Public:
##     .updateSelection: function (value) 
##     clearSelection: function (ownerId = "") 
##     clone: function (deep = FALSE) 
##     data: function (withSelection = FALSE, withFilter = TRUE, withKey = FALSE) 
##     groupName: function () 
##     initialize: function (data, key = NULL, group = createUniqueId(4, prefix = "SharedData")) 
##     key: function () 
##     origData: function () 
##     selection: function (value, ownerId = "") 
##   Private:
##     .data: data.frame
##     .filterCV: ClientValue, R6
##     .group: SharedDataffc4c552
##     .key: NULL
##     .rv: reactivevalues
##     .selectionCV: ClientValue, R6
## Error in datatable(ships_shared, extensions = "Scroller", style = "bootstrap", : 'data' must be 2-dimensional (e.g. data frame or matrix)

Unfortunately table filter does not update dynamically when we navigate map using drag and drop or mouse scroll.

An alternative to crosstalk is robservable an R package that brings observables to htmlwidgets, allowing for shiny-like interactivity in the browser.

Crossfilter in Shiny

In Shiny we can filter table when zooming or changing the area of the map. In order to do that we need to use bounds which are input from the leaflet map. From leaflet R package documentation we know that input$MAPID_bounds provides the latitude/longitude bounds of the currently visible map area. Below we have an example of how to create a coordinated map and table in Shiny. In our case we create a new reactive variable containing filtered data, where we filter latitude and longitude on bounds elements (east, west, north, south). UI of the app is straightforward. Core logic is the dynamic filtering of table where we use reactive value of input$map_bounds.

library(shiny)
library(magrittr)

ships <- read.csv("https://raw.githubusercontent.com/Appsilon/crossfilter-demo/master/app/ships.csv")

ui <- shinyUI(fluidPage(
  fluidRow(
    column(6, leaflet::leafletOutput("map")),
    column(6, DT::dataTableOutput("tbl"))
  )
))

server <- shinyServer(function(input, output) {
  
  output$map <- leaflet::renderLeaflet({
    leaflet::leaflet(ships) %>%
      leaflet::addTiles() %>% 
      leaflet::addMarkers()
  })
  
  in_bounding_box <- function(data, lat, long, bounds) {
    data %>%
      dplyr::filter(lat > bounds$south & lat < bounds$north & long < bounds$east & long > bounds$west)
  }
  
  data_map <- reactive({
    if (is.null(input$map_bounds)){
      ships
    } else {
      bounds <- input$map_bounds
      in_bounding_box(ships, lat, long, bounds)
    }
  })
  
  output$tbl <- DT::renderDataTable({
    DT::datatable(data_map(), extensions = "Scroller", style = "bootstrap", class = "compact", width = "100%",
                  options = list(deferRender = TRUE, scrollY = 300, scroller = TRUE, dom = 'tp'))
  })
  
})

shinyApp(ui = ui, server = server)

Summary

We showed you two simple examples of how to build coordinated leaflet map and DT table in RMarkdown and Shiny. We highly encourage you to try it yourself and also look at more examples available on crosstalk page. Shiny app code used in this example is also available on Github.

Blog Logo

Olga Mierzwa-Sulima


Published

Image

Appsilon Data Science Blog

How to create and use technology to deliver business results.

Back to the top