mirror of
https://github.com/wassname/moral-maps.git
synced 2026-09-09 11:27:22 +08:00
Add MFQ-2 per-zone p90 respondent ellipses to ipsative map
respondent_profiles now returns countries alongside profiles; plot_ipsative_pca gains respondent_zones -> a p90 Gaussian ellipse per IW zone of the projected Atari respondent cloud (edge-only, no scipy). mfq2 uses these real-respondent ellipses; the other instruments keep country-mean hulls. Journal notes the finding: individual profiles overlap across cultures (within >> between variance), so only the country-mean hull reproduces the Economist's clean zone blobs. Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -104,6 +104,13 @@ ECONOMIST_OUTLIERS = {"China", "South Korea", "United States", "Great Britain",
|
||||
"Nigeria", "Pakistan", "Sweden"}
|
||||
|
||||
|
||||
def _zone_of(country: str) -> str | None:
|
||||
"""IW zone of a verbatim country string, or None for a known-corrupt row. KeyErrors (fail loud)
|
||||
on an unrecognised country so a normalization bug can't silently drop it."""
|
||||
canon = _COUNTRY_CANON.get(country, country)
|
||||
return None if canon is None else IW_ZONE[canon]
|
||||
|
||||
|
||||
def zones_for(countries: list[str]) -> tuple[dict[str, list[str]], set[str]]:
|
||||
"""Group verbatim country strings by IW zone + the subset to emphasize. Fails loud (KeyError)
|
||||
on a country absent from the taxonomy so a name-normalization bug can't silently drop a dot from
|
||||
@@ -112,12 +119,12 @@ def zones_for(countries: list[str]) -> tuple[dict[str, list[str]], set[str]]:
|
||||
dropped: list[str] = []
|
||||
emph: set[str] = set()
|
||||
for c in countries:
|
||||
canon = _COUNTRY_CANON.get(c, c)
|
||||
if canon is None:
|
||||
z = _zone_of(c)
|
||||
if z is None:
|
||||
dropped.append(c)
|
||||
continue
|
||||
groups.setdefault(IW_ZONE[canon], []).append(c)
|
||||
if canon in ECONOMIST_OUTLIERS:
|
||||
groups.setdefault(z, []).append(c)
|
||||
if _COUNTRY_CANON.get(c, c) in ECONOMIST_OUTLIERS:
|
||||
emph.add(c)
|
||||
if dropped:
|
||||
logger.warning(f"excluded known-unmapped countries from zone hulls: {dropped}")
|
||||
@@ -269,16 +276,23 @@ def plot_ordinal(run_dir: Path, out: Path, name: str, vec_label: str, C: float,
|
||||
# fit the ipsative PCA on it (better-conditioned, the true envelope). Other instruments have no raw
|
||||
# per-person data, so scatter a marginal resample from each country's published mean+sd as the haze
|
||||
# while keeping the PCA basis on the society means M.
|
||||
# mfq2 has real per-respondent data -> draw a p90 respondent ELLIPSE per zone (grounded in
|
||||
# people) and drop the country-mean hull. Other instruments have only society means -> the
|
||||
# country-mean convex HULL is the best-available zone blob.
|
||||
_, emph = zones_for(countries)
|
||||
if name == "mfq2":
|
||||
respondents, haze = T.maps.respondent_profiles(dims, instr.scale_max), None
|
||||
resp_countries, respondents = T.maps.respondent_profiles(dims, instr.scale_max)
|
||||
haze, zones = None, None
|
||||
respondent_zones = [_zone_of(c) for c in resp_countries]
|
||||
else:
|
||||
respondents, haze = None, human_haze(instr)
|
||||
zones, respondent_zones = zones_for(countries)[0], None
|
||||
traj = {c: _frac(prof_c[c], instr.scale_max) for c in coh_cs}
|
||||
zones, emph = zones_for(countries)
|
||||
figm = T.maps.plot_ipsative_pca(instr, dims, countries, Mfrac,
|
||||
_frac(base, instr.scale_max), _frac(pos, instr.scale_max),
|
||||
_frac(neg, instr.scale_max), respondents=respondents, haze=haze,
|
||||
traj=traj, zones=zones, emphasize=emph, labels=labels)
|
||||
traj=traj, zones=zones, emphasize=emph,
|
||||
respondent_zones=respondent_zones, labels=labels)
|
||||
figm.axes[0].set_title(f"{instr.display}: humans vs LLMs steered for {vec_label}", fontsize=10)
|
||||
paths = [T.maps.save_both(figm, out / name, "map_pca_ipsative")]
|
||||
plt.close(figm)
|
||||
|
||||
Reference in New Issue
Block a user