I am trying to create a custom coloring for an animated choropleth map. I am using Plotly express and my dataframe looks like this.
where I am plotting the values on each region (region code=K_KRAJ, region name=N_KRAJ) and my animation is over the variables.
The values are in percentages so the min is 0 and max is 1. I want to divide the colors into 6 parts with exactly the midpoints as written here in color_continous_scale
fig = px.choropleth(df_anim,
locations="K_KRAJ",
featureidkey="properties.K_KRAJ",
geojson=regions_json,
color="value",
hover_name="N_KRAJ",
color_continuous_scale=[(0.0, "#e5e5e5"), (0.0001, "#e5e5e5"),
(0.0001, "#ffe5f0"), (0.0075, "#ffe5f0"),
(0.0075, "#facfdf"), (0.01, "#facfdf"),
(0.01, "#f3b8ce"), (0.025, "#f3b8ce"),
(0.025, "#eca2bf"), (0.05, "#eca2bf"),
(0.05, "#e37fb1"), (1, "#e37fb1")
],
animation_frame="variable"
)
fig.update_geos(fitbounds="locations", visible=False)
fig.show()
Unfortunately, that creates a wrong map like this
the second map which is almost correct was created using the largest value as 100% and mathematically finding the midpoints. Even though this is very close to being correct, there can always be numerical mistakes and I would rather use the code shown above if it worked correctly.
the almost correct one was created like this (max value was 0.06821107602623269)
color_continuous_scale=[(0.0, "#e5e5e5"), (0.001449275362, "#e5e5e5"), # 0.01% , 0.0001
(0.01449275362, "#ffe5f0"), (0.1086956522, "#ffe5f0"), # 0.75% , 0.0075
(0.1086956522, "#facfdf"), (0.1449275362, "#facfdf"), # 1% , 0.01
(0.1449275362, "#f3b8ce"), (0.3623188406, "#f3b8ce"), # 2.5% , 0.025
(0.3623188406, "#eca2bf"), (0.7246376812, "#eca2bf"), # 5% , 0.05
(0.7246376812, "#e37fb1"), (1, "#e37fb1") # 6.9% , 0.069
],
And even best if someone knew how to change the numbers in the colorscale which is shown in the images on the right from numbers to percentages (0.05 -> 5%)
If I add range_color=(0, 1) it adds the correct colors but then there is a useless colorbar on the right.
pd.cut()
and get the bin edgesedges = pd.cut(df_anim["value"], bins=5, retbins=True)[1]
edges = edges[:-1] / edges[-1]
colors = ["#e5e5e5", "#ffe5f0", "#facfdf", "#f3b8ce", "#eca2bf", "#e37fb1"]
cc_scale = (
[(0, colors[0])]
+ [(e, colors[(i + 1) // 2]) for i, e in enumerate(np.repeat(edges, 2))]
+ [(1, colors[5])]
)
from pathlib import Path
import geopandas as gpd
import pandas as pd
import numpy as np
import plotly.express as px
# simulate source data
gdf = gpd.read_file(
list(Path.home().joinpath("Downloads/WGS84").glob("*KRAJ*.shp"))[0]
).set_crs("epsg:4326")
gdf["geometry"] = gdf.to_crs(gdf.estimate_utm_crs()).simplify(2000).to_crs(gdf.crs)
regions_json = gdf.__geo_interface__
df = (
pd.json_normalize(regions_json["features"])
.pipe(lambda d: d.loc[:, [c.strip() for c in d.columns if c[0:3] == "pro"]])
.rename(columns={"properties.ID": "K_KRAJ", "properties.NAZEV_NUTS": "N_KRAJ"})
)
df_anim = df.merge(
pd.DataFrame(
{"variable": [f"REL{n1}{n2}" for n1 in range(15, 21) for n2 in ["06", "12"]]}
),
how="cross",
).pipe(lambda d: d.assign(value=np.random.uniform(0, 0.003, len(d))))
# end data simulation
edges = pd.cut(df_anim["value"], bins=5, retbins=True)[1]
edges = edges[:-1] / edges[-1]
colors = ["#e5e5e5", "#ffe5f0", "#facfdf", "#f3b8ce", "#eca2bf", "#e37fb1"]
cc_scale = (
[(0, colors[0])]
+ [(e, colors[(i + 1) // 2]) for i, e in enumerate(np.repeat(edges, 2))]
+ [(1, colors[5])]
)
fig = px.choropleth(
df_anim,
locations="K_KRAJ",
featureidkey="properties.ID", ### ! changed !
geojson=regions_json,
color="value",
hover_name="N_KRAJ",
color_continuous_scale=cc_scale,
animation_frame="variable",
)
fig.update_geos(fitbounds="locations", visible=False)