Interactive Maps and ETF Analysis
In this post, I’ll describe a Shiny app to support the Emerging Markets ETF Country Exposure analysis developed in a previous post
I have done some additional work and updated the analysis to include five ETFs in the app, whereas we originally imported data on 1 ETF. The new notebook is available here.
As we start to build our Shiny app, we will assume that our underlying Notebook has been used to build the desired ETF data objects and spatial data frame, and that those have been saved in an appropriately named .RDat file. That’s an important assumption because the first thing we’ll do in the chunk below is load up that file.
A brief aside before we dive into the first code chunk: if you look at the source code for this app, you might notice that the code chunks have different ‘contexts’ – something we haven’t had in our previous flexdashboards. For example, the first chunk has ‘context = “data”’, the next has ’context = “render”, and so on. That is because I am taking advantage of a new run_time option called shiny_prerendered that is still under development. This run time allows your flexdashboard to “pre-render” certain objects, meaning they are knit prior to deployment. Here, we pre-render our data and our map object.
First, we’ll load up our data and build a map of the world, shaded by our original emerging markets fund. That is what a user will see when first arriving to the app. We can think of it as the default map and shading before the user has done anything. We didn’t have to use emerging markets as the default, but it was the first fund we imported in the Notebook.
This code should look familiar. When we load the .RDat file, all of the saved objects go the global environment, and after the chunk has been run (or pre-rendered), the data and map object are available.
Now we want to set up some radio buttons so that the user can eventually change the shading of the map. This is pure UI work and is a simple selection of radio buttons. Note, in particular, how we give the user a choice of radio buttons with a nice intuitive label but then assign each button with a string value that matches the name of our fund objects. For example, we label the second button as “Global Infrastructure ETF”, which is what the user sees. But, upon choosing that button, we assign an input value equal to “infrastructure”.
helpText("Choose a radio button to change the way the map is shaded.") radioButtons("fund", "Fund", c("Emerging Markets ETF" = "emerging_market", "Total International ETF" = "international", "Global Infrastructure ETF" = "infrastructure", "Asia Ex Japan ETF" = "asia_ex_japan", "Europe ETF" = "europe"))
Go back and look at the Notebook and, no surprise, “infrastructure” is what we titled our infrastructure fund object, as well as the column in the spatial data frame that contains country weights for the infrastructure fund.
I will harp on this point again below but it was quite purposeful to choose one label – “infrastructure” – for both the fund object and the column in the spatial data frame. It allows us to use one radio button to reference both of those. The same convention holds true for the other funds: one label for the fund object and the spatial data frame column that holds country weights.
Let’s go ahead and render the default map to the user.
Next, we build that default map in the “server” context. It will be shaded by whatever defaults we chose in the first chunk. In this case, we shade by the emerging market ETF country weights.
# Build the map that is displayed in the "render" chunk above. output$fundMap <- renderLeaflet({ leaf_world_emerging })
After we have that map, finally, the fun part: we’re going to allow the user to change the shading of the map according to the radio buttons. But - but! - we don’t want to rebuild a map of the world upon each radio button selection. Instead, we want to change just the shading and the pop up, and we’ll use the leafletProxy() function to accomplish that.
This will be a four step process: (1) use observeEvent() to capture the input from the radio button, (2) build a new palette based on that input, (3)build a new pop up based on that input, (4) display the new map which consists of the original map plus the new palette and pop up.
Have a read through the code chunk and then we’ll dissect it.
# The fun part: allow the user to change the shading according to the radio button input. observeEvent(input$fund, { # Use Observe event to capture the radio button input and assign it to 'indicator'. indicator <- as.character(input$fund) # Build a new palette, based on the reactive value 'indicator'. newPal <- colorNumeric( palette = "Greens", # The next line will choose a column of the spatial dataframe. Good thing we # used the same name for the fund object and the spatial dataframe column. domain = world_fund_country_weights[[indicator]] ) # Create a new pop up based on 'indicator'. newPopup <- paste0("<strong>Country: </strong><br>", world_fund_country_weights$name, "<br><strong> Country Weight: </strong>", world_fund_country_weights[[indicator]], "%" ) # Take our already constructed map, called 'fundMap' and use leaflet proxy to add our new # shading and pop up to the map. leafletProxy( "fundMap", data = world_fund_country_weights) %>% # Remove the previous layerID so we have a clean slate removeShape( layerId = ~name ) %>% addProviderTiles("CartoDB.Positron") %>% addPolygons(stroke = TRUE, color = "black", weight = .4, opacity = 1.0, smoothFactor = 0.5, fill = TRUE, fillColor = # The next two lines are where we update the map with the new # palette shading and pop up. ~newPal(world_fund_country_weights[[indicator]]), fillOpacity = .8, layerId = ~name, popup = newPopup) })
Inside the observeEvent function, capture the radio button selection with ‘input$fund’, and save it to an object called ‘indicator’.
Create a new palette based on the indicator called ‘newPal’, in the same way as we created the palette in the the first code chunk, except instead of explicitly passing in a fund like ‘emerging_market’, pass in ‘indicator’, which has taken on a fund value from the radio button.
Create a new pop up based on the indicator using the same process. Call it newPopup and pass it ‘indicator’ instead of an explicit fund name.
- Display the new map.
- Use leafletProxy() to build out that new map. We don’t want rebuild the whole map, so we tell leafletProxy() to start with the already built ‘fundMap’.
- Remove the layerId so we have a clean slate. Add back the provider tiles; then add back the polygons.
- To add a palette, we do two things: use ‘newPal’, which is based on the radio button, and use the column from the spatial data frame that has the same name as ‘indicator’ (that is, shade the palette by ‘world_fund_country_weights[[indicator]]’). We can do that because the column in the spatial data frame has the same name as the fund object.
In the original the Notebook where we created the data frame emerging_market_country_weights, we made a decision that makes interactive shading smoother: we named the column in the data frame that holds the country weights ‘emerging_market’. Then we added that data frame to the spatial data frame and the spatial data frame got a new column called ‘emerging_market’.
Notice that because he column in the spatial data frame emerging_market, has the same name as our fund object We can reference both from one selection of the radio button. Convenient!
Just to finish off the new map, we use ‘popup = newPopup’ to add the popup object we created from the radio input. Our app now has an interactively shaded map!
Before we head to happy hour, though, let’s finish up with a relatively simple addition to the app. When a user clicks a country, not only does the user see a popup but also country level detail in a data table. The data table shows the individual companies that the ETF holds in the clicked country, and the weight to each of those companies.
Again, the hard work was done in the Notebook. When we loaded the .RDat file, it contained an object for each fund, with that convenient label I have belabored, with a column for countries and a column for companies. All we need do is filter that object by the clicked country. Our friend eventReactive() again allows us to capture the clicked country’s id, and we subset by that id. For example, if a user clicks on China, we subset and display only the companies with ‘China’ in the country column.
# Capture whatever country is clicked. clickedCountry <- eventReactive(input$fundMap_shape_click, { return(input$fundMap_shape_click$id) }) output$table <- renderDataTable({ # A repeat from above - get the fund object based on the radio button. fund <- reactive({ fund <- get(input$fund) fund[4] <- fund[4]/100 fund }) # Let's use datatable because I like giving the user the ability to filter on columns. fund <- data.table(fund()) # Subset the table by clickedCountry. fund_subsetted <- subset(fund, Country == as.character(clickedCountry())) # An aesthetic decision here: I don't want to display the column of countries because # I am going to add a caption with that information. fund_subsetted$Country <- NULL # The final datatable object that will be displayed. Note the nice # formatPercentage function which allows us to # add a '%' to the Weight column. datatable(head(fund_subsetted, n =20), fillContainer = TRUE, caption = as.character(clickedCountry()), # The next two lines are purely for aesthetics. They add a # border and stripe to each cell and then center the column values. # If you don't like how that looks, comment these out and re-run. class = 'cell-border stripe', options = list(dom = 't', pageLength = 20)) %>% # I want the Weight column to include a '%' - again, an aesthetic preference. formatPercentage(3, 2) })
And, we’re done! Please note, however, that the techniques employed in this map can be used to map country exposures of mutual funds, bespoke stock/bond portfolios, currency and commodities holdings, etc. Enjoy!