#!/usr/bin/env python # coding: utf-8 # ## Choropleth classification with PySAL and GeoPandas - With legend # # This is a modified version of http://nbviewer.ipython.org/github/geopandas/geopandas/blob/master/examples/choropleths.ipynb # with the patch to add a legend to those figures. # In[1]: get_ipython().run_line_magic('matplotlib', 'inline') # In[2]: import geopandas as gp # In[3]: # we use PySAL for loading a test shapefile # replace this cell if you have a local shapefile and want to use GeoPandas readers import pysal as ps pth = ps.examples.get_path("columbus.shp") tracts = gp.GeoDataFrame.from_file(pth) # In[4]: ax = tracts.plot(column='CRIME', scheme='QUANTILES', k=3, colormap='OrRd') # In[5]: tracts.head() # Slightly adapted functions: # In[6]: def __pysal_choro(values, scheme, k=5): """ Wrapper for choropleth schemes from PySAL for use with plot_dataframe Parameters ---------- values Series to be plotted scheme pysal.esda.mapclassify classificatin scheme ['Equal_interval'|'Quantiles'|'Fisher_Jenks'] k number of classes (2 <= k <=9) Returns ------- values Series with values replaced with class identifier if PySAL is available, otherwise the original values are used """ try: from pysal.esda.mapclassify import Quantiles, Equal_Interval, Fisher_Jenks schemes = {} schemes['equal_interval'] = Equal_Interval schemes['quantiles'] = Quantiles schemes['fisher_jenks'] = Fisher_Jenks s0 = scheme scheme = scheme.lower() if scheme not in schemes: scheme = 'quantiles' print('Unrecognized scheme: ', s0) print('Using Quantiles instead') if k < 2 or k > 9: print('Invalid k: ', k) print('2<=k<=9, setting k=5 (default)') k = 5 binning = schemes[scheme](values, k) values = binning.yb except ImportError: print('PySAL not installed, setting map to default') return binning # In[7]: # adding linewidth parameter def plot_polygon(ax, poly, facecolor='red', edgecolor='black', alpha=0.5, linewidth=1): """ Plot a single Polygon geometry """ from descartes.patch import PolygonPatch a = np.asarray(poly.exterior) # without Descartes, we could make a Patch of exterior ax.add_patch(PolygonPatch(poly, facecolor=facecolor, alpha=alpha)) ax.plot(a[:, 0], a[:, 1], color=edgecolor, linewidth=linewidth) for p in poly.interiors: x, y = zip(*p.coords) ax.plot(x, y, color=edgecolor, linewidth=linewidth) def plot_multipolygon(ax, geom, facecolor='red', edgecolor='black', alpha=0.5, linewidth=1): """ Can safely call with either Polygon or Multipolygon geometry """ if geom.type == 'Polygon': plot_polygon(ax, geom, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linewidth=linewidth) elif geom.type == 'MultiPolygon': for poly in geom.geoms: plot_polygon(ax, poly, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linewidth=linewidth) # In[8]: import numpy as np from geopandas.plotting import (plot_linestring, plot_point, norm_cmap) def plot_dataframe(s, column=None, colormap=None, alpha=0.5, categorical=False, legend=False, axes=None, scheme=None, k=5, linewidth=1): """ Plot a GeoDataFrame Generate a plot of a GeoDataFrame with matplotlib. If a column is specified, the plot coloring will be based on values in that column. Otherwise, a categorical plot of the geometries in the `geometry` column will be generated. Parameters ---------- GeoDataFrame The GeoDataFrame to be plotted. Currently Polygon, MultiPolygon, LineString, MultiLineString and Point geometries can be plotted. column : str (default None) The name of the column to be plotted. categorical : bool (default False) If False, colormap will reflect numerical values of the column being plotted. For non-numerical columns (or if column=None), this will be set to True. colormap : str (default 'Set1') The name of a colormap recognized by matplotlib. alpha : float (default 0.5) Alpha value for polygon fill regions. Has no effect for lines or points. legend : bool (default False) Plot a legend (Experimental; currently for categorical plots only) axes : matplotlib.pyplot.Artist (default None) axes on which to draw the plot scheme : pysal.esda.mapclassify.Map_Classifier Choropleth classification schemes k : int (default 5) Number of classes (ignored if scheme is None) Returns ------- matplotlib axes instance """ import matplotlib.pyplot as plt from matplotlib.lines import Line2D from matplotlib.colors import Normalize from matplotlib import cm if column is None: return plot_series(s.geometry, colormap=colormap, alpha=alpha, axes=axes) else: if s[column].dtype is np.dtype('O'): categorical = True if categorical: if colormap is None: colormap = 'Set1' categories = list(set(s[column].values)) categories.sort() valuemap = dict([(k, v) for (v, k) in enumerate(categories)]) values = [valuemap[k] for k in s[column]] else: values = s[column] if scheme is not None: binning = __pysal_choro(values, scheme, k=k) values = binning.yb # set categorical to True for creating the legend categorical = True binedges = [binning.yb.min()] + binning.bins.tolist() categories = ['{0:.2f} - {1:.2f}'.format(binedges[i], binedges[i+1]) for i in range(len(binedges)-1)] cmap = norm_cmap(values, colormap, Normalize, cm) if axes == None: fig = plt.gcf() fig.add_subplot(111, aspect='equal') ax = plt.gca() else: ax = axes for geom, value in zip(s.geometry, values): if geom.type == 'Polygon' or geom.type == 'MultiPolygon': plot_multipolygon(ax, geom, facecolor=cmap.to_rgba(value), alpha=alpha, linewidth=linewidth) elif geom.type == 'LineString' or geom.type == 'MultiLineString': plot_multilinestring(ax, geom, color=cmap.to_rgba(value)) # TODO: color point geometries elif geom.type == 'Point': plot_point(ax, geom, color=cmap.to_rgba(value)) if legend: if categorical: patches = [] for value, cat in enumerate(categories): patches.append(Line2D([0], [0], linestyle="none", marker="o", alpha=alpha, markersize=10, markerfacecolor=cmap.to_rgba(value))) ax.legend(patches, categories, numpoints=1, loc='best') else: # TODO: show a colorbar raise NotImplementedError plt.draw() return ax # In[9]: ax = plot_dataframe(tracts, column='CRIME', scheme='QUANTILES', k=3, colormap='OrRd', legend=True) # In[10]: # thicker linewidth for polygons ax = plot_dataframe(tracts, column='CRIME', scheme='QUANTILES', k=3, colormap='OrRd', legend=True, linewidth=2.0) # In[11]: tracts.plot(column='CRIME', scheme='equal_interval', k=7, colormap='OrRd') # In[12]: plot_dataframe(tracts, column='CRIME', scheme='equal_interval', k=7, colormap='OrRd', legend=True) # In[ ]: