Choropleth Maps in F#

How to make choropleth maps in F# with Plotly.


In [1]:
#r "nuget: Plotly.NET,  2.0.0-preview.8"
#r "nuget: Plotly.NET.Interactive,  2.0.0-preview.8"
Installed Packages
  • Plotly.NET, 2.0.0-preview.8
  • Plotly.NET.Interactive, 2.0.0-preview.8

Loading extensions from Plotly.NET.Interactive.dll

Added Kernel Extension including formatters for Plotly.NET charts.

A Choropleth Map is a map composed of colored polygons. It is used to represent spatial variations of a quantity. This page documents how to build outline choropleth maps, but you can also build choropleth tile maps using our Mapbox trace types.

Base Map Configuration

Introduction: main parameters for choropleth outline maps

Making choropleth maps requires two main types of input:

Geometry information: This can either be a supplied GeoJSON file where each feature has either an id field or some identifying value in properties; or one of the built-in geometries within plot_ly: US states and world countries (see below) A list of values indexed by feature identifier. The GeoJSON data is passed to the geojson argument, and the data is passed into the z argument of choropleth traces.

Note the geojson attribute can also be the URL to a GeoJSON file, which can speed up map rendering in certain cases.

Choropleth Map Using GeoJSON

In [2]:
#r "nuget: FSharp.Data"
#r "nuget: Newtonsoft.Json"
open FSharp.Data
open Newtonsoft.Json
open Plotly.NET.LayoutObjects
open Plotly.NET.TraceObjects
    
#r "nuget: Deedle"
open Deedle
open System.IO
open System.Text

open Plotly.NET 

let data = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/fips-unemp-16.csv"
    |> fun csv -> Frame.ReadCsvString(csv,true,separators=",",schema="fips=string,unemp=float")

let geoJson = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json"
    |> JsonConvert.DeserializeObject 

let locationsGeoJSON: string [] = 
    data
    |> Frame.getCol "fips"
    |> Series.values
    |> Array.ofSeq
let zGeoJSON: int [] = 
    data
    |> Frame.getCol "unemp"
    |> Series.values
    |> Array.ofSeq

Chart.ChoroplethMap(
    locations = locationsGeoJSON,
    z = zGeoJSON,
    Locationmode=StyleParam.LocationFormat.GeoJson_Id,
    GeoJson = geoJson,
    FeatureIdKey="id"
)

|> Chart.withGeo(
    Geo.init(
        Scope=StyleParam.GeoScope.NorthAmerica, 
        Projection=GeoProjection.init(StyleParam.GeoProjectionType.AzimuthalEqualArea),
        ShowLand=true,
        
        LandColor = Color.fromString "lightgrey"
    )
)

|> Chart.withSize (800.,800.)
Installed Packages
  • Deedle, 2.4.3
  • FSharp.Data, 4.2.4
  • newtonsoft.json, 12.0.3
Out[2]:

Indexing by GeoJSON Properties

If the GeoJSON you are using either does not have an id field or you wish you use one of the keys in the properties field, you may use the featureidkey parameter to specify where to match the values of locations.

In the following GeoJSON object/data-file pairing, the values of properties.district match the values of the district column:

In [3]:
#r "nuget: FSharp.Data"

open FSharp.Data

open Newtonsoft.Json

let data = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/election.csv"
    |> fun csv -> Frame.ReadCsvString(csv,true,separators=",")

let geoJson = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/election.geojson"
    |> JsonConvert.DeserializeObject 

let locationsGeoJSON: string [] = 
    data
    |> Frame.getCol "district"
    |> Series.values
    |> Array.ofSeq

let zGeoJSON: int [] = 
    data
    |> Frame.getCol "Bergeron"
    |> Series.values
    |> Array.ofSeq


Chart.ChoroplethMap(locations = locationsGeoJSON,
    z = zGeoJSON,    
    GeoJson = geoJson,
    Colorscale= StyleParam.Colorscale.Viridis,
    FeatureIdKey="properties.district")
 
|> Chart.withGeoStyle(FitBounds=StyleParam.GeoFitBounds.Locations,Visible=false)
|> Chart.withColorBarStyle(Title.init("Bergeron Votes"))
|> Chart.withTitle(title="2013 Montreal Election")
|> Chart.withSize (800.,800.)
Installed Packages
  • FSharp.Data, 4.2.4
Out[3]:

Using Built-in Country and State Geometries

Plotly comes with two built-in geometries which do not require an external GeoJSON file:

USA States Countries as defined in the Natural Earth dataset. Note and disclaimer: cultural (as opposed to physical) features are by definition subject to change, debate and dispute. Plotly includes data from Natural Earth "as-is" and defers to the Natural Earth policy regarding disputed borders which read:

Natural Earth Vector draws boundaries of countries according to defacto status. We show who actually controls the situation on the ground.

To use the built-in countries geometry, provide locations as three-letter ISO country codes.

In [4]:
let data = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv"
    |> fun csv -> Frame.ReadCsvString(csv,true,separators=",")

data.Print()
       COUNTRY              GDP (BILLIONS) CODE 
0   -> Afghanistan          21.71          AFG  
1   -> Albania              13.40          ALB  
2   -> Algeria              227.80         DZA  
3   -> American Samoa       0.75           ASM  
4   -> Andorra              4.80           AND  
5   -> Angola               131.40         AGO  
6   -> Anguilla             0.18           AIA  
7   -> Antigua and Barbuda  1.24           ATG  
8   -> Argentina            536.20         ARG  
9   -> Armenia              10.88          ARM  
10  -> Aruba                2.52           ABW  
11  -> Australia            1483.00        AUS  
12  -> Austria              436.10         AUT  
13  -> Azerbaijan           77.91          AZE  
14  -> Bahamas, The         8.65           BHM  
:      ...                  ...            ...  
207 -> Uganda               26.09          UGA  
208 -> Ukraine              134.90         UKR  
209 -> United Arab Emirates 416.40         ARE  
210 -> United Kingdom       2848.00        GBR  
211 -> United States        17420.00       USA  
212 -> Uruguay              55.60          URY  
213 -> Uzbekistan           63.08          UZB  
214 -> Vanuatu              0.82           VUT  
215 -> Venezuela            209.20         VEN  
216 -> Vietnam              187.80         VNM  
217 -> Virgin Islands       5.08           VGB  
218 -> West Bank            6.64           WBG  
219 -> Yemen                45.45          YEM  
220 -> Zambia               25.61          ZMB  
221 -> Zimbabwe             13.74          ZWE  

In [5]:
let getColumnData column=
        data
        |> Frame.getCol column
        |> Series.values
        |> Array.ofSeq

Chart.ChoroplethMap(locations=(getColumnData "CODE"),z=getColumnData "GDP (BILLIONS)",Text=getColumnData "COUNTRY",Colorscale=StyleParam.Colorscale.Bluered)
Out[5]:

To use the USA States geometry, set locationmode='USA-states' and provide locations as two-letter state abbreviations

In [6]:
let data = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/2011_us_ag_exports.csv"
    |> fun csv -> Frame.ReadCsvString(csv,true,separators=",")

let getColumnData column=
        data
        |> Frame.getCol column
        |> Series.values
        |> Array.ofSeq

data?hover <- [ for k in data.RowKeys -> 
                    let dataRow = (data.GetRowAt(k))
                    let state = string (dataRow |> Series.get "state") 
                    let beef = string (dataRow |> Series.get "beef")
                    let dairy = string (dataRow |> Series.get "dairy")
                    let fruits = string (dataRow |> Series.get "total fruits")
                    let veggies = string (dataRow |> Series.get "total veggies")
                    let wheat = string (dataRow |> Series.get "wheat")
                    let corn = string (dataRow |> Series.get "corn")
                    String.Format(" {0} <br> Beef {1} Dairy {2} <br> Fruits {3} Veggies {4} <br> Wheat {5} Corn {6}",state, beef,dairy,fruits,veggies,wheat,corn) ]

Chart.ChoroplethMap(locations=(getColumnData "code"),z=getColumnData "total exports",Locationmode=StyleParam.LocationFormat.USA_states,Text=getColumnData "hover", Colorscale=StyleParam.Colorscale.Bluered)
Out[6]:

Customize choropleth chart

Note In this example we set layout.geo.scope to usa to automatically configure the map to display USA-centric data in an appropriate projection.

In [7]:
Chart.ChoroplethMap(locations=(getColumnData "code"),z=getColumnData "total exports",Locationmode=StyleParam.LocationFormat.USA_states,Text=getColumnData "hover", Colorscale=StyleParam.Colorscale.Bluered)
|> Chart.withGeo(
    Geo.init(
        Scope=StyleParam.GeoScope.Usa, 
        Projection=GeoProjection.init(StyleParam.GeoProjectionType.AlbersUSA),        
        ShowLakes=true,
        LakeColor = Color.fromString "white"
    )
)
Out[7]:

World Choropleth Map

In [8]:
let data = 
    Http.RequestString "https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv"
    |> fun csv -> Frame.ReadCsvString(csv,true,separators=",")

let getColumnData column=
        data
        |> Frame.getCol column
        |> Series.values
        |> Array.ofSeq

Chart.ChoroplethMap(locations=(getColumnData "CODE"),z=getColumnData "GDP (BILLIONS)",Text=getColumnData "COUNTRY",Colorscale=StyleParam.Colorscale.Greens,Marker=Marker.init(Line=Line.init(Color= Color.fromString "grey",Width=0.5)))
|> Chart.withColorBarStyle(title=Title.init("GDP Billions US$"))
|> Chart.withTitle(title="2014 Global GDP<br>Source:<a href=\"https://www.cia.gov/library/publications/the-world-factbook/fields/2195.html\">CIA World Factbook</a>")
|> Chart.withGeo(
    Geo.init(
        Projection=GeoProjection.init(StyleParam.GeoProjectionType.Mercator),        
        ShowCoastLines=false,
        ShowFrame=false
    )
)
Out[8]: