wvs map: colour model stars by lab family (region-hued), declutter labels

Models are now stars coloured by lab family instead of one Economist red, hue
chosen to echo the lab's home region: Chinese labs warm (qwen orange, deepseek
pink) near the East-Asia red, US labs cool (claude purple, gpt blue, gemini/gemma
sea blue, grok indigo, llama steel), Europe green (mistral). Legend keys each
family. Also drop the ' (rated)' tag from on-map labels and Nigeria from the
always-on landmarks (Egypt already anchors the African-Islamic corner).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-07-05 12:31:58 +08:00
co-authored by Claudypoo
parent 8f64ff7b71
commit 103ea11039
3 changed files with 52 additions and 17 deletions
+4 -1
View File
@@ -286,10 +286,13 @@ def main() -> None:
# signposts through the human median, 4 auto-selected zone hulls, textalloc labels, model stars.
_, emph = zones_for(countries)
# Title + caption live in the README (nicer voice, editable), not baked into the figure.
# Drop the " (rated)" readout tag from the on-map labels (the cache/CI-table keep it) -- the map is
# crowded and every model here is rated, so the tag adds nothing.
plot_models = {k.replace(" (rated)", ""): v for k, v in models.items()}
fig = maps.plot_value_map(
"WVS Inglehart-Welzel", countries, P,
("Survival", "Self-expression", "Traditional", "Secular-Rational"),
models=models, emphasize=emph)
models=plot_models, emphasize=emph)
fig.savefig(args.out, dpi=200, bbox_inches="tight")
logger.info(f"wrote {args.out}")
+44 -14
View File
@@ -223,9 +223,31 @@ def _pole_signposts(ax, med_x: float, med_y: float, poles: tuple[str, str, str,
ax.annotate(xp, xy=(0.994, med_y), xytext=(0.9, med_y), xycoords=tY, arrowprops=awp, **kw)
# Economist convention: every model is the SAME bold red (bigger than the grey society dots), told
# apart by its on-map label, not by colour. Distinct from the muted ZONE_COLORS.
# Economist convention: every model is the SAME bold red, told apart by its label. We keep MODEL_RED
# as the fallback but colour each model STAR by its lab FAMILY, with the hue chosen to echo the lab's
# home region (Chinese labs warm / near the East-Asia red; US labs cool blue-purple; Europe green) so a
# family clusters by colour at a glance, not just by reading labels. -- added by Claude
MODEL_RED = "#d0021b"
MODEL_FAMILY_COLORS = {
"deepseek": "#ff6fa3", # DeepSeek (China) -> pink, a lighter East-Asia red
"qwen": "#f28e2b", # Qwen / Alibaba (China) -> orange
"claude": "#7b3fa0", # Anthropic (US) -> purple
"gpt": "#1f77b4", # OpenAI (US) -> blue
"gemini": "#1198a6", # Gemini / Google (US) -> sea blue
"gemma": "#5fc9d3", # Gemma / Google open sibling -> lighter sea blue
"grok": "#3b4cc0", # Grok / xAI (US) -> indigo
"llama": "#4e79a7", # Llama / Meta (US) -> steel blue
"mistral": "#59a14f", # Mistral (France / Europe) -> green
}
def model_family_color(name: str) -> str:
"""The lab-family colour for a model key (substring match on the family name), MODEL_RED if none."""
key = name.lower()
for fam, col in MODEL_FAMILY_COLORS.items():
if fam in key:
return col
return MODEL_RED
def plot_value_map(display: str, countries: list[str], P: np.ndarray,
@@ -269,15 +291,16 @@ def plot_value_map(display: str, countries: list[str], P: np.ndarray,
sx, sy = list(P[:, 0]), list(P[:, 1])
if models:
# models carry (x, y[, x_se, y_se]); the CI is NOT drawn -- with a dozen+ models the whisker
# crosses overlap into noise. Uncertainty lives in the companion table (wvs_map save_ci_table),
# which also shows it's item-disagreement (irreducible by N), not sampling noise.
# crosses overlap into noise. Uncertainty lives in the companion table (wvs_map's CI table),
# which also shows it's item-disagreement (irreducible by N), not sampling noise. Each model is
# a STAR coloured by its lab family (model_family_color), and its label takes the same colour.
mnames = list(models)
mx = np.array([models[k][0] for k in mnames])
my = np.array([models[k][1] for k in mnames])
ax.scatter(mx, my, s=120, marker="o", c=MODEL_RED, # Economist: bigger red dots
edgecolors="white", linewidths=1.0, zorder=8)
mcols = [model_family_color(k) for k in mnames]
ax.scatter(mx, my, s=230, marker="*", c=mcols, edgecolors="white", linewidths=0.8, zorder=8)
tx += list(mx); ty += list(my); txt += mnames
tcol += [MODEL_RED] * len(mnames)
tcol += mcols
sx += list(mx); sy += list(my)
if steer:
bx, by, _ = steer["base"]
@@ -300,14 +323,21 @@ def plot_value_map(display: str, countries: list[str], P: np.ndarray,
textsize=9, textcolor=tcol, linecolor="#aaa", linewidth=0.6, draw_lines=True)
_pole_signposts(ax, med_x, med_y, poles)
ax.set_xticks([]); ax.set_yticks([]); ax.set_xlabel(""); ax.set_ylabel("")
if models: # minimal colour legend (red = models, grey = societies)
if models: # legend: one star swatch per lab family present
from matplotlib.lines import Line2D
handles = [Line2D([], [], marker="o", linestyle="none", markerfacecolor=MODEL_RED,
markeredgecolor="white", markersize=11, label="AI models"),
Line2D([], [], marker="o", linestyle="none", markerfacecolor="#8f8a80",
markeredgecolor="white", markersize=8, label=f"{len(countries)} societies")]
ax.legend(handles=handles, loc="upper left", fontsize=9, frameon=False,
borderaxespad=0.8, handletextpad=0.3).set_zorder(11)
fams, seen_f = [], set()
for k in mnames: # keep map order, dedupe to one entry per family
low = k.lower()
fam = next((f for f in MODEL_FAMILY_COLORS if f in low), None)
if fam and fam not in seen_f:
seen_f.add(fam)
fams.append((fam, MODEL_FAMILY_COLORS[fam]))
handles = [Line2D([], [], marker="*", linestyle="none", markerfacecolor=col,
markeredgecolor="white", markersize=12, label=fam) for fam, col in fams]
handles.append(Line2D([], [], marker="o", linestyle="none", markerfacecolor="#8f8a80",
markeredgecolor="white", markersize=8, label=f"{len(countries)} societies"))
ax.legend(handles=handles, loc="upper left", fontsize=8, frameon=False,
borderaxespad=0.6, handletextpad=0.3, labelspacing=0.3, ncol=2).set_zorder(11)
# Title + caption are OFF by default -- the README carries the headline + sources (nicer voice
# there than baked jargon). Pass title/note only for a standalone figure.
if title:
+4 -2
View File
@@ -74,9 +74,11 @@ _CANON = {
"(nu": None, # corrupt big5 row (n=369); country unidentifiable from the aggregate CSV
}
# The named outliers on the Economist chart, bolded on our maps where present.
# The named outliers on the Economist chart, bolded on our maps where present. Nigeria dropped: Egypt
# already anchors the bottom-left corner (it's the corner-outlier auto-label), so both crowds the
# African-Islamic corner. -- Claude
ECONOMIST_OUTLIERS = {"China", "South Korea", "United States", "Great Britain", "Japan",
"Nigeria", "Pakistan", "Sweden"}
"Pakistan", "Sweden"}
# Coarser macro-zones for the maps. The nine fine IW zones over-fragment low-dimensional maps: the
# English-speaking world and the European religions (Protestant/Catholic/Baltic) don't separate, so