Shaping metrics¶
What shape does your data take? This page covers the three knobs you turn to control it: metric type (which picks a distribution), archetype (which picks a trajectory shape), and baseline (which narrows the value range per segment).
Metric types¶
Every metric declares a type and a polarity. The type picks a
distribution; the polarity decides whether a high trajectory position
means a high or a low value.
| Type | Default distribution | When to use | Range required? |
|---|---|---|---|
score |
beta (alpha=2, beta=5) | A 0–1 score, percentile, normalized rate | optional (defaults to [0, 1]) |
count |
poisson (lambda=5) | Discrete counts: tickets, logins, visits | rejected — counts are unbounded |
amount |
lognorm or beta, picked from range | Currency, sizes, anything with a meaningful unit | required |
index |
normal centered on the range midpoint | NPS, satisfaction index, a scored 0–100 | required |
The amount type picks lognorm when the range's lower bound is zero
or the ratio max/min ≥ 10 (right-skewed amounts like revenue), and
beta otherwise (bounded amounts like discount percentages).
from plotsim import create
config = create(
about="Customer health metrics",
unit="customer",
window=("2024-01", "2024-12", "monthly"),
metrics=[
{"name": "engagement", "type": "score", "polarity": "positive"},
{"name": "logins", "type": "count", "polarity": "positive"},
{"name": "mrr", "type": "amount", "polarity": "positive", "range": [50, 50000]},
{"name": "nps", "type": "index", "polarity": "positive", "range": [0, 100]},
],
segments=[{"name": "active", "count": 60, "archetype": "growth"}],
)
about: Customer health metrics
unit: customer
window: { start: "2024-01", end: "2024-12", every: monthly }
metrics:
- { name: engagement, type: score, polarity: positive }
- { name: logins, type: count, polarity: positive }
- { name: mrr, type: amount, polarity: positive, range: [50, 50000] }
- { name: nps, type: index, polarity: positive, range: [0, 100] }
segments:
- { name: active, count: 60, archetype: growth }
Explicit distribution¶
When the auto-pick is wrong, declare it. The six families: lognorm,
gamma, poisson, beta, normal, weibull.
Required params per family: lognorm needs s; gamma and weibull
need shape; beta needs alpha and beta (with optional scale);
normal needs sigma; poisson accepts no params (its center is
lambda, derived from the trajectory position).
Polarity¶
positive means high position → high value. negative flips: high
position → low value. The trajectory engine inverts the position
internally for negative metrics before centering the distribution. This
is how "engagement up means churn risk down" falls out for free.
You can have at most fifty metrics per config.
Archetypes¶
Each segment names an archetype that defines the trajectory shape every entity in the segment will follow. Six shape words ship out of the box:
| Word | Curve | Story |
|---|---|---|
growth |
sigmoid (rising) | S-curve ramp, midpoint at 50 % of the window |
decline |
exp_decay | Exponential fall from high to low |
flat |
plateau | Constant low level |
seasonal |
oscillating | Sine wave, period = 2 windows, ±0.4 amplitude |
accelerating |
compound | Compounding growth from low to high |
spike_then_crash |
sigmoid → step → plateau | Rises fast, drops abruptly, stays low |
Multi-phase archetype DSL¶
A segment archetype can be a composite spec: chain shapes with >,
declare transition periods with @.
reads as "flat for the first ten periods, then decline through period end." The shape on the left occupies periods 0..10; the shape on the right occupies 10..end.
For three or more phases, one @ between every pair of >:
is "ramp for eight periods, crash through period sixteen, flat after."
Transition periods must be strictly ascending and lie in [1, n_periods - 1].
Segment counts must be in the range 3..5,000. The total across all segments is capped at 100,000 entities.
Baselines¶
Two segments with the same growth archetype can still end up at
different value levels if you set per-metric baselines. The vocabulary
is high, mid, low, mapped to the upper, middle, or lower third of
each metric's value range.
Both segments are flat — the trajectory shape is the same — but the two cohorts live in different bands of the metric's range.
Baselines only constrain the value range; they don't reshape the curve. Stack them with archetypes to get "high baseline that grows" or "low baseline that crashes."
Per-segment attributes¶
attributes doubles as the source for pool.<attr> columns on the
auto-generated dimension table — each value becomes a per-entity choice
list. The builder also rolls them up into the per-cohort labels you'll
see in the manifest.
Attribute values must be strings (or lists of strings); numerics are
stringified because the underlying column is dtype: string.
Common patterns¶
Mixed cohorts with deliberate drama. Use a small number of segments
with contrasting archetypes — growth, flat, flat > decline @ N,
growth > spike_then_crash > flat @ A @ B — to produce a dataset where
the population's average looks calm but individual entities tell
distinct stories. The SaaS template does exactly this.
Range-bounded amounts. When a value has a meaningful natural ceiling
(NPS 0–100, discount 0–25 %), set range. The engine clamps every
sampled value to the range before noise, and the manifest's
variance_partitions reflect the bounded scale honestly.
Distribution choice over polarity. Don't model "wait time" as
positive-polarity revenue with a negative coefficient — model it as a
negative-polarity amount with a wait-time-shaped distribution
(gamma, typically). The downstream correlations and seasonality
machinery handles polarity correctly only when polarity is declared
honestly.
Going deeper¶
- Connecting metrics — correlations, causal lag, seasonality once shapes are set.
- Adding realism — noise, heteroscedastic scaling, heavy-tailed families, quality issues.
- Reference: Config fields — every config field with type and default.