Repository: geopandas/geopandas

Issue:
control on custom rounding for decimal values in plot_dataframe
In a choropleth map based on a geodataframe we are adding a legend as was raised in this SO [question](https://stackoverflow.com/questions/31755886/choropleth-map-from-geopandas-geodatafame). Result looks like this:
![image](https://user-images.githubusercontent.com/5186265/73940335-9145b300-48eb-11ea-9272-522740e065c0.png)

Since we are using custom user defined bins it would be logical to also have the binedges in the legend correspond to the number of decimals in the custom bins.

I cannot find any method to control this legend settings. I found the following hardcoded line ([source](https://github.com/geopandas/geopandas/blob/master/geopandas/plotting.py#L585:587)) in the geopandas github page:
```
categories = [
    "{0:.2f} - {1:.2f}".format(binedges[i], binedges[i + 1])
    for i in range(len(binedges) - 1)
]
```
Which seems to confirm that this is currently not controllable?
ENH: choropleth legend formatting
This is using master of [mapclassify](https://github.com/sjsrey/mapclassify) where some enhancements for legend handling are being considered. The motivation is to be more explicit in the formatting of the legends from these classifiers to have their definition be unambiguous.

I've been exploring their use with geopandas and am opening this to get feedback/ideas.

Below are excerpts from the [example notebook](https://github.com/sjsrey/geopandas/blob/mcm/examples/choro_legends.ipynb):


![2019-08-11-1034-1565544885-sel](https://user-images.githubusercontent.com/118042/62837388-b058d600-bc23-11e9-8879-20a30e87bbe4.png)

![2019-08-11-1043-1565545387-sel](https://user-images.githubusercontent.com/118042/62837484-d763d780-bc24-11e9-871d-091f4fe72a46.png)

![2019-08-11-1047-1565545623-sel](https://user-images.githubusercontent.com/118042/62837533-683ab300-bc25-11e9-8e91-56b06e12da68.png)


Related changes to geopandas to incorporate these enhancements are [here](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1).  

## Release timing note
In terms of release cadence, the pysal meta package is back on a six month release cycle (end of january and july). mapclassify can be done more frequently. When the meta package is released, it includes the stable version of mapclassify at that point in time.






Discussion:

I like this a lot. Both clarification of intervals and formatting of numbers.

Regarding your changes to make this possible, if I am looking correctly, there are only a couple of lines, right? Can you, before making a PR, disable your automatic linter which is changing much more than this? 

If I understand correctly your timing note, time to implement this change will come after the end of January (to have it supported by both stable mapclassify and pysal)?

Thanks for this!
> I like this a lot. Both clarification of intervals and formatting of numbers.
> 

Cool.

> Regarding your changes to make this possible, if I am looking correctly, there are only a couple of lines, right? Can you, before making a PR, disable your automatic linter which is changing much more than this?

Yes, of course. Sorry about the noise induced in the diff.

Key changes: 
- plotting.py [Line 399](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1#diff-9f920647ae0bb977ab8d642b436d8eacR399)
- plotting.py [Line 478](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1#diff-9f920647ae0bb977ab8d642b436d8eacR478)
- test_plotting.py [Line 422](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1#diff-04033cfa31e6e588e765d43278b20949R422)
- test_plotting.py [Line 430](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1#diff-04033cfa31e6e588e765d43278b20949R430)
- test_plotting.py [Line 443](https://github.com/geopandas/geopandas/compare/master...sjsrey:mcm?expand=1#diff-04033cfa31e6e588e765d43278b20949R443)
> 
> If I understand correctly your timing note, time to implement this change will come after the end of January (to have it supported by both stable mapclassify and pysal)?

That would be the easiest path. If mapclassify gets bumped before then, the meta package and mapclassify would be out of sync so that could cause problems. Handling that might add more complexity than is desired. So waiting until the next meta release might be the way to go. Also gives time to add any enhancements others might want.
> 
> Thanks for this!



Resolving patch:
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
--- a/geopandas/plotting.py
+++ b/geopandas/plotting.py
@@ -5,6 +5,8 @@
 
 import geopandas
 
+from distutils.version import LooseVersion
+
 
 def deprecated(new):
     """Helper to provide deprecation warning."""
@@ -663,11 +665,10 @@ def plot_dataframe(
         binning = _mapclassify_choro(values[~nan_idx], scheme, **classification_kwds)
         # set categorical to True for creating the legend
         categorical = True
-        binedges = [values[~nan_idx].min()] + binning.bins.tolist()
-        categories = [
-            "{0:.2f} - {1:.2f}".format(binedges[i], binedges[i + 1])
-            for i in range(len(binedges) - 1)
-        ]
+        fmt = "{:.2f}"
+        if legend_kwds is not None and "fmt" in legend_kwds:
+            fmt = legend_kwds.pop("fmt")
+        categories = binning.get_legend_classes(fmt)
         values = np.array(binning.yb)
 
     # fill values with placeholder where were NaNs originally to map them properly
@@ -742,6 +743,8 @@ def plot_dataframe(
 
         if legend_kwds is None:
             legend_kwds = {}
+        if "fmt" in legend_kwds:
+            legend_kwds.pop("fmt")
 
         from matplotlib.lines import Line2D
         from matplotlib.colors import Normalize
@@ -831,15 +834,18 @@ class identifier and the bins.
     """
     try:
         import mapclassify.classifiers as classifiers
+
     except ImportError:
-        try:
-            import pysal.viz.mapclassify.classifiers as classifiers
-        except ImportError:
-            raise ImportError(
-                "The 'mapclassify' or 'pysal' package is required to use the"
-                " 'scheme' keyword"
-            )
+        raise ImportError(
+            "The 'mapclassify' >= 2.2.0 package is required to use the 'scheme' keyword"
+        )
+    from mapclassify import __version__ as mc_version
 
+    if mc_version < LooseVersion("2.2.0"):
+        raise ImportError(
+            "The 'mapclassify' >= 2.2.0 package is required to "
+            "use the 'scheme' keyword"
+        )
     schemes = {}
     for classifier in classifiers.CLASSIFIERS:
         schemes[classifier.lower()] = getattr(classifiers, classifier)
@@ -884,10 +890,8 @@ class identifier and the bins.
             )
 
     if classification_kwds["k"] is not None:
-        try:
-            from inspect import getfullargspec as getspec
-        except ImportError:
-            from inspect import getargspec as getspec
+        from inspect import getfullargspec as getspec
+
         spec = getspec(scheme_class.__init__)
         if "k" not in spec.args:
             del classification_kwds["k"]
diff --git a/geopandas/tools/_show_versions.py b/geopandas/tools/_show_versions.py
--- a/geopandas/tools/_show_versions.py
+++ b/geopandas/tools/_show_versions.py
@@ -94,7 +94,6 @@ def _get_deps_info():
         "pyproj",
         "matplotlib",
         "mapclassify",
-        "pysal",
         "geopy",
         "psycopg2",
         "geoalchemy2",


Test patch:
diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py
--- a/geopandas/tests/test_plotting.py
+++ b/geopandas/tests/test_plotting.py
@@ -638,6 +638,19 @@ def test_colorbar_kwargs(self):
 
         assert ax.get_figure().axes[1].get_xlabel() == label_txt
 
+    def test_fmt_ignore(self):
+        # test if fmt is removed if scheme is not passed (it would raise Error)
+        # GH #1253
+
+        self.df.plot(
+            column="values",
+            categorical=True,
+            legend=True,
+            legend_kwds={"fmt": "{:.0f}"},
+        )
+
+        self.df.plot(column="values", legend=True, legend_kwds={"fmt": "{:.0f}"})
+
     def test_multipolygons_color(self):
 
         # MultiPolygons
@@ -888,10 +901,7 @@ def setup_class(cls):
         try:
             import mapclassify  # noqa
         except ImportError:
-            try:
-                import pysal  # noqa
-            except ImportError:
-                pytest.importorskip("mapclassify")
+            pytest.importorskip("mapclassify")
         pth = get_path("naturalearth_lowres")
         cls.df = read_file(pth)
         cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index))
@@ -904,9 +914,9 @@ def test_legend(self):
             )
         labels = [t.get_text() for t in ax.get_legend().get_texts()]
         expected = [
-            u"140.00 - 5217064.00",
-            u"5217064.00 - 19532732.33",
-            u"19532732.33 - 1379302771.00",
+            u"[       140.00,    5217064.00]",
+            u"(   5217064.00,   19532732.33]",
+            u"(  19532732.33, 1379302771.00]",
         ]
         assert labels == expected
 
@@ -915,7 +925,20 @@ def test_negative_legend(self):
             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.41", u"-3.41 - 3.30", u"3.30 - 10.00"]
+        expected = [u"[-10.00,  -3.41]", u"( -3.41,   3.30]", u"(  3.30,  10.00]"]
+        assert labels == expected
+
+    def test_fmt(self):
+        ax = self.df.plot(
+            column="NEGATIVES",
+            scheme="FISHER_JENKS",
+            k=3,
+            cmap="OrRd",
+            legend=True,
+            legend_kwds={"fmt": "{:.0f}"},
+        )
+        labels = [t.get_text() for t in ax.get_legend().get_texts()]
+        expected = [u"[-10,  -3]", u"( -3,   3]", u"(  3,  10]"]
         assert labels == expected
 
     @pytest.mark.parametrize("scheme", ["FISHER_JENKS", "FISHERJENKS"])
@@ -933,7 +956,7 @@ def test_classification_kwds(self):
             legend=True,
         )
         labels = [t.get_text() for t in ax.get_legend().get_texts()]
-        expected = ["140.00 - 9961396.00", "9961396.00 - 1379302771.00"]
+        expected = ["[       140.00,    9961396.00]", "(   9961396.00, 1379302771.00]"]
         assert labels == expected
 
     def test_invalid_scheme(self):
diff --git a/geopandas/tests/test_show_versions.py b/geopandas/tests/test_show_versions.py
--- a/geopandas/tests/test_show_versions.py
+++ b/geopandas/tests/test_show_versions.py
@@ -37,7 +37,6 @@ def test_get_deps_info():
     assert "pyproj" in deps_info
     assert "matplotlib" in deps_info
     assert "mapclassify" in deps_info
-    assert "pysal" in deps_info
     assert "geopy" in deps_info
     assert "psycopg2" in deps_info
     assert "geoalchemy2" in deps_info