How to create dynamic filtering in data science reports and dashboards

Estimated time:
time
min

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. <h2 id="what-is-crossfiltering">What is crossfiltering?</h2> 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. <h2 id="htmlwidgets-and-crosstalk">Htmlwidgets and Crosstalk</h2> First let’s have a look at a <a href="https://github.com/rstudio/crosstalk">crosstalk</a> package that allows us to crossfilter between different <a href="http://www.htmlwidgets.org/" target="_blank" rel="noopener noreferrer">htmlwidgets</a>. Currently only available on Github. We need to install crosstalk: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">devtools</span><span class="o">::</span><span class="n">install_github</span><span class="p">(</span><span class="s2">"rstudio/crosstalk"</span><span class="p">)</span></code></pre> </figure> We use htmlwidgets from leaflet and DT packages that are compatible with crosstalk. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">devtools</span><span class="o">::</span><span class="n">install_github</span><span class="p">(</span><span class="s2">"rstudio/DT"</span><span class="p">)</span> <span class="n">devtools</span><span class="o">::</span><span class="n">install_github</span><span class="p">(</span><span class="s2">"rstudio/leaflet"</span><span class="p">)</span></code></pre> </figure> 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. <figure class="highlight"> <pre><code class="language-text" data-lang="text">## Error in library(crosstalk): there is no package called 'crosstalk'</code></pre> </figure> <figure class="highlight"> <pre><code class="language-text" data-lang="text">## Error in library(leaflet): there is no package called 'leaflet'</code></pre> </figure> <figure class="highlight"> <pre><code class="language-text" data-lang="text">## Error in library(DT): there is no package called 'DT'</code></pre> </figure> <figure class="highlight"> <pre><code class="language-text" data-lang="text">## Error in eval(expr, envir, enclos): object 'SharedData' not found</code></pre> </figure> <figure class="highlight"> <pre><code class="language-text" data-lang="text">## Error in bscols(leaflet(ships_shared) %&gt;% addTiles() %&gt;% addMarkers(), : could not find function "bscols"</code></pre> </figure> Unfortunately table filter does not update dynamically when we navigate map using drag and drop or mouse scroll. An alternative to crosstalk is <a href="https://github.com/ramnathv/robservable" target="_blank" rel="noopener noreferrer">robservable</a> an R package that brings observables to htmlwidgets, allowing for shiny-like interactivity in the browser. <h2 id="crossfilter-in-shiny">Crossfilter in Shiny</h2> In Shiny we can filter table when zooming or changing the area of the map. In order to do that we need to use <code class="highlighter-rouge">bounds</code> which are input from the leaflet map. From leaflet R package documentation we know that <code class="highlighter-rouge">input$MAPID_bounds</code> 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 (<code class="highlighter-rouge">east, west, north, south</code>). UI of the app is straightforward. Core logic is the dynamic filtering of table where we use reactive value of <code class="highlighter-rouge">input$map_bounds</code>. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span> <span class="n">library</span><span class="p">(</span><span class="n">magrittr</span><span class="p">)</span> <br><span class="n">ships</span> <span class="o">&lt;-</span> <span class="n">read.csv</span><span class="p">(</span><span class="s2">"https://raw.githubusercontent.com/Appsilon/crossfilter-demo/master/app/ships.csv"</span><span class="p">)</span> <br><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">shinyUI</span><span class="p">(</span><span class="n">fluidPage</span><span class="p">(</span>  <span class="n">fluidRow</span><span class="p">(</span>    <span class="n">column</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="n">leaflet</span><span class="o">::</span><span class="n">leafletOutput</span><span class="p">(</span><span class="s2">"map"</span><span class="p">)),</span>    <span class="n">column</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="n">DT</span><span class="o">::</span><span class="n">dataTableOutput</span><span class="p">(</span><span class="s2">"tbl"</span><span class="p">))</span>  <span class="p">)</span> <span class="p">))</span> <br><span class="n">server</span> <span class="o">&lt;-</span> <span class="n">shinyServer</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="p">{</span>    <span class="n">output</span><span class="o">$</span><span class="n">map</span> <span class="o">&lt;-</span> <span class="n">leaflet</span><span class="o">::</span><span class="n">renderLeaflet</span><span class="p">({</span>    <span class="n">leaflet</span><span class="o">::</span><span class="n">leaflet</span><span class="p">(</span><span class="n">ships</span><span class="p">)</span> <span class="o">%&gt;%</span>      <span class="n">leaflet</span><span class="o">::</span><span class="n">addTiles</span><span class="p">()</span> <span class="o">%&gt;%</span>      <span class="n">leaflet</span><span class="o">::</span><span class="n">addMarkers</span><span class="p">()</span>  <span class="p">})</span>    <span class="n">in_bounding_box</span> <span class="o">&lt;-</span> <span class="k">function</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">long</span><span class="p">,</span> <span class="n">bounds</span><span class="p">)</span> <span class="p">{</span>    <span class="n">data</span> <span class="o">%&gt;%</span>      <span class="n">dplyr</span><span class="o">::</span><span class="n">filter</span><span class="p">(</span><span class="n">lat</span> <span class="o">&gt;</span> <span class="n">bounds</span><span class="o">$</span><span class="n">south</span> <span class="o">&amp;</span> <span class="n">lat</span> <span class="o">&lt;</span> <span class="n">bounds</span><span class="o">$</span><span class="n">north</span> <span class="o">&amp;</span> <span class="n">long</span> <span class="o">&lt;</span> <span class="n">bounds</span><span class="o">$</span><span class="n">east</span> <span class="o">&amp;</span> <span class="n">long</span> <span class="o">&gt;</span> <span class="n">bounds</span><span class="o">$</span><span class="n">west</span><span class="p">)</span>  <span class="p">}</span>    <span class="n">data_map</span> <span class="o">&lt;-</span> <span class="n">reactive</span><span class="p">({</span>    <span class="k">if</span> <span class="p">(</span><span class="nf">is.null</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">map_bounds</span><span class="p">)){</span>      <span class="n">ships</span>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>      <span class="n">bounds</span> <span class="o">&lt;-</span> <span class="n">input</span><span class="o">$</span><span class="n">map_bounds</span>      <span class="n">in_bounding_box</span><span class="p">(</span><span class="n">ships</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="n">long</span><span class="p">,</span> <span class="n">bounds</span><span class="p">)</span>    <span class="p">}</span>  <span class="p">})</span>    <span class="n">output</span><span class="o">$</span><span class="n">tbl</span> <span class="o">&lt;-</span> <span class="n">DT</span><span class="o">::</span><span class="n">renderDataTable</span><span class="p">({</span>    <span class="n">DT</span><span class="o">::</span><span class="n">datatable</span><span class="p">(</span><span class="n">data_map</span><span class="p">(),</span> <span class="n">extensions</span> <span class="o">=</span> <span class="s2">"Scroller"</span><span class="p">,</span> <span class="n">style</span> <span class="o">=</span> <span class="s2">"bootstrap"</span><span class="p">,</span> <span class="n">class</span> <span class="o">=</span> <span class="s2">"compact"</span><span class="p">,</span> <span class="n">width</span> <span class="o">=</span> <span class="s2">"100%"</span><span class="p">,</span>                  <span class="n">options</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">deferRender</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span> <span class="n">scrollY</span> <span class="o">=</span> <span class="m">300</span><span class="p">,</span> <span class="n">scroller</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span> <span class="n">dom</span> <span class="o">=</span> <span class="s1">'tp'</span><span class="p">))</span>  <span class="p">})</span>   <span class="p">})</span> <br><span class="n">shinyApp</span><span class="p">(</span><span class="n">ui</span> <span class="o">=</span> <span class="n">ui</span><span class="p">,</span> <span class="n">server</span> <span class="o">=</span> <span class="n">server</span><span class="p">)</span></code></pre> </figure> <!--html_preserve--> <iframe src="http://demo.appsilon.com/crossfilter-demo/" width="100%" height="400" frameborder="0" scrolling="yes"></iframe> <!--/html_preserve--> <h2 id="summary">Summary</h2> 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 <a href="https://github.com/rstudio/crosstalk" target="_blank" rel="noopener noreferrer">crosstalk</a> page. Shiny app code used in this example is also available on <a href="https://github.com/Appsilon/crossfilter-demo" target="_blank" rel="noopener noreferrer">Github</a>.

Contact us!
Damian's Avatar
Damian Rodziewicz
Head of Sales
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Have questions or insights?
Engage with experts, share ideas and take your data journey to the next level!
Join Slack

Take Your Business Further with Custom
Data Solutions

Unlock the full potential of your enterprise with our data services, tailored to the unique needs of Fortune 500 companies. Elevate your strategy—connect with us today!

r
tutorials
shiny dashboards