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:
Hi @juanpac96,

thanks for opening PR. As I mentioned on SO, geopandas does `categories.sort()` which always returns alphabetical order of categories. So to do this properly, we need to do changes on our side.

However, you may do the trick now. Change your actual categories to a number representing order of each category value (`< 8` to `0`, `8 - 12` to `1` etc., probably as a new column) and then edit labels in the legend (https://stackoverflow.com/questions/54370302/changing-the-order-of-entries-for-a-geopandas-choropleth-map-legend).

Hope this helps. We will consider adding support of manual sorting of categorical values. Thanks!
This could be fixed together with #893 by enabling passing list of categories.
As mentioned on the PR, I think it should be possible to have better support for pandas' categorical dtype, and ensure we honor the order of all categories.

Resolving patch:
diff --git a/geopandas/plotting.py b/geopandas/plotting.py
--- 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
--- a/geopandas/tests/test_plotting.py
+++ b/geopandas/tests/test_plotting.py
@@ -2,6 +2,7 @@
 import warnings
 
 import numpy as np
+import pandas as pd
 
 from shapely.affinity import rotate
 from shapely.geometry import (
@@ -262,30 +263,45 @@ def test_multipoints_alpha(self):
         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"])