El bloque <mix>
Úselo cuando las proporciones de una columna no son iguales y además cada
variante es más que una sola palabra. Los datos reales son desparejos: casi todos
los pedidos están paid y muy pocos cancelled; casi todas las cuentas son free
y muy pocas premium. Usted quiere una columna donde las variantes aparezcan en
proporciones fijas — ni repartidas por igual, ni como ruido aleatorio.
<mix> es una distribución: una fuente con nombre que reparte sus variantes —
las ramas <case> — entre las filas en porcentajes
exactos. Piénselo como una <sequence> cuyos
valores se distribuyen por percent, salvo que cada rama puede ser todo un
compuesto de literales y generadores en vez de un único valor simple. El reparto es
determinista para una seed dada.
Las salidas de ejemplo que siguen son ilustrativas — los valores exactos que produce
una seed dada pueden cambiar entre versiones del core, pero los conteos que
garantiza una máscara percent nunca cambian.
- los porcentajes escritos en la configuración
- la proporción que realmente se produjo
Una distribución con nombre
<mix> va directamente en <env>, justo
al lado de <sequence> — no hace falta ningún envoltorio, basta con darle un
name. El valor se lee con ${{Nombre}}, exactamente igual que cualquier otra
fuente con nombre:
<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>
Las primeras filas no dicen nada — las proporciones solo se ven a lo largo de toda la muestra:
A B A B B A
Cuente las 100 filas y el reparto es exacto:
A 25 B 70 C 5
Exactamente 25 / 70 / 5. La máscara fija las dos primeras proporciones; la tercera
rama se lleva el resto, 100 − 25 − 70 = 5. Esto no es «como 25 %»: es un
reparto exacto por el método de Hamilton (mayores restos), el mismo que gobierna
percent en el generador text.
Cambie las proporciones y cambia el reparto
Las mismas ramas A/B/C, pero con 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>
A 60 B 30 C 10
60 / 30, y el sobrante 10 se va a la tercera rama. Un solo número de la máscara
vuelve a cortar la columna entera.
Cuándo se necesita <mix> — y cuándo basta con text
Para una simple distribución de cadenas ya hechas, <mix> es excesivo: el generador
text ya reparte proporciones exactas con percent.
<sequence name="Gender">
<gen type="text" value="Hombre,Mujer" percent="50,50"/>
</sequence>
Hombre 10 Mujer 10
<mix> se gana su lugar solo cuando las ramas son compuestas — cuando cada
variante se arma con su propia mezcla de texto literal y generadores. Ese es el caso
al que llevan las secciones siguientes.
Atributos
| Atributo | Obligatorio | Qué hace |
|---|---|---|
name | sí | El nombre para interpolar con ${{Nombre}} |
percent | no | La proporción de cada <case>; omítalo para un reparto uniforme |
parent | no | Una secuencia padre — el reparto se calcula dentro de su subconjunto |
flag | no | Agrega una columna de respuestas que marca la rama atípica (ver más abajo) |
comment | no | Una nota libre para el autor de la configuración; nunca se renderiza |
Un <mix> debe contener al menos un <case> — una rama.
Todo lo demás es opcional.
percent — opcional, y las máscaras cortas
Deje percent fuera por completo y las ramas se reparten de forma uniforme.
Tres casos sobre 99 filas dan 33 cada uno:
<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>
low 33 mid 33 high 33
Cuando sí se da una máscara, esta sigue la misma gramática que
percent en text: un número fija la proporción de esa
rama, y cada posición vacía — una coma suelta dentro de la máscara ("25,,70") o
una coma final ("25,70,") — reparte por igual lo que falta para llegar a 100. Los
dos ejemplos del inicio de esta página ya se apoyan en esa regla: percent="25,70"
deja que la tercera rama absorba el resto.
count decide qué fila recibe qué rama
percent es una cuota sobre toda la corrida, no una moneda al aire por fila. Es una
promesa que vale tener: pida 15% y obtiene 15%, no "más o menos, según la suerte". Pero
trae una consecuencia que sorprende la primera vez, y conviene encontrarla aquí y no en
sus datos.
<mix name="Temperature" percent="85">
<case><gen type="number" value="-5..5" decimals="2"/></case>
<case><gen type="number" value="[450..501],[650..701],[1000..1100]" decimals="2"/></case>
</mix>
Ejecútelo con count="10", luego con count="100", y compare las primeras diez filas:
count=10 -1.76 3.70 -0.44 0.96 499.88 0.21 2.18 2.20 -3.16 0.69 count=100 -1.76 468.42 -0.44 0.96 499.88 0.21 2.18 2.20 -3.16 0.69
Cambió la fila dos. Nada más: -1.76, -0.44, 0.96, 0.21, 2.18 y 0.69 son
idénticos, e incluso el pico de la fila cinco se quedó donde estaba. Los valores no se
movieron; lo que se movió fue la asignación de rama. El número de cada fila está atado
a esa fila y se mantiene; lo que cambia es qué rama le tocó.
La razón es aritmética. Con diez filas, 15% son una fila y media, y una fila no se parte,
así que la cuota sale en un solo pico. Con cien filas son exactamente quince. Distinto
count significa distinta cantidad de filas con pico, y sus lugares salen de una
permutación sobre toda la corrida, de modo que una corrida más larga es otra permutación:
count | filas con pico | 15% de count |
|---|---|---|
| 10 | 1 | 1.5 |
| 20 | 3 | 3.0 |
| 100 | 15 | 15.0 |
| 1000 | 150 | 150.0 |
Si necesita que una fila no cambie al cambiar count, lo que quiere es una decisión por
fila — anomaly en el generador decide fila por fila, así que
las primeras diez filas son idénticas con cualquier longitud de corrida. El intercambio es
simétrico: la proporción pasa a ser aproximada, y diez filas podrían llevar un valor
atípico o tres.
En resumen: <mix percent=> para una proporción exacta, cuando produce un conjunto de
datos fijo. anomaly= para filas estables, cuando sube count para ver qué sigue.
parent — una distribución dentro de un subconjunto
Déle un parent a un <mix> y los porcentajes se cuentan contra el subconjunto
filtrado de filas, no contra el count completo — la misma regla que gobierna una
<sequence> dependiente. Aquí las cuentas de paga
reciben un desglose por nivel; las cuentas gratuitas dejan la columna vacía:
<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>
Sobre count="100", 30 filas son Paid. La máscara 60 / 30 se aplica a esas 30
filas, así que Tier se reparte 18 / 9 / 3:
Silver 18 Gold 9 Platinum 3
18 + 9 + 3 = 30 — todo el subconjunto de paga, y no un porcentaje del total de 100. Este es el corazón del modelo jerárquico; el tratamiento completo, con niveles
anidados, está en
Dependencias jerárquicas.
<mix> se anida
Un <case> puede contener a su vez un <mix> anidado, y el reparto anidado se
cuenta contra las filas que eligieron la rama externa — la misma regla de
subconjunto, un nivel más adentro. Aquí un tercio de todas las filas son error, y
dentro de ellas un <mix> interno gradúa la severidad:
<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>
Sobre count="100" el reparto externo es 34 ok / 33 warn / 33 error. La máscara
interna 70 / 20 divide entonces esas 33 filas de error en 23 / 7 / 3:
ok 34 warn 33 error/minor 23 error/major 7 error/fatal 3
23 + 7 + 3 = 33 — exactamente el subconjunto de errores.
Lo mismo vale un nivel más arriba: un <mix> escrito dentro de una rama de
<switch> toma su cuota sobre las
filas que esa rama capturó, no sobre la ejecución completa.
Una proporción menor que un registro
La regla del subconjunto tiene un borde que conviene conocer de antemano. Un porcentaje es una proporción de las filas que llegan a la rama, no una probabilidad que se tira para cada fila. Cuando esa proporción resulta menor que una fila entera, la rama puede producir un valor y puede no producir nada.
Esta configuración le da un diagnóstico a cada registro. El diez por ciento de cada sexo recibe un diagnóstico específico de ese sexo, y el resto recibe uno general:
<env count="10" seed="demo" local="en">
<sequence name="Gender">
<gen type="text" value="Male,Female" percent="50,50"/>
</sequence>
<switch name="Diagnosis" on="Gender">
<case is="Male">
<mix percent="10,90">
<case><gen type="template" value="medical.diagnosisMale"/></case>
<case><gen type="template" value="medical.diagnosis"/></case>
</mix>
</case>
<case is="Female">
<mix percent="10,90">
<case><gen type="template" value="medical.diagnosisFemale"/></case>
<case><gen type="template" value="medical.diagnosis"/></case>
</mix>
</case>
</switch>
</env>
Female,Coronary Artery Disease Female,Tuberculosis Male,Attention Deficit Hyperactivity Disorder Male,Osteoarthritis Male,Hypothyroidism Male,Celiac Disease Female,Anemia Female,Postpartum Hemorrhage Female,Tinnitus Male,Obstructive Sleep Apnea
El lado femenino funcionó: Postpartum Hemorrhage en la fila 8 viene de
medical.diagnosisFemale. Del lado masculino no aparece nada de
medical.diagnosisMale — las cinco filas masculinas llevan condiciones generales.
Los datos son correctos, la configuración es correcta y el reparto también. Cuente las filas masculinas: son cinco. El diez por ciento de cinco filas es media fila.
percent="10" sobre un subconjunto de cinco filas pide 0.5 registros. TDC no
puede emitir medio registro, así que emite uno o ninguno, y lo decide únicamente
el seed. La ejecución de arriba no tuvo mala suerte. Deje la configuración
igual, cambie solo el seed, y el diagnóstico masculino específico aparece en
alrededor de la mitad de las ejecuciones.
Cada vez que una proporción sea pequeña, multiplíquela por la cantidad de filas que realmente llegarán a ella. Por debajo de 1, la columna es un volado.
El volado se tira una sola vez, cuando usted elige el seed, y no otra vez en cada ejecución. La configuración de arriba devuelve los mismos diez registros siempre: una columna que salió vacía sigue vacía, y volver a ejecutarla no prueba nada. Es el determinismo funcionando tal como se promete, y era justo lo que volvía difícil de detectar la trampa. La salida es estable, reproducible y no corresponde a la proporción que usted pidió.
Ahora check lo dice antes de correr, y hace la cuenta por usted — una vez por
rama:
warning[TDC251]: percent="10" over 5 rows asks for 0.5 records — the result is 0 or 1, and the seed decides which
Aquí no hay término medio
La misma configuración, con un número cambiado; medido sobre 30 seeds por fila:
percent | Filas pedidas, de 5 | Diagnósticos masculinos específicos |
|---|---|---|
5 | 0.25 | 0 con cada seed |
9 | 0.45 | 0 con cada seed |
10 | 0.5 | 0 o 1 — lo decide el seed |
11 | 0.55 | 1 con cada seed |
16 | 0.8 | 1 con cada seed |
20 | 1.0 | 1 con cada seed |
Nada aparece ni desaparece de a poco. Por debajo del 10 % la rama no dispara nunca, por encima del 10 % dispara exactamente una vez, y el 10 % es el único valor del rango que varía.
La razón está en el método de reparto. Primero cada rama toma las filas enteras
que cubre su proporción, y después las filas sobrantes van a las ramas con la
parte fraccionaria más grande. Con percent="11" las proporciones son 0.55 y
4.45, así que la única fila sobrante va a la primera rama con cada seed. Con
percent="9" son 0.45 y 4.55, y va a la segunda con cada seed. Solo con
percent="10" ambas partes fraccionarias valen 0.5, no hay nada que las separe,
y el empate se rompe con el seed.
Dos maneras de volverlo seguro
Subir la proporción para que la fracción gane sin empate. Cambia un carácter:
<mix percent="20,80">
Female,Coronary Artery Disease Female,Tuberculosis Male,Attention Deficit Hyperactivity Disorder Male,Spermatocele Male,Hypothyroidism Male,Celiac Disease Female,Anemia Female,Postpartum Hemorrhage Female,Tinnitus Male,Obstructive Sleep Apnea
La fila 4 ahora lleva Spermatocele. Nada más en la columna se movió.
O subir el count y conservar el 10 %. Con count="20" el subconjunto
masculino tiene diez filas, y el 10 % de diez es exactamente una:
Male,Osteoarthritis Male,Hypothyroidism Male,Obesity Male,Vitamin D Deficiency Male,Cryptorchidism Male,Obstructive Sleep Apnea Male,Varicose Veins Male,Vitamin D Deficiency Male,Attention Deficit Hyperactivity Disorder Male,Cholecystitis
Un Cryptorchidism en diez filas masculinas, con cada seed. Una vez que la
cantidad de filas pedidas es un número entero, la cuenta es exacta y el seed solo
decide a qué filas les toca.
Ramas compuestas
Esto es lo que <mix> le da y una lista pelada de cadenas no puede dar: un <case>
puede armar su valor con varias piezas — un fragmento literal
<data>, uno o más
generadores y <mix> anidados. En este contexto
<data> es solo un trozo literal del valor (texto de pegamento), no formato de
salida — para eso vea
Máscaras y mayúsculas.
<mix name="Charge" percent="10,12,34,">
<case><data>reembolso: </data><gen type="number" value="1..10"/></case>
<case><data>contracargo: </data><gen type="number" value="11..20"/></case>
<case><gen type="number" value="21..40"/></case>
<case><gen type="number" value="41..100"/><data> (marcado)</data></case>
</mix>
36 reembolso: 4 contracargo: 18 66 (marcado) reembolso: 8 reembolso: 1 73 (marcado) 82 (marcado)
Cada rama armó su propia forma: reembolso: 4 es el literal reembolso: más un
number en 1..10; 66 (marcado) es un número en
41..100 seguido del literal (marcado); la tercera rama es un número pelado sin
ningún envoltorio. Agrupe las 100 filas por rama y las proporciones siguen siendo
exactas:
reembolso 10 contracargo 12 simple 34 marcado 44
10 / 12 / 34 / 44. La última rama no tiene porcentaje propio — la coma final de
percent="10,12,34," la deja abierta, así que se lleva el resto, 44.
Una distribución genera — no da formato
<mix> produce datos, así que vive únicamente en <env>. No se puede poner
dentro del bloque de salida: un <mix> directamente en un <line> se rechaza antes
de la corrida con el error TDC132, porque el
bloque de salida es solo para la
disposición. Declare <mix name="…"> en <env> e interpole ${{Nombre}} donde
quiera el valor.
Si necesita una elección que no sea por porcentaje, dos vecinos la cubren:
<switch>elige por una clave — una tabla de consulta, comopaís → moneda.- Una
<sequence>con ramas condicionales<gen if="…">elige por una condición arbitraria — gana la primera rama verdadera.
Marcar valores atípicos con flag
Una rama se puede etiquetar como anómala — <case anomaly="true"> — y usted puede
pedirle al mix una columna de respuestas mediante flag. El resultado es un
conjunto de datos contra el cual probar un detector de anomalías: los valores
atípicos están ahí, y una columna acompañante registra exactamente en qué filas
cayeron.
<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}} vale true justo en las filas que salieron de la rama etiquetada — el
25 % de ellas — y false en todas las demás. La etiqueta se deriva de la misma
decisión que eligió la rama, así que nunca puede contradecir al valor.
anomaly="true" es solo una etiqueta: el valor atípico en sí es lo que produzca el
generador de esa rama, y por eso usted conserva el control total sobre cómo se ve.
Este es apenas un rincón de un tema más amplio — la inyección de valores atípicos y
la columna flag reciben su tratamiento completo en la guía de anomalías.
Siguiente paso
- Generador text — proporciones
percentexactas para una lista simple de opciones, cuando las ramas son palabras sueltas. - Secuencias — la fuente con nombre junto a la
cual vive
<mix>, y el modeloparentque comparten. - Dependencias jerárquicas — la
historia completa de
parent, con porcentajes anidados en varios niveles.