From f4fa69eae02cd8990849a929b063a48d366447a7 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann <36797143+martinfleis@users.noreply.github.com> Date: Sat, 8 Dec 2018 21:36:45 +0000 Subject: [PATCH] Use mapclassify instead of PySAL due to change of PySAL structure. (#872) --- ci/travis/27-dev.yaml | 2 +- ci/travis/27-latest-conda-forge.yaml | 2 +- ci/travis/27-latest-defaults.yaml | 2 +- ci/travis/27-pd020.yaml | 2 +- ci/travis/35-minimal.yaml | 2 +- ci/travis/36-pd020.yaml | 2 +- ci/travis/36-pd022.yaml | 2 +- ci/travis/37-dev.yaml | 2 +- ci/travis/37-latest-conda-forge.yaml | 2 +- ci/travis/37-latest-defaults.yaml | 2 +- geopandas/plotting.py | 58 +++++++++++++++++----------- geopandas/tests/test_plotting.py | 22 +++++------ requirements.test.txt | 2 +- 13 files changed, 56 insertions(+), 46 deletions(-) diff --git a/ci/travis/27-dev.yaml b/ci/travis/27-dev.yaml index f00e969..eb07e54 100644 --- a/ci/travis/27-dev.yaml +++ b/ci/travis/27-dev.yaml @@ -19,7 +19,6 @@ dependencies: - rtree - matplotlib - descartes - - pysal #- geopy - SQLalchemy - psycopg2 @@ -28,3 +27,4 @@ dependencies: - git+https://github.com/pydata/pandas.git - codecov - geopy + - mapclassify==1.0.1 diff --git a/ci/travis/27-latest-conda-forge.yaml b/ci/travis/27-latest-conda-forge.yaml index 5f9cb00..a7be152 100644 --- a/ci/travis/27-latest-conda-forge.yaml +++ b/ci/travis/27-latest-conda-forge.yaml @@ -18,7 +18,7 @@ dependencies: - rtree - matplotlib - descartes - - pysal + - mapclassify==1.0.1 #- geopy - SQLalchemy - psycopg2 diff --git a/ci/travis/27-latest-defaults.yaml b/ci/travis/27-latest-defaults.yaml index 1888b16..c0b199c 100644 --- a/ci/travis/27-latest-defaults.yaml +++ b/ci/travis/27-latest-defaults.yaml @@ -18,7 +18,6 @@ dependencies: - rtree - matplotlib - descartes - - pysal #- geopy - SQLalchemy - psycopg2 @@ -26,3 +25,4 @@ dependencies: - pip: - codecov - geopy + - mapclassify==1.0.1 diff --git a/ci/travis/27-pd020.yaml b/ci/travis/27-pd020.yaml index 929616c..03340f2 100644 --- a/ci/travis/27-pd020.yaml +++ b/ci/travis/27-pd020.yaml @@ -20,8 +20,8 @@ dependencies: - rtree - matplotlib==2.0.2 - descartes - - pysal - geopy - SQLalchemy - psycopg2 - libspatialite + - mapclassify==1.0.1 diff --git a/ci/travis/35-minimal.yaml b/ci/travis/35-minimal.yaml index c191c07..87ed3c3 100644 --- a/ci/travis/35-minimal.yaml +++ b/ci/travis/35-minimal.yaml @@ -18,7 +18,7 @@ dependencies: - rtree - matplotlib==1.5.3 - descartes - - pysal + - mapclassify - geopy - SQLalchemy - psycopg2 diff --git a/ci/travis/36-pd020.yaml b/ci/travis/36-pd020.yaml index 7556ab2..34ee5d4 100644 --- a/ci/travis/36-pd020.yaml +++ b/ci/travis/36-pd020.yaml @@ -17,7 +17,7 @@ dependencies: - rtree - matplotlib==1.5.3 - descartes - - pysal + - mapclassify - geopy - SQLalchemy - psycopg2 diff --git a/ci/travis/36-pd022.yaml b/ci/travis/36-pd022.yaml index 9adc938..140890d 100644 --- a/ci/travis/36-pd022.yaml +++ b/ci/travis/36-pd022.yaml @@ -17,7 +17,6 @@ dependencies: - rtree - matplotlib==2.0.2 - descartes - - pysal #- geopy - SQLalchemy - psycopg2 @@ -25,3 +24,4 @@ dependencies: - pip: - codecov - geopy + - mapclassify diff --git a/ci/travis/37-dev.yaml b/ci/travis/37-dev.yaml index 727345d..0b64f2d 100644 --- a/ci/travis/37-dev.yaml +++ b/ci/travis/37-dev.yaml @@ -18,7 +18,6 @@ dependencies: - rtree - matplotlib - descartes - - pysal #- geopy - SQLalchemy - psycopg2 @@ -28,3 +27,4 @@ dependencies: - git+https://github.com/pydata/pandas.git - codecov - geopy + - mapclassify diff --git a/ci/travis/37-latest-conda-forge.yaml b/ci/travis/37-latest-conda-forge.yaml index f40de82..c7a7ff1 100644 --- a/ci/travis/37-latest-conda-forge.yaml +++ b/ci/travis/37-latest-conda-forge.yaml @@ -17,7 +17,7 @@ dependencies: - rtree - matplotlib - descartes - - pysal + - mapclassify - geopy - SQLalchemy - psycopg2 diff --git a/ci/travis/37-latest-defaults.yaml b/ci/travis/37-latest-defaults.yaml index 0cf0ba8..ef7801d 100644 --- a/ci/travis/37-latest-defaults.yaml +++ b/ci/travis/37-latest-defaults.yaml @@ -17,7 +17,6 @@ dependencies: - rtree - matplotlib - descartes - - pysal #- geopy - SQLalchemy - psycopg2 @@ -25,3 +24,4 @@ dependencies: - pip: - codecov - geopy + - mapclassify diff --git a/geopandas/plotting.py b/geopandas/plotting.py index 181a116..0469b90 100644 --- a/geopandas/plotting.py +++ b/geopandas/plotting.py @@ -26,7 +26,7 @@ def _flatten_multi_geoms(geoms, colors=None): colors = [None] * len(geoms) components, component_colors = [], [] - + if not geoms.geom_type.str.startswith('Multi').any(): return geoms, colors @@ -349,10 +349,10 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, legend : bool (default False) Plot a legend. Ignored if no `column` is given, or if `color` is given. scheme : str (default None) - Name of a choropleth classification scheme (requires PySAL). - A pysal.esda.mapclassify.Map_Classifier object will be used - under the hood. Supported schemes: 'Equal_interval', 'Quantiles', - 'Fisher_Jenks' + Name of a choropleth classification scheme (requires mapclassify). + A mapclassify.Map_Classifier object will be used + under the hood. Supported schemes: 'Quantiles', + 'Equal_Interval', 'Fisher_Jenks', 'Fisher_Jenks_Sampled' k : int (default 5) Number of classes (ignored if scheme is None) vmin : None or float (default None) @@ -445,7 +445,7 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, values = np.array([valuemap[k] for k in values]) if scheme is not None: - binning = __pysal_choro(values, scheme, k=k) + binning = _mapclassify_choro(values, scheme, k=k) # set categorical to True for creating the legend categorical = True binedges = [values.min()] + binning.bins.tolist() @@ -513,17 +513,18 @@ def plot_dataframe(df, column=None, cmap=None, color=None, ax=None, return ax -def __pysal_choro(values, scheme, k=5): +def _mapclassify_choro(values, scheme, k=5): """ - Wrapper for choropleth schemes from PySAL for use with plot_dataframe + Wrapper for choropleth schemes from mapclassify for use with plot_dataframe Parameters ---------- values Series to be plotted scheme : str - One of pysal.esda.mapclassify classification schemes - Options are 'Equal_interval', 'Quantiles', 'Fisher_Jenks' + One of mapclassify classification schemes + Options are 'Quantiles', 'Equal_Interval', 'Fisher_Jenks', + 'Fisher_Jenks_Sampled' k : int number of classes (2 <= k <=9) @@ -535,17 +536,28 @@ def __pysal_choro(values, scheme, k=5): """ 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 - scheme = scheme.lower() - if scheme not in schemes: - raise ValueError("Invalid scheme. Scheme must be in the" - " set: %r" % schemes.keys()) - binning = schemes[scheme](values, k) - return binning + from mapclassify import ( + Quantiles, Equal_Interval, Fisher_Jenks, + Fisher_Jenks_Sampled) except ImportError: - raise ImportError("PySAL is required to use the 'scheme' keyword") + try: + from mapclassify.api import ( + Quantiles, Equal_Interval, Fisher_Jenks, + Fisher_Jenks_Sampled) + except ImportError: + raise ImportError( + "The 'mapclassify' package is required to use the 'scheme' " + "keyword") + + schemes = {} + schemes['equal_interval'] = Equal_Interval + schemes['quantiles'] = Quantiles + schemes['fisher_jenks'] = Fisher_Jenks + schemes['fisher_jenks_sampled'] = Fisher_Jenks_Sampled + + scheme = scheme.lower() + if scheme not in schemes: + raise ValueError("Invalid scheme. Scheme must be in the" + " set: %r" % schemes.keys()) + binning = schemes[scheme](values, k) + return binning diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py index cf370c0..cc79826 100644 --- a/geopandas/tests/test_plotting.py +++ b/geopandas/tests/test_plotting.py @@ -12,6 +12,7 @@ from shapely.affinity import rotate from shapely.geometry import MultiPolygon, Polygon, LineString, Point, MultiPoint from geopandas import GeoSeries, GeoDataFrame, read_file +from geopandas.datasets import get_path import pytest @@ -386,39 +387,36 @@ class TestNonuniformGeometryPlotting: assert ax.collections[2].get_sizes() == [10] -class TestPySALPlotting: +class TestMapclassifyPlotting: @classmethod def setup_class(cls): - try: - import pysal as ps - except ImportError: - raise pytest.skip("PySAL is not installed") - - pth = ps.examples.get_path("columbus.shp") + pytest.importorskip('mapclassify') + pth = get_path('naturalearth_lowres') cls.df = read_file(pth) cls.df['NEGATIVES'] = np.linspace(-10, 10, len(cls.df.index)) def test_legend(self): with warnings.catch_warnings(record=True) as _: # don't print warning - # warning coming from pysal / scipy.stats - ax = self.df.plot(column='CRIME', scheme='QUANTILES', k=3, + # warning coming from scipy.stats + ax = self.df.plot(column='pop_est', scheme='QUANTILES', k=3, cmap='OrRd', legend=True) labels = [t.get_text() for t in ax.get_legend().get_texts()] - expected = [u'0.18 - 26.07', u'26.07 - 41.97', u'41.97 - 68.89'] + expected = [u'-99.00 - 4579438.67', u'4579438.67 - 16639804.33', + u'16639804.33 - 1338612970.00'] assert labels == expected def test_negative_legend(self): ax = self.df.plot(column='NEGATIVES', scheme='FISHER_JENKS', k=3, cmap='OrRd', legend=True) labels = [t.get_text() for t in ax.get_legend().get_texts()] - expected = [u'-10.00 - -3.33', u'-3.33 - 3.33', u'3.33 - 10.00'] + expected = [u'-10.00 - -3.41', u'-3.41 - 3.30', u'3.30 - 10.00'] assert labels == expected def test_invalid_scheme(self): with pytest.raises(ValueError): scheme = 'invalid_scheme_*#&)(*#' - self.df.plot(column='CRIME', scheme=scheme, k=3, + self.df.plot(column='gdp_md_est', scheme=scheme, k=3, cmap='OrRd', legend=True) diff --git a/requirements.test.txt b/requirements.test.txt index bf7a8c0..fa1755e 100644 --- a/requirements.test.txt +++ b/requirements.test.txt @@ -8,4 +8,4 @@ pytest>=3.1.0 pytest-cov codecov rtree>=0.8 -pysal +mapclassify