Skip to main content

The <mix> block

Use it when the shares in a column aren't equal and each share is more than a single word. Real data is lopsided: most orders are paid and few are canceled; most accounts are free and few are premium. You want a column where the variants appear in fixed proportions — not split evenly, and not as random noise.

<mix> is a distribution: a named source that lays its variants — the <case> branches — across the rows in exact percentages. Think of it as a <sequence> whose values are spread by percent, except that each branch can be assembled from literals and generators instead of being a single plain value. The layout is deterministic for a given seed.

Example outputs below are illustrative — the exact values a given seed produces can shift between core versions, but the counts that percent guarantees never do.

Declared shares against produced shares, over 1000 rows. Not approximately — the counts land on the declared shares exactly.
  • the percentages written in the config
  • the share that actually came out

A named distribution

<mix> sits directly in <env>, right beside <sequence> — no wrapper needed, just a name. You read the value with ${{Name}}, exactly like any other named source:

<env count="100" seed="demo" inject="${{%}}">
<mix name="Code" percent="25,70">
<case><gen type="text" value="A"/></case>
<case><gen type="text" value="B"/></case>
<case><gen type="text" value="C"/></case>
</mix>
</env>
<block>
<line><data>${{Code}}</data></line>
</block>

The first few rows tell you nothing — proportions only show up across the whole sample:

./run code.tdc (first 6 rows)
A
B
A
B
B
A

Tally all 100 rows and the split is exact:

./run code.tdc (count=100, tallied)
A   25
B   70
C    5

Exactly 25 / 70 / 5. The list sets the first two shares; the third branch takes the remainder, 100 − 25 − 70 = 5. This isn't "about 25%" — it's an exact layout by the Hamilton (largest-remainder) method, the same one that drives percent on the text generator.

Change the shares, change the split

Same A/B/C branches, but percent="60,30":

<mix name="Code" percent="60,30">
<case><gen type="text" value="A"/></case>
<case><gen type="text" value="B"/></case>
<case><gen type="text" value="C"/></case>
</mix>
./run code.tdc (count=100, tallied)
A   60
B   30
C   10

60 / 30, and the leftover 10 goes to the third branch. One number in the list re-cuts the whole column.

When you need <mix> — and when text is enough

For a plain distribution of ready-made strings, <mix> is overkill: the text generator already does exact shares with percent.

<sequence name="Gender">
<gen type="text" value="Male,Female" percent="50,50"/>
</sequence>
./run gender.tdc (count=20, tallied)
Male     10
Female   10

<mix> earns its keep only when the branches are compound — when each variant is assembled from its own combination of literal text and generators. That's the case the next few sections build toward.

Attributes

AttributeRequiredWhat it does
nameyesThe name to interpolate with ${{Name}}
percentnoThe share of each <case>; omit for a uniform split
parentnoA parent sequence — the split is computed inside its subset
flagnoAdds an answer-key column marking the outlier branch (see below)
commentnoA free-form note for the config author; never rendered

A <mix> must contain at least one <case> — one branch. Everything else is optional.

percent — optional, and partial

Leave percent off entirely and the branches split uniformly. Three cases over 99 rows means 33 apiece:

<mix name="Bucket">
<case><gen type="text" value="low"/></case>
<case><gen type="text" value="mid"/></case>
<case><gen type="text" value="high"/></case>
</mix>
./run bucket.tdc (count=99, tallied)
low    33
mid    33
high   33

When you do supply one, it follows the same grammar as percent on text: a number fixes that branch's share, and the empty positions — a bare comma inside the list ("25,,70") or a trailing comma ("25,70,") — divide what's left of 100 equally among themselves. Both examples at the top of this page already rely on that rule: percent="25,70" leaves the third branch to soak up the remainder.

parent — a distribution inside a subset

Give a <mix> a parent and the percentages are counted against the filtered subset of rows, not the whole count — the same rule that governs a dependent <sequence>. Here the paid accounts get a tier breakdown; the free accounts leave the column empty:

<sequence name="Segment">
<gen type="text" value="Free,Paid" percent="70,30"/>
</sequence>

<mix name="Tier" parent="Segment.Paid" percent="60,30">
<case><gen type="text" value="Silver"/></case>
<case><gen type="text" value="Gold"/></case>
<case><gen type="text" value="Platinum"/></case>
</mix>

Over count="100", 30 rows are Paid. The 60 / 30 split is applied to those 30 rows, so Tier splits 18 / 9 / 3:

./run tier.tdc (Paid rows only, tallied)
Silver     18
Gold        9
Platinum    3

18 + 9 + 3 = 30 — the whole paid subset, not a percentage of the full 100. This is the heart of the hierarchical model; the full treatment, with nested levels, is in Hierarchical dependencies.

<mix> nests

A <case> can itself hold a nested <mix>, and the nested split is counted against the rows that chose the outer branch — the same subset rule, one level in. Here a third of all rows are error, and within those an inner <mix> grades the severity:

<mix name="Status" percent="34,33">
<case>
<gen type="text" value="ok"/>
</case>
<case>
<gen type="text" value="warn"/>
</case>
<case>
<mix percent="70,20">
<case><data>error/minor</data></case>
<case><data>error/major</data></case>
<case><data>error/fatal</data></case>
</mix>
</case>
</mix>

Over count="100" the outer split is 34 ok / 33 warn / 33 error. The inner 70 / 20 split then divides those 33 error rows into 23 / 7 / 3:

./run status.tdc (count=100, tallied)
ok            34
warn          33
error/minor   23
error/major    7
error/fatal    3

23 + 7 + 3 = 33 — exactly the error subset.

The same holds one level out: a <mix> written inside a <switch> branch takes its quota over the rows that branch matched, not over the run.

Compound branches

Here's what <mix> gives you that a bare list of strings can't: a <case> can assemble its value from several pieces — a literal <data> fragment, one or more generators, and a nested <mix>. In this context <data> is just a literal chunk of the value (glue text), not output formatting — for that, see Masks & case.

<mix name="Charge" percent="10,12,34,">
<case><data>refund: </data><gen type="number" value="1..10"/></case>
<case><data>chargeback: </data><gen type="number" value="11..20"/></case>
<case><gen type="number" value="21..40"/></case>
<case><gen type="number" value="41..100"/><data> (flagged)</data></case>
</mix>
./run charge.tdc (first 8 rows)
36
refund: 4
chargeback: 18
66 (flagged)
refund: 8
refund: 1
73 (flagged)
82 (flagged)

Each branch built its own shape: refund: 4 is the literal refund: plus a number in 1..10; 66 (flagged) is a number in 41..100 followed by the literal (flagged); the third branch is a bare number with no wrapper at all. Fold the 100 rows by branch and the shares are still exact:

./run charge.tdc (count=100, tallied by branch)
refund       10
chargeback   12
plain        34
flagged      44

10 / 12 / 34 / 44. The last branch has no percent of its own — the trailing comma in percent="10,12,34," leaves it open, so it takes the remainder, 44.

A distribution generates — it doesn't format

<mix> produces data, so it lives only in <env>. You can't put it inside the output block: a <mix> placed directly in a <line> is rejected before the run with error TDC132, because the output block is for layout only. Declare <mix name="…"> in <env> and interpolate ${{Name}} where you want the value.

If you need a choice that isn't by percentage, two neighbors cover it:

  • <switch> picks by a key — a lookup table, like country → currency.
  • A <sequence> with conditional <gen if="…"> branches picks by an arbitrary condition — the first true branch wins.

Marking outliers with flag

A branch can be tagged as anomalous — <case anomaly="true"> — and flag makes the mix emit an answer-key column alongside it. The result is a dataset you can test an anomaly detector against: the outliers are present, and a companion column records exactly which rows they landed on.

<mix name="Temp" percent="75,25" flag="Bad">
<case><gen type="number" value="20..24"/></case>
<case anomaly="true"><gen type="number" value="90..99"/></case>
</mix>

${{Bad}} reads true on precisely the rows that came from the tagged branch — 25% of them — and false everywhere else. The label is derived from the same decision that picked the branch, so it can never disagree with the value. anomaly="true" is only a label: the outlier itself is whatever the branch's generator produces, which is why you keep full control over how it looks. This is one corner of a larger topic — outlier injection and the flag column get their own full treatment in the anomalies guide.

Next

  • Text generator — exact percent shares for a plain list of options, when the branches are single words.
  • Sequences — the named source <mix> sits next to, and the parent model the two share.
  • Hierarchical dependencies — the full parent story, with nested percentages across multiple levels.