Choropleth

mapclassify is intended to be used with visualizaiton packages to handle the actual rendering of the choropleth maps defined on its classifiers. In this notebook, we explore some examples of how this is done. The notebook also includes an example that combines mapclassify with ipywidgets to allow for the interactive exploration of the choice of:

  • classification method

  • number of classes

  • colormap

[14]:
import libpysal
import geopandas as gpd
import mapclassify
[15]:
mapclassify.__version__
[15]:
'2.3.0'

The example in this notebook use data on southern US counties from a built-in dataset available through libpysal. We use libpysal to obtain the path to the shapefile and then use geopandas to create a geodataframe from the shapefile:

[16]:
pth = libpysal.examples.get_path('south.shp')
gdf = gpd.read_file(pth)

Once created, the geodataframe has a plot method that can be called to create our first choropleth map. We will specify the column to classify and plot as BLK90 which is the percentage of the county population that is black. The classification scheme is set to Quantiles, and the number of classes set to k=10 (declies):

[19]:
gdf.plot(column='BLK90', scheme='Quantiles', k=10, figsize=(16, 9))
[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f5f21541710>
../_images/notebooks_03_choropleth_6_1.png

We can peak under the hood a bit and recreate the classification object that was used in the previous choropleth:

[20]:
q10 = mapclassify.Quantiles(gdf.BLK90,k=10)
q10
[20]:
Quantiles

   Interval      Count
----------------------
[ 0.00,  0.28] |   142
( 0.28,  1.54] |   141
( 1.54,  3.31] |   141
( 3.31,  5.72] |   141
( 5.72, 10.01] |   141
(10.01, 15.51] |   141
(15.51, 22.89] |   141
(22.89, 31.57] |   141
(31.57, 42.38] |   141
(42.38, 86.24] |   142

For quick, exploratory work, the classifier object has its own plot method that takes a geodataframe as an argument:

[24]:
_ = q10.plot(gdf)
../_images/notebooks_03_choropleth_10_0.png

Back to working directly with the dataframe, we can toggle on the legend:

[25]:
import matplotlib.pyplot as plt
f, ax = plt.subplots(1, figsize=(16, 9))
gdf.assign(cl=q10.yb).plot(column='cl', categorical=True, \
        k=10, cmap='OrRd', linewidth=0.1, ax=ax, \
        edgecolor='white', legend=True)
ax.set_axis_off()
plt.show()
../_images/notebooks_03_choropleth_12_0.png

Here we see the 10 classes, but without more specific information on the legend, the user has to know that 0 is the first declile and 9 the 10th. We also do not know the values that define these classes.

We can rectify this as follows:

[27]:
q10.get_legend_classes()
[27]:
['[ 0.00,  0.28]',
 '( 0.28,  1.54]',
 '( 1.54,  3.31]',
 '( 3.31,  5.72]',
 '( 5.72, 10.01]',
 '(10.01, 15.51]',
 '(15.51, 22.89]',
 '(22.89, 31.57]',
 '(31.57, 42.38]',
 '(42.38, 86.24]']
[28]:
mapping = dict([(i,s) for i,s in enumerate(q10.get_legend_classes())])
[29]:
mapping
[29]:
{0: '[ 0.00,  0.28]',
 1: '( 0.28,  1.54]',
 2: '( 1.54,  3.31]',
 3: '( 3.31,  5.72]',
 4: '( 5.72, 10.01]',
 5: '(10.01, 15.51]',
 6: '(15.51, 22.89]',
 7: '(22.89, 31.57]',
 8: '(31.57, 42.38]',
 9: '(42.38, 86.24]'}
[30]:
def replace_legend_items(legend, mapping):
    for txt in legend.texts:
        for k,v in mapping.items():
            if txt.get_text() == str(k):
                txt.set_text(v)

import matplotlib.pyplot as plt
f, ax = plt.subplots(1, figsize=(16, 9))
gdf.assign(cl=q10.yb).plot(column='cl', categorical=True, \
        k=10, cmap='OrRd', linewidth=0.1, ax=ax, \
        edgecolor='white', legend=True,
                          legend_kwds={'loc': 'lower right'})
ax.set_axis_off()
replace_legend_items(ax.get_legend(), mapping)
plt.show()
../_images/notebooks_03_choropleth_17_0.png

Interactive Exploration of Choropleth Classification

Next, we develop a small application that relies on mapclassify together with palettable and ipywidgets to explore the choice of:

  • classification method

  • number of classes

  • colormap

[33]:
from mapclassify import color
import mapclassify
from ipywidgets import interact, Dropdown, RadioButtons, IntSlider, VBox, HBox, FloatSlider, Button, Label

def replace_legend_items(legend, mapping):
    for txt in legend.texts:
        for k,v in mapping.items():
            if txt.get_text() == str(k):
                txt.set_text(v)

k_classifiers = {
    'equal_interval': mapclassify.EqualInterval,
    'fisher_jenks': mapclassify.FisherJenks,
    'jenks_caspall': mapclassify.JenksCaspall,
    'jenks_caspall_forced': mapclassify.JenksCaspallForced,
    'maximum_breaks': mapclassify.MaximumBreaks,
    'natural_breaks': mapclassify.NaturalBreaks,
    'quantiles': mapclassify.Quantiles,
    }
def k_values(ctype, cmap):
    k = list(mapclassify.color.colorbrewer.COLOR_MAPS[ctype][cmap].keys())
    return list(map(int, k))

def update_map(method='quantiles', k=5, cmap='Blues'):
    classifier = k_classifiers[method](gdf.BLK90, k=k)
    mapping = dict([(i,s) for i,s in enumerate(classifier.get_legend_classes())])
    #print(classifier)
    f, ax = plt.subplots(1, figsize=(16, 9))
    gdf.assign(cl=classifier.yb).plot(column='cl', categorical=True, \
        k=k, cmap=cmap, linewidth=0.1, ax=ax, \
        edgecolor='grey', legend=True, \
        legend_kwds={'loc': 'lower right'})
    ax.set_axis_off()
    ax.set_title("Pct Black 1990")
    replace_legend_items(ax.get_legend(), mapping)

    plt.show()



data_type = RadioButtons(options=['Sequential', 'Diverging', 'Qualitative'])

bindings = {'Sequential': range(3,9+1),
            'Diverging': range(3,11+1),
            'Qualitative': range(3,12+1)}

cmap_bindings = {'Sequential': list(color.sequential.keys()),
                 'Diverging': list(color.diverging.keys()),
                 'Qualitative': list(color.qualitative.keys())}

class_val = Dropdown(options=bindings[data_type.value])
cmap_val = Dropdown(options=cmap_bindings[data_type.value])

def type_change(change):
    class_val.options = bindings[change['new']]
    cmap_val.options = cmap_bindings[change['new']]

def cmap_change(change):
    cmap=change['new']
    ctype = data_type.value
    k = k_values(ctype, cmap)
    class_val.options = k

data_type.observe(type_change, names=['value'])
cmap_val.observe(cmap_change, names=['value'])


from ipywidgets import Output, Tab
out = Output()
t = Tab()
t.children = [out]
#t

# In this case, the interact function must be defined after the conditions stated above...
# therefore, the k now depends on the radio button

with out:
    interact(update_map, method=list(k_classifiers.keys()), cmap=cmap_val, k = class_val)

display(VBox([data_type, out]))


Chainging the type of colormap (sequential, diverging, qualitative) will update the options for the available color maps (cmap). Changining any of the values using the dropdowns will update the classification and the resulting choropleth map.

It is important to note that the example variable is best portrayed with the sequential colormaps. The other two types of colormaps are included for demonstration purposes only.