Home > Software design >  Make geopandas colors sync across subplots
Make geopandas colors sync across subplots

Time:02-19

I am mapping data by zip code. I've pasted a screenshot of the geopandas DataFrame (geodata_mainland) below (apologies for not including it in an easily reproducible way, if anyone knows how to do that for geopandas geometry column, I am happy to update it). I have separate DataFrames called ak and hi for Alaska and Hawaii. I am using the following code to map the quartiles by zip code with insets for Alaska and Hawaii.

import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, figsize=(20,20))
geodata_mainland.plot(column='quartile', cmap='Purples', linewidth=0.1, ax=ax, edgecolor='black', legend = True,categorical=True) 
ax2 = fig.add_axes([0.1, 0.25, 0.2, 0.2])
ak.plot(column='quartile', cmap='Purples', linewidth=0.1, ax=ax2, edgecolor='black', legend = False)
ax3 = fig.add_axes([0.32, 0.3, 0.1, 0.1])
hi.plot(column='quartile', cmap='Purples', linewidth=0.1, ax=ax3, edgecolor='black', legend = False)
ax.axis('off')
ax2.set_xticks([])
ax2.set_yticks([])
ax3.set_xticks([])
ax3.set_yticks([])

It works really well except that the mapping between quartiles and colors is different for Alaska and Hawaii. Specifically, all zip codes in both states are quartile 4, but because I am plotting them separately, they are showing up as the color for quartile 1. Is is possible to sync the mapping between colors and values across subplots? I've also inserted my current map for reference. Here is a screenshot of what the data looks like.

Current map

CodePudding user response:

  • the solution is very simple, pass vmin and vmax arguments to plot()
  • have used geometry from GitHub and synthesized quartile
  • colors are correct, insets are different probably due to different geometry

solution

import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, figsize=(20, 20))
geodata_mainland.plot(
    column="quartile",
    cmap="Purples",
    linewidth=0.1,
    ax=ax,
    edgecolor="black",
    legend=True,
    categorical=True,
)
ax2 = fig.add_axes([0.1, 0.25, 0.2, 0.2])
ak.plot(
    column="quartile",
    cmap="Purples",
    vmin=1,
    vmax=4,
    linewidth=0.1,
    ax=ax2,
    edgecolor="black",
    legend=False,
)
ax3 = fig.add_axes([0.32, 0.3, 0.1, 0.1])
hi.plot(
    column="quartile",
    cmap="Purples",
    vmin=1,
    vmax=4,
    linewidth=0.1,
    ax=ax3,
    edgecolor="black",
    legend=False,
)
ax.axis("off")
ax2.set_xticks([])
ax2.set_yticks([])
ax3.set_xticks([])
ax3.set_yticks([])

enter image description here

setup

  • get geometry
  • synthesise quartile where it is 4 for AK and HI
from pathlib import Path
import requests, urllib
import pandas as pd
import numpy as np
import geopandas as gpd

url = "https://raw.githubusercontent.com/gweissman86/three_digit_zips/main/three_dig_zips/three_dig_zips"
f = Path.cwd().joinpath("ZIP3")
if not f.is_dir():
    f.mkdir()
    for e in [".cpg", ".dbf", ".prj", ".shp", ".shx"]:
        r = requests.get(f"{url}{e}", stream=True, headers={"User-Agent": "XY"})
        with open(f.joinpath(f"three_dig_zips{e}"), "wb") as fd:
            for chunk in r.iter_content(chunk_size=128):
                fd.write(chunk)


gdf = gpd.read_file(list(f.glob("*.shp"))[0])

gdf_states = gpd.read_file(
    "https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/50m/cultural/ne_50m_admin_1_states_provinces.json"
).loc[lambda d: d["iso_a2"].eq("US"), ["iso_3166_2", "name", "region_sub", "geometry"]]

gdf = (
    gdf.sjoin(gdf_states.to_crs(gdf.crs), how="left")
    .assign(quartile=lambda d: np.random.randint(1, 5, len(d)))
    .reset_index(drop=True)
)
gdf.loc[gdf["iso_3166_2"].isin(["US-AK", "US-HI"]), "quartile"] = 4

# final setup ...
geodata_mainland = gdf.loc[~gdf["iso_3166_2"].isin(["US-AK", "US-HI"])]
ak = gdf.loc[gdf["iso_3166_2"].isin(["US-AK"])]
hi = gdf.loc[gdf["iso_3166_2"].isin(["US-HI"])]

CodePudding user response:

There might be another way, but I found that it works to define a custom colormap. For example:

from matplotlib import colors
cmap = colors.ListedColormap([(0,1,0,0.25),(0, 0.75, 0.25,0.5),(0, 0.25, 0.75,0.75),(0,0,1,1)] )
boundaries = [0,1.1,2.1,3.1,4.1]
norm = colors.BoundaryNorm(boundaries, cmap.N, clip=True)

and then define a custom legend using the colormap:

from matplotlib.patches import Patch
legend_elements = [Patch(facecolor=(0,1,0,0.25), edgecolor='white',
                         label='First Quartile'),Patch(facecolor=(0, 0.75, 0.25,0.5), edgecolor='white',
                         label='Second Quartile'),Patch(facecolor=(0, 0.25, 0.75,0.75), edgecolor='white',
                         label='Third Quartile'),Patch(facecolor=(0,0,1,1), edgecolor='white',
                         label='Fourth Quartile')]
ax.legend(handles=legend_elements)
  • Related