Reputation: 41
I'm trying to make multiple choropleth maps (one for each year in my data), filling in each polygon according to a categorical variable. I would also like to attach legends to these plots.
I read the data using geopandas:
import geopandas as gpd
import matplotlib.pyplot as plt
all_gdf = gpd.read_file('./my-data.shp')
Then I can just create separate maps by subsetting to each year in a for-loop:
years = all_gdf.year.unique()
for y in years:
fig, ax = plt.subplots()
tmp_gdf = all_gdf.query(f'year == {y}')
tmp_gdf.plot(column='col_to_plot',
legend=True, categorical=True,
ax=ax)
This produces a map for each year, but because not all categories are present for each year, I get a different color scheme for each map. For example, year 2015 might have categories a, b, c, while year 2016 might have categories a, c, d, and e, so the color for c in the 2015 map is different than the color for c in the 2016 map. I would like to make sure c is the same color for all maps.
Then I found this: https://www.earthdatascience.org/courses/earth-analytics-python/spatial-data-vector-shapefiles/python-customize-map-legends-geopandas/, and their examples seem to be exactly what I'm looking for, but following their code, my maps fail to produce legends.
color_dict = {
'a': 'red',
'b': 'blue',
'c': 'green',
'd': 'yellow',
'e': 'cyan',
'f': 'magenta'}
for y in years:
fig, ax = plt.subplots()
tmp_gdf = all_gdf.query(f'year == {y}')
for ctype, data in tmp_gdf.groupby('col_to_plot'):
color = color_dict[ctype]
data.plot(color=color, ax=ax, label=ctype)
ax.legend()
plt.show()
This produces the correct colors in the map, but it refuses to make legends.
At this point, I'm completely lost, and any help would be greatly appreciated.
Upvotes: 1
Views: 3326
Reputation: 41
Here's my solution... As an example, I'll use the world map dataset that ships with GeoPandas and plot the GDP decile each country is in.
# Import the things an setup
import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from matplotlib import cm
from matplotlib.colors import ListedColormap
sns.set_style('darkgrid')
sns.set_context('notebook')
# Read the data and calculate the deciles
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world = world[(world['name'] != 'Antarctica')
& ~world['continent'].str.contains("Seven seas")]
world['gdp_decile'] = pd.qcut(world['gdp_md_est'], 10,
labels=[q+1 for q in range(10)])
# Plot the world map
fig, ax = plt.subplots(figsize=(13, 10))
world.plot(column='gdp_decile', categorical=True, ax=ax, legend=True,
legend_kwds={'title': 'Decile'})
ax.set(xticklabels=[], yticklabels=[])
ax.grid(False)
Now, let's plot each continent separately with the same legend colours.
# Create a colour mapping between the legend values and some colours
palette = dict(zip(sorted(world['gdp_decile'].unique()),
[cm.tab10(x) for x in range(world['gdp_decile'].nunique())]))
cm.tab10
is an example colour map. There are plenty more.
for continent, df in world.groupby('continent'):
# Need to ensure the column you're plotting is sorted
df = df.sort_values('gdp_decile')
# Create a colormap using only the colours in this group
cmap = ListedColormap([color for decile, color in palette.items()
if decile in df['gdp_decile'].unique()])
# Do the plotting!
fig, ax = plt.subplots(figsize=(10, 7))
df.plot(column='gdp_decile', categorical=True, legend=True, cmap=cmap,
ax=ax, legend_kwds={'title': 'Decile'})
ax.set(xticklabels=[], yticklabels=[], title=continent)
ax.grid(False)
plt.show()
I hope that helps!
Upvotes: 4