Validates synthetic population data (LLM personas, simulated survey-takers) against real human benchmarks using distribution-shape metrics (Cohen's w, chi-square) and sampling-noise floors.
How this skill is triggered — by the user, by Claude, or both
Slash command
/nemotron-personas-korea:synthetic-population-validityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You have answers from a synthetic source — LLM personas, simulated agents, a
You have answers from a synthetic source — LLM personas, simulated agents, a generative model of a population — and a real human reference. The question is always some form of "how faithfully does the synthetic reproduce the real, and what can I honestly claim from it?" This skill is the methodology for answering that without fooling yourself, distilled from a multi-week validation campaign.
Two failure modes dominate this work, and almost everything here guards against one of them:
If you do only one thing: compare the whole distribution to the reference with
Cohen's w (an effect size — not the mean, not a p-value), and gate it on the
perfect-respondent sampling floor — a gap that sits inside the floor band at your n
is sampling noise, not a finding. Report w per variable alongside its floor. That one
loop (scripts/stats.py + scripts/sampling_floor.py) already defuses both failure
modes; everything below differentiates it.
Validity is nested. Each level is necessary for the one above it and tells you nothing about it. Passing level N licenses you to look at level N+1 — never to declare done.
| Level | The question | Statistic | Helper | Read it as |
|---|---|---|---|---|
| 1 · Marginal | Does central tendency match? | Δmean, Δquartile-mass | (trivial) | Necessary, never sufficient |
| 2a · Shape | Is the whole distribution right? | Cohen's w (+ χ²) | cohen_w, chi_square_vs_reference | Effect-size tiers, not p |
| 2b · Tail | Which bin is wrong? | per-bin obs vs ref | (tabulate) | Name the deficit (e.g. Q4 missing) |
| 3a · Dispersion | Do distinct units spread like real? | SD ratio; ICC | sd_ratio, variance_components | <0.85 ⇒ homogenized |
| 3b · Joint | Are cross-variable relationships preserved? | mean |Δr|, sign flips | correlation_distortion | a sign flip ⇒ marginal-only claims |
| Fallback | If marginals are unreachable, is the ordering right? | Spearman ρ | signal_correlation | high ρ ⇒ rank/relative claims OK |
Helpers live in scripts/stats.py (domain-agnostic — you pass your own bins,
columns, and reference). Detailed formulas, inputs, and interpretation are in
references/metric-catalog.md.
The deepest finding of the campaign: under-dispersion and correlation-distortion are the same defect seen two ways. A generator that collapses distinct units onto a too-similar, low-dimensional manifold both narrows each marginal (3a) and warps the joint structure (3b). Don't treat them as separate problems.
A distance is "validity" only under these conditions. Skipping them produces numbers that look like method signal but are artifacts.
Don't compute the whole stack at once, and don't start at the bottom of it. The stack numbers the marginal as Level 1 because it's logically necessary for the rest — but that numbering is a stringency taxonomy, not a work order. Start the work at the strongest rung — the one that already gives a verdict you can trust on its own — and climb out from there. Work per variable.
Glance at the marginal (Level 1) — don't rest there. Read Δmean / Δquartile-mass: a gross mismatch is a cheap early exit (the source is off at the crudest level), but a match tells you nothing — necessary, never sufficient, and a matching mean is the trap. So the marginal is a precondition you read in passing, not where the validation lives.
Start here — shape (Level 2). Per variable, compute Cohen's w against the reference
and read it with the fidelity tiers (effect_size_tier), not p-values: with a synthetic
source N is arbitrary, so χ² always rejects and p is uninformative. Tiers: <0.10
excellent · 0.10–0.30 small · 0.30–0.50 medium-low · 0.50–0.80 medium · >0.80
large (document as a gap, don't build claims on it). Gate every w on its sampling floor
(Step 2) — a w inside the band is noise, not a verdict. Read the per-bin counts to name
which bin is off. Shape + floor is the smallest complete, honest check — the "if you
do only one thing" loop — and you could stop here and still have a verdict you trust.
Climb to structure (Level 3). A clean shape licenses the next look, it doesn't end
the inquiry: climb to between-unit dispersion (sd_ratio, plus variance_components for
ICC) and cross-variable joint structure (correlation_distortion). Because
under-dispersion and correlation-distortion are the same defect, a variable that passed
shape can still collapse here — so a pass is a reason to climb, never to stop. Report
each rung as you reach it.
Per variable, never only the aggregate. A mean w can improve while one variable is χ²-rejected, and self-cancelling per-variable effects hide inside a null aggregate. The accumulation of these per-variable rungs is the validity profile you hand over (see The honest deliverable) — you reach it by climbing, not by dumping all the levels in one shot.
This is the highest-leverage step in the whole workflow. A flawless respondent still scores a nonzero w at finite n, purely from sampling noise. Compute that floor and you know whether your number is even resolvable from perfection.
python scripts/sampling_floor.py --reference ref.json --n 50
ref.json is your reference vector (or a dict of them for a mean-across-dimensions
floor). The script bootstraps the 95% band of w that a perfect respondent produces
at your n. A measured score inside the band is indistinguishable from perfect —
stop tuning. (At n=50 one instrument's floor was [0.148, 0.313]; a measured 0.305
sat inside it. The back half of a 14-iteration campaign had been optimizing inside
that band.)
Then size n so the floor is narrower than the effect you care about:
python scripts/sampling_floor.py --reference ref.json --power 50,100,150,200
Power here means "floor band narrower than the gap." The same instrument's floor narrowed from [0.148, 0.313] at n=50 to [0.09, 0.20] at n=123 — only then could a real gap be separated from luck. (An external anchor: distribution replication tends to need n ≳ 200 — Sun et al. 2024 — converging with survey-methodology heuristics.)
Two floors, not one. The sampling floor bounds error from your sample size; the human reference has its own reliability ceiling — you can't expect the synthetic source to match the reference better than the reference matches itself on retest. Where the reference has repeat measurements, normalize the metric by the human test–retest value, not by perfect agreement (Park et al. 2024): "reached 0.85 of the human self-consistency ceiling" is the honest framing, not "85% of perfect."
Query count is not sample size. More LLM queries shrink your standard errors toward zero without improving validity — the binding uncertainty is bias against the reference, not Monte-Carlo precision (Brand et al.). Don't report LLM-query N as if it were survey N.
A plateau is a signal to decompose, not to iterate again. Write the gap as a sum of independent sources and bound each with one cheap experiment — this can prove you're done (or prove which lever is dead) for ~50 model calls, instead of more blind tuning.
gap ≈ sampling-floor + composition + source-skew + measurement-act
python scripts/rtm_check.py --csv paired.csv --prev prev_run --curr curr_run
Shifts that reverse at the extremes (low rises, high falls) are RTM — the
intervention left units under-specified and the model fell back to its prior. The
fix is an active anchor (a concrete behavior/instance), not another
reformulation of passive content.The durable output of a validation campaign is not a "best" pipeline — it's a characterized validity profile: for each variable, whether the synthetic source can support claims about it, and the mechanism (structural ceiling vs rescuable vs fragile-to-composition). That profile tells future users what the data can and can't be used for, and it survives long after any "best condition."
When a structural wall makes marginal match unreachable, state the honest claim rather than chasing an impossible target:
With the validity axis complete — what to compare, whether the gap is real, what you can claim — there is a second, parallel axis the synthetic source should also reproduce: reliability (is the measurement internally consistent / reproducible?). It's not a higher level of the stack; a perfectly reliable source can be reliably wrong. Two checks, both framed as a synth-vs-real comparison:
cronbach_alpha on the item scores, synth α vs real
α. A synthetic source that collapses units onto a low-dimensional manifold
inflates α (everyone answers each item alike — the within-scale face of
correlation_distortion); independent per-item LLM noise deflates it. With LLMs α
is necessary-not-sufficient: high internal consistency routinely coexists with low
parallel-forms reliability, so pair it with a prompt-perturbation check —
re-elicit under reworded prompts and see if the distribution moves (Ye et al.
2025). Label your α as population internal-consistency, not single-unit
test–retest (you generated a population of personas).krippendorff_alpha (nominal/ordinal/interval, handles missing).
Two uses: the categorical/ordinal analog of ICC for draw-to-draw reproducibility
(raters = repeat draws of a unit); and chance-corrected per-unit synth-vs-real
agreement when you hold unit-linked ground truth (raters = {synth, real}) — a
stronger claim than distribution match, but it needs a real answer per synthetic
unit, not just a reference distribution.The alignment pressure that flattens the distribution can also defeat naive bias checks, so validate with indirect/behavioral probes, not self-report (Demszky et al. 2023).
Persist per run, or comparisons across iterations silently break: generator/model ID + prompt hash, per-item instrument wording, demographic stratification (target + realized), seed, and data-source SHA. Pin explicit kwargs in reproduction scripts — a changed default silently alters re-run artifacts. Pin the model version/date, not just the name: a pinned-name model is silently re-tuned over time, so a validated recipe can decay with no code change (Bail 2024) — re-validate against the reference after any model update.
These methods aren't idiosyncratic to one project — the published record on
synthetic / LLM survey data converges on the same signature: means align, higher
moments don't. LLM samples match marginals while under-dispersing, distorting
joint / subgroup structure, and skewing toward majority / aligned views; the
mechanism is alignment (RLHF compresses diversity and flattens contrasts); it is
not fixable by more queries, higher temperature, or mean-matching fine-tuning; and
the data is valid for interpolation / triage, not discovery. references/literature.md
holds the citations and per-paper takeaways — Argyle's algorithmic-fidelity criteria,
the marketing-WTP and causal-confounding caveats, the reliability-vs-validity
distinction, and the diversity-collapse evidence — and is worth reading when you need
a citable basis or a second method (Cramér's-V matrix comparison, Jensen–Shannon
divergence, embedding-space diversity, the steerability/locality test).
scripts/stats.py — the metric menu: cohen_w, chi_square_vs_reference,
effect_size_tier, sd_ratio, variance_components (ICC), correlation_distortion,
signal_correlation, and the reliability axis cronbach_alpha, krippendorff_alpha.
Pure functions over your arrays; import or read for the math.scripts/sampling_floor.py — the perfect-respondent floor band and n-power curve.scripts/rtm_check.py — the regression-to-mean conditional-shift diagnostic.references/metric-catalog.md — per-metric formula, required input shape,
interpretation thresholds, and gotchas. Read it when you need the detail behind a
helper or are choosing which statistic fits your data.references/literature.md — the published evidence base: citations, per-paper
transferable takeaways, and the convergent failure-mode synthesis.npx claudepluginhub cjunekim/claude-plugins --plugin nemotron-personas-koreaGuides reception of code review feedback: verify before implementing, avoid performative agreement, push back with technical reasoning when needed.
Design banners for social media, ads, website heroes, and print with multiple art direction options and AI-generated visuals.