Mapping using Pysal and Folium: Example


In [1]:
import pysal as ps
import geojson as gj
import folium_mapping as fm

First, we need to convert the data into a JSON format. JSON, short for "Javascript Serialized Object Notation," is a simple and effective way to represent objects in a digital environment. For geographic information, the GeoJSON standard defines how to represent geographic information in JSON format. Python programmers may be more comfortable thinking of JSON data as something akin to a standard Python dictionary.


In [2]:
filepath = ps.examples.get_path('south.shp')[:-4]

In [3]:
shp = ps.open(filepath + '.shp')
dbf = ps.open(filepath + '.dbf')

In [4]:
js = fm.build_features(shp, dbf)

Just to show, this constructs a dictionary with the following keys:


In [5]:
js.keys()


Out[5]:
['type', 'bbox', 'features']

In [6]:
js.type


Out[6]:
'FeatureCollection'

In [7]:
js.bbox


Out[7]:
[-106.6495132446289, 24.95596694946289, -75.0459976196289, 40.63713836669922]

In [8]:
js.features[0]


Out[8]:
{"bbox": [-80.6688232421875, 40.39815902709961, -80.52220916748047, 40.63713836669922], "geometry": {"coordinates": [[[-80.6280517578125, 40.39815902709961], [-80.60203552246094, 40.480472564697266], [-80.62545776367188, 40.504398345947266], [-80.6336441040039, 40.53913879394531], [-80.6688232421875, 40.568214416503906], [-80.66793060302734, 40.58207321166992], [-80.63754272460938, 40.61391830444336], [-80.61175537109375, 40.619998931884766], [-80.57462310791016, 40.615909576416016], [-80.52220916748047, 40.63713836669922], [-80.52456665039062, 40.47871780395508], [-80.52377319335938, 40.4029655456543], [-80.6280517578125, 40.39815902709961]]], "type": "Polygon"}, "id": null, "properties": {"BLK60": 3.839454752, "BLK70": 3.2554278095, "BLK80": 2.5607402642, "BLK90": 2.5572616581, "CNTY_FIPS": "029", "COFIPS": 29, "DNL60": 6.1681225056, "DNL70": 6.1714993547, "DNL80": 6.1714631077, "DNL90": 6.0508978146, "DV60": 2.2779893943, "DV70": 2.5591397849, "DV80": 5.0619350519, "DV90": 7.2636377003, "FH60": 9.9812973718, "FH70": 7.8, "FH80": 9.7857968181, "FH90": 12.604551644, "FIPS": "54029", "FIPSNO": 54029, "FP59": 9.6, "FP69": 5.9, "FP79": 6.5327526442, "FP89": 10.17311807, "GI59": 0.2236450331, "GI69": 0.2953773833, "GI79": 0.3322512119, "GI89": 0.3639335641, "HC60": 0.6666666667, "HC70": 1.6666666667, "HC80": 2.6666666667, "HC90": 0.3333333333, "HR60": 1.6828642349, "HR70": 4.1929776011, "HR80": 6.5977204876, "HR90": 0.9460827444, "MA60": 28.9, "MA70": 30.0, "MA80": 31.4, "MA90": 37.7, "MFIL59": 8.8410143105, "MFIL69": 9.2471543451, "MFIL79": 10.073356901, "MFIL89": 10.327970666, "NAME": "Hancock", "PO60": 39615, "PO70": 39749, "PO80": 40418, "PO90": 35233, "POL60": 10.586963113, "POL70": 10.590339963, "POL80": 10.607030509, "POL90": 10.469738422, "PS60": 1.218684208, "PS70": 1.1368342185, "PS80": 1.0385705291, "PS90": 0.8964534429, "RD60": -1.394676863, "RD70": -1.307438562, "RD80": -1.159302086, "RD90": -0.399028376, "SOUTH": 1, "STATE_FIPS": "54", "STATE_NAME": "West Virginia", "STFIPS": 54, "UE60": 3.1, "UE70": 2.7, "UE80": 7.0763827919, "UE90": 6.8578070515}, "type": "Feature"}

Then, we write the json to a file.


In [9]:
with open('./example.json', 'w') as out:
    gj.dump(js, out)

Mapping

Let's look at the columns that we're going to map.


In [11]:
js.features[0].properties.keys()[0:5]


Out[11]:
['HR90', 'PS90', 'FH80', 'HC60', 'FP79']

We can map these attributes by calling them as commands to the choropleth mapping function.

Here's the simplest mapping command:


In [12]:
fm.choropleth_map('./example.json', 'FIPS', 'HR90')


Out[12]:

This produces a map using default classifications and color schemes and saves it to an html file. We set the function to have sane defaults. However, if the user wants to have more control, we have many options available.

There are arguments to change classification scheme:


In [13]:
fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles')


Out[13]:

Most PySAL classifiers are supprorted.

Base Map Type


In [15]:
fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Jenks Caspall', tiles='Stamen Toner', save=True)


Out[15]:

We support the entire range of builtin basemap types in Folium, but custom tilesets from MapBox are not supported yet.

Color Scheme


In [16]:
fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Jenks Caspall', tiles='Stamen Toner', fill_color = 'PuBuGn', save=True)


Out[16]:

All color schemes are Color Brewer and simply pass through to Folium on execution.

Class numbers


In [18]:
fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Equal Interval', classes=6, tiles='Stamen Toner', fill_color='PuBuGn',save=True)


Out[18]:

Folium supports up to 6 classes.

Standardization


In [19]:
fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles', std='HR80' , tiles='Stamen Toner', fill_color='PuBuGn',save=True)


Out[19]:

The goal of the std argument was to implement a very flexible transformation argument that could take functions, fields, and static items. As of now, it only supports float/int scaling and division by another field, which would support the mapping of spatial rates or population rates.

Conclusion

Overall, the function neatly encapsulates three components:

  • plumbing large lists of arguments together to simplify mapping from JSON files
  • searching for sane defaults (bounding box & zoom level)
  • implementing classification and standardization useful for choropleth mapping

Due to some pretty strong limitations in Folium itself, its usefulness for rich, dynamic, and interactive visualizations built from small, atomic functions is probably limited. It would need to be extended quite significantly (or linked with even more packages with more fickle APIs) to usefully generate mixed-mode scientific visualizations. With Folium's natural linkages to Pandas, mapping derived quantites is also possible.

But, it's really simple to map quickly from PySAL:


In [22]:
with open('example2.json', 'w') as out:
    gj.dump(fm.build_features(ps.open(filepath + '.shp'), ps.open(filepath + '.dbf')), out)
fm.choropleth_map('example2.json', 'FIPS', 'HR70')


Out[22]: