Logo

The Data Daily

Visualizing geo-spatial data with sf and plotly - Carson's blog on R, RStudio, plotly, shiny, data visualization, statistics, etc

Visualizing geo-spatial data with sf and plotly - Carson's blog on R, RStudio, plotly, shiny, data visualization, statistics, etc

Visualizing geo-spatial data with sf and plotly
2018/03/30
Need help with R, data viz, and/or stats? Work with me or attend my 2 day workshop !
In my last post , we explored interactive visualizations of simple features (i.e., interactive maps) via ggplot2’s geom_sf() and plotly’s ggplotly(). This time we’ll make similar visualizations using plotly’s “non-ggplot2” mapping interfaces (namely plot_ly(), plot_geo(), and plot_mapbox()). Here’s a quick example of reading a shape file into R as simple features via st_read() , then plotting those features (in this case, North Carolina counties) using each one of the four mapping approaches plotly provides. Notice how all of these options auto-magically know how to render simple features:
# to replicate these examples, you currently need the dev version # devtools::install_github('ropensci/plotly') library(plotly) nc [1] "sf" "data.frame"
subplot(nrows = 2, ggplot(nc) + geom_sf(), plot_ly(nc), plot_geo(nc), plot_mapbox(nc) ) %>% hide_legend()
You might be wondering, “What can plotly offer over other interactive mapping packages such as leaflet , mapview , mapedit , etc?”. One big feature is the linked brushing framework , which works best when linking plotly together with other plotly graphs (i.e., only a subset of brushing features are supported when linking to other crosstalk-compatible htmlwidgets ). Another is the ability to leverage the plotly.js API to make efficient updates in shiny apps via plotlyProxy() . Speaking of efficiency, plotly.js keeps on improving the performance of their WebGL-based rendering, so I recommend trying plot_ly() (with toWebGL()) and/or plot_mapbox() if you have lots of graphical elements to render. Also, by having a consistent interface between these various mapping approaches, it’s much quicker and easier to switch from one approach to another when you need to leverage a different set of strengths and weaknesses .
Speaking of strengths, plotly.js also has ????-class 3D rendering support. When used in combination with plotly’s linking framework, we can do some nifty things – all inside self-contained HTML! For example, here are linked 3D & 2D views of tropical storm paths which is useful for querying anomalies and provides some insight into the relationship between distance traveled, altitude, latitude, and longitude: 1
# To see the actual R code that generates the self-contained HTML result, # enter this in your R console or visit https://github.com/ropensci/plotly/tree/master/demo demo("sf-plotly-3D-globe", package = "plotly")
As you’ll see in the NEWS of the development version, I’ve also added new stroke and span arguments which make it easier to control the outline color and width of filled polygons/markers/bars/etc. These new arguments work in a similar way to existing arguments like color and linetype. In particular, constant values can be specified via I():
plot_ly(nc, stroke = I("#119dff"), span = I(5), color = I("#00cc96"), linetype = I("dash"))
Furthermore, values not wrapped in I() are mapped to a visual range defined by the plural form of the argument (i.e., strokes and spans). However, to effectively map data to these sorts of visuals, we might want to generate mutliple traces.
One trace to rule them all?
One of the trickiest things about mastering plotly and/or plotly.js is knowing what can and can not be done with just one trace. As I elude to in the plotly for R book , if something can be implemented with a single trace, then it should, because traces don’t scale very well (i.e., can easily lead to a sluggish plot). That’s why, by default, the maps above are implemented with just one trace. 2 However, when you need certain scalar (i.e., non-data-array) trace properties (e.g. fillcolor) to vary, you might want to use split to create a trace for every level in the split variable. For example, in this map of territories in Franconia , we ensure one trace per territory (via the split argument), which leads to two fairly obvious features:
The ability to hide/show territories by clicking (or double-clicking) legend entries. As we’ll discuss later, there are actually ways to do this sort of thing with just one trace via the crosstalk (i.e. linked views) framework .
A different color for each territory. In this case, we’ve used split without specifying color, so plotly.js will impose it’s default coloring rules, but you could easily set a constant color across traces (e.g., color = I("black")) or, as we’ll see shortly, use a custom color scheme.
# load trails data (an sf object bundled with the mapview package) # install.packages('mapview') data(franconia, package = "mapview") # Compare this result with: `plot_ly(franconia, split = ~NAME_ASCI, color = I("black"))` plot_ly(franconia, split = ~NAME_ASCI)
Having a different trace for each territory opens the door for further territory-level customization, such as having a custom color, linetype, fill-specific tooltip, etc. When color is numeric, and you want it to set a different fill-color for certain polygon(s), you’ll need split to ensure there is no more than one color per trace:
plot_ly( franconia, split = ~NUTS_ID, color = ~SHAPE_AREA, alpha = 1, showlegend = FALSE )
On the other hand, if color is a discrete variable, plot_ly() always produces one trace per level. Since, in this case, there are over 30 districts, I’m going to use a color palette that does a fairly good job in distinguishing between a large number of groups:
cols % highlight("plotly_selected", persistent = TRUE) )
If you’re interested in understanding the full power of the linking framework, my 2 day plotly for R workshop is the best way to learn it effectively. I also offer this workshop as an on-site training course, so please get in touch if you have any interest!
More learning resources
At least currently, the best examples of using sf with plotly are within the package demos. Any demo names that are prefixed with ‘sf’ when you look at the list provided by demo(package = "plotly") are relevant. For example, demo("sf-dt", package = "plotly") gives an example of querying simple feature data by linking plot_mapbox() with DT via crosstalk. Also be on the look-out for updates to the mapping section of the plotly for R book as well as examples in some of my more recent talks .
Future work
I’m excited to see the author of sf, Edzer Pebesma, starting work on stars – a tidy (and sf friendly) approach to working with geo-spatial arrays (e.g. raster data). Once that project becomes stable, I’m hoping to find the time and resources to build a similar bridge between plotly and stars. This effort might have to take a back seat for several months though unless someone would like to sponsor (or otherwise assist in) development.
Appendix: strengths and weaknesses
Below is a list of strengths (blue) and weaknesses (red) for each mapping approach in plotly. Note that plotly.js is still under development, so this list is likely change a bit (please let me know if I’m missing anything):
1. plot_ly() and geom_sf()

Images Powered by Shutterstock