问题
I am plotting a certain categorical value over the map of a city. The line of code I use to plot is the following:
fig = plt.figure(figsize=(12, 12))
ax = plt.gca()
urban_data.plot(column="category", cmap="viridis", ax=ax, categorical=True, /
k=4, legend=True, linewidth=0.5, /
legend_kwds={'fontsize':'19', 'loc':'lower left'})
where urban data is a geopandas dataframe, and I am using matplotlib as plotting library. The argument legend_kwds
allows me to control minor things on the legend, like the position or the font size, but I cannot decide major things like, for example, the order of the entries in the legend box. In fact my categories are ranked, let's say 1-2-3-4, but I always get them displayed in a different order.
Is it possible to have more control over the legend? For example by calling it outside the gdf.plot()
function? And, if so, how do I match the colors in the legend with those in the map, which are discrete values (that I don't know exactly) of a viridis colormap?
EDIT: here is a verifiable example. Unfortunately shapefiles need other files to work, and here a geometry
(an area, not a point) column is needed, so I have to ask you to download this shpfile of the US. Everything you need is within this folder. Here's the code to reproduce the issue. The plot in output is bad because I did not care about the coordinates system here, but the important thing is the legend.
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
gdf=gpd.read_file('.../USA_adm1.shp')
clusters=np.random.randint(0,4, size=52)
gdf['cluster']=clusters
clusdict={1: 'lower-middle', 2: 'upper-middle', 3: 'upper', 0: 'lower'}
gdf['cluster']=gdf['cluster'].map(clusdict)
fig = plt.figure(figsize=(12, 12))
ax = plt.gca()
gdf.plot(column='cluster',cmap='viridis', categorical=True, legend=True, ax=ax)
回答1:
The bad news is that categories in legends produced by geopandas are sorted and this is hardcoded (see source-code here).
One solution is hence to have the categorical column such that if it is sorted, it would correspond to the desired order. Using integers seems fine for that. Then one can replace the names in the legend, once it is produced in the correct order.
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
gdf=gpd.read_file('data/USA_adm/USA_adm1.shp')
clusters=np.random.randint(0,4, size=52)
gdf['cluster']=clusters
clusdict={1: 'lower-middle', 2: 'upper-middle', 3: 'upper', 0: 'lower'}
fig = plt.figure(figsize=(12, 12))
ax = plt.gca()
gdf.plot(column='cluster',cmap='viridis', categorical=True, legend=True, ax=ax)
def replace_legend_items(legend, mapping):
for txt in legend.texts:
for k,v in mapping.items():
if txt.get_text() == str(k):
txt.set_text(v)
replace_legend_items(ax.get_legend(), clusdict)
plt.show()
回答2:
Assuming that you have 4 legends, you can do the following to set them in whatever order you like. The following code shows how to put them in the following order (using index): 0, 2, 3, 1.
Here ax
is the axis object which you have define using ax = plt.gca()
handles,labels = ax.get_legend_handles_labels()
handles = [handles[0], handles[2], handles[3], handles[1]]
labels = [labels[0], labels[2], labels[3], labels[1]]
ax.legend(handles, labels)
Let me give you an example:
Default order
fig, ax = plt.subplots()
x = np.arange(5)
plt.plot(x, x, label=r'$y=x$')
plt.plot(x, 2*x, label=r'$y=2x$')
plt.plot(x, 3*x, label=r'$y=3x$')
plt.plot(x, 4*x, label=r'$y=4x$')
plt.legend(fontsize=16)
Manually changed order
fig, ax = plt.subplots()
x = np.arange(5)
plt.plot(x, x, label=r'$y=x$')
plt.plot(x, 2*x, label=r'$y=2x$')
plt.plot(x, 3*x, label=r'$y=3x$')
plt.plot(x, 4*x, label=r'$y=4x$')
handles,labels = ax.get_legend_handles_labels()
handles = [handles[0], handles[2],handles[3], handles[1]]
labels = [labels[0], labels[2], labels[3], labels[1]]
ax.legend(handles, labels, fontsize=16)
One can also use list comprehension using a pre-specified order list as
order = [0, 2, 3, 1]
handles,labels = ax.get_legend_handles_labels()
handles = [handles[i] for i in order]
labels = [labels[i] for i in order]
ax.legend(handles, labels, fontsize=16)
来源:https://stackoverflow.com/questions/54370302/changing-the-order-of-entries-for-a-geopandas-choropleth-map-legend