Repository: geopandas/geopandas

Issue:
Legend of GeoDataFrame does not sort categorical data values
I try to plot a map of temperature with geopandas in Google Colaboratory. This is my dataset:
```

id = '1LOywA8JPP6vOFDP-CBOBgkSmX7zv2pDc'

downloaded = drive.CreateFile({'id':id}) 
downloaded.GetContentFile('temp_zone.geojson')
print('The files was downloaded')

temp_zone = gpd.read_file('temp_zone.geojson')
print(temp_zone.rango.unique())
temp_zone.head()
```
And this is the output:
![image](https://user-images.githubusercontent.com/40966420/66720955-c3c12280-edc7-11e9-812f-14432eb67829.png)

How you can see when I print the column of "rango", this have not any order, so I will sort as I want:

```
ordered_temp = [ '< 8', '8 - 12', '12 - 16','16 - 20', '20 - 22', '22 - 24','24 - 26','26 - 28', '> 28' ]
temp_zone['rango'] = temp_zone.rango.astype('category',
                                        ordered=True,
                                        categories=ordered_temp)
print(temp_zone.rango.unique())
```

Finally, when I try to plot the map, the values in the legend are not sorted as wrote before in the categorical data, because the value '<8' is near the last position when this should be in the first position. The image is something like this:

![image](https://user-images.githubusercontent.com/40966420/66720993-ebb08600-edc7-11e9-9fcf-11c21b8dba46.png)


Anyone does know how to fix it?

Discussion:
martinfleis: Nice! Pandas 0.23 does not like it but considering we will not support it for much longer, I think that it is fine as it is.

Patch:
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
index 169fe39d..9fef122c 100644
--- a/geopandas/plotting.py
+++ b/geopandas/plotting.py
@@ -619,44 +619,40 @@ def plot_dataframe(
                 "The dataframe and given column have different number of rows."
             )
         else:
-            values = np.asarray(column)
+            values = column
     else:
-        values = np.asarray(df[column])
+        values = df[column]
 
-    if values.dtype is np.dtype("O"):
+    if pd.api.types.is_categorical_dtype(values.dtype):
+        if categories is not None:
+            raise ValueError(
+                "Cannot specify 'categories' when column has categorical dtype"
+            )
         categorical = True
-
-    nan_idx = pd.isna(values)
-
-    if categories:
+    elif values.dtype is np.dtype("O") or categories:
         categorical = True
 
+    nan_idx = np.asarray(pd.isna(values), dtype="bool")
+
     # Define `values` as a Series
     if categorical:
         if cmap is None:
             cmap = "tab10"
 
-        if categories is None:
-            categories = list(set(values[~nan_idx]))
-            categories.sort()
-        else:
-            if not pd.api.types.is_list_like(categories):
-                raise ValueError(
-                    "Categories must be a list-like object. Objects that are "
-                    "considered list-like are for example Python lists, tuples, sets, "
-                    "NumPy arrays, and Pandas Series."
-                )
-            missing = [x for x in values[~nan_idx] if x not in categories]
-            if missing:
-                raise ValueError(
-                    "Column contains values not listed in categories. "
-                    "Missing categories: {}.".format(missing)
-                )
+        cat = pd.Categorical(values, categories=categories)
+        categories = list(cat.categories)
+
+        # values missing in the Categorical but not in original values
+        missing = list(np.unique(values[~nan_idx & cat.isna()]))
+        if missing:
+            raise ValueError(
+                "Column contains values not listed in categories. "
+                "Missing categories: {}.".format(missing)
+            )
 
-        valuemap = dict((k, v) for (v, k) in enumerate(categories))
-        values = np.array([valuemap[k] for k in values[~nan_idx]])
+        values = cat.codes[~nan_idx]
         vmin = 0 if vmin is None else vmin
-        vmax = max(valuemap.values()) if vmax is None else vmax
+        vmax = len(categories) - 1 if vmax is None else vmax
 
     if scheme is not None:
         if classification_kwds is None:


Test patch:
diff --git a/geopandas/tests/test_plotting.py b/geopandas/tests/test_plotting.py
index 2dec35ed..88e50f70 100644
--- a/geopandas/tests/test_plotting.py
+++ b/geopandas/tests/test_plotting.py
@@ -2,6 +2,7 @@ import itertools
 import warnings
 
 import numpy as np
+import pandas as pd
 
 from shapely.affinity import rotate
 from shapely.geometry import (
@@ -262,30 +263,45 @@ class TestPointPlotting:
         with pytest.raises(TypeError):  # no list allowed for alpha
             ax = self.df2.plot(alpha=[0.7, 0.2])
 
-    def test_categorical_list(self):
-        self.df["cats"] = ["cat1", "cat2"] * 5
+    def test_categories(self):
+        self.df["cats_object"] = ["cat1", "cat2"] * 5
         self.df["nums"] = [1, 2] * 5
-        self.df["singlecat"] = ["cat2"] * 10
-        ax1 = self.df.plot("cats", legend=True)
-        ax2 = self.df.plot("singlecat", categories=["cat1", "cat2"], legend=True)
-        ax3 = self.df.plot("nums", categories=[1, 2], legend=True)
-        point_colors1 = ax1.collections[0].get_facecolors()
-        point_colors2 = ax2.collections[0].get_facecolors()
-        point_colors3 = ax3.collections[0].get_facecolors()
-        np.testing.assert_array_equal(
-            point_colors1[1], point_colors2[0], point_colors3[0]
+        self.df["singlecat_object"] = ["cat2"] * 10
+        self.df["cats"] = pd.Categorical(["cat1", "cat2"] * 5)
+        self.df["singlecat"] = pd.Categorical(
+            ["cat2"] * 10, categories=["cat1", "cat2"]
+        )
+        self.df["cats_ordered"] = pd.Categorical(
+            ["cat2", "cat1"] * 5, categories=["cat2", "cat1"]
         )
+
+        ax1 = self.df.plot("cats_object", legend=True)
+        ax2 = self.df.plot("cats", legend=True)
+        ax3 = self.df.plot("singlecat_object", categories=["cat1", "cat2"], legend=True)
+        ax4 = self.df.plot("singlecat", legend=True)
+        ax5 = self.df.plot("cats_ordered", legend=True)
+        ax6 = self.df.plot("nums", categories=[1, 2], legend=True)
+
+        point_colors1 = ax1.collections[0].get_facecolors()
+        for ax in [ax2, ax3, ax4, ax5, ax6]:
+            point_colors2 = ax.collections[0].get_facecolors()
+            np.testing.assert_array_equal(point_colors1[1], point_colors2[1])
+
         legend1 = [x.get_markerfacecolor() for x in ax1.get_legend().get_lines()]
-        legend2 = [x.get_markerfacecolor() for x in ax2.get_legend().get_lines()]
-        legend3 = [x.get_markerfacecolor() for x in ax3.get_legend().get_lines()]
-        np.testing.assert_array_equal(legend1, legend2)
-        np.testing.assert_array_equal(legend1, legend3)
+        for ax in [ax2, ax3, ax4, ax5, ax6]:
+            legend2 = [x.get_markerfacecolor() for x in ax.get_legend().get_lines()]
+            np.testing.assert_array_equal(legend1, legend2)
 
-        with pytest.raises(ValueError, match="Categories must be a list-like object."):
-            self.df.plot(column="cats", categories="non_list")
+        with pytest.raises(TypeError):
+            self.df.plot(column="cats_object", categories="non_list")
 
         with pytest.raises(
             ValueError, match="Column contains values not listed in categories."
+        ):
+            self.df.plot(column="cats_object", categories=["cat1"])
+
+        with pytest.raises(
+            ValueError, match="Cannot specify 'categories' when column has"
         ):
             self.df.plot(column="cats", categories=["cat1"])