Módulo 8: Project Measure Mercados Recommendations Launch
Revisa las trampas: ¿el resultado aguanta el escrutinio?
Descripción
El resultado de la lección 6 es significativo — p = 0.0114, aunque con una muestra que, según la lección 5, corrió ligeramente por debajo del ideal (12,000 contra los 12,997 que el MDE pre-registrado exigía). Pero "significativo" no es lo mismo que "confiable", y el módulo 7 —construido en paralelo a este capstone— identifica dos formas concretas en que un resultado significativo puede, de todas formas, engañar. Esta lección corre las dos: primero, simpsonCheck(), para confirmar que el lift agregado no esconde una reversión cuando se segmenta por mobile y desktop —la paradoja de Simpson—. Segundo, un chequeo de peeking: qué habría pasado si alguien hubiera mirado el resultado del experimento antes de la semana 6, y por qué esa práctica, aunque parezca inofensiva, infla el riesgo de un falso positivo mucho más de lo que la intuición sugiere.
Conexión con el módulo. Esta es la sexta capa del capstone, y la segunda que retoma el módulo 7. Implementa simpsonCheck(segments) siguiendo la convención descrita en el diseño de ese módulo, y un modelo ejecutable de peeking —primero sobre la traza real semana a semana del experimento de Mercado, después con una simulación que cuantifica cuánto infla el falso positivo la práctica de mirar y parar—. Ninguna de las dos revela un problema oculto en el resultado de la lección 6 — al contrario: las dos lo ponen a prueba y lo confirman. Pero el ejercicio de ponerlo a prueba, no solo de aceptarlo, es exactamente lo que este módulo enseña.
Una analogía: el detector de mentiras que se aplica incluso al testigo que ya creíste
Un investigador serio no deja de verificar la coartada de un sospechoso solo porque ya suena convincente — cruza la historia contra la evidencia física, busca contradicciones, pregunta "¿y si lo miro desde otro ángulo, sigue siendo cierto?". No porque desconfíe sin motivo, sino porque una historia convincente que además resiste el escrutinio es mucho más sólida que una que simplemente nadie cuestionó. El resultado significativo de la lección 6 es la historia convincente. Esta lección es el escrutinio: ¿resiste cuando la miras por segmento? ¿resiste si reconstruyes cómo se llegó a ella, semana por semana?
Ejemplo trabajado: Simpson, y las dos caras del peeking
Parte 1 — simpsonCheck(): ¿el lift se sostiene al segmentar?
// simpsonCheck: compara el lift de cada segmento contra el lift agregado, y marca si
// algun segmento invierte la direccion del efecto respecto al agregado -- la firma de
// la paradoja de Simpson. Modelo del modulo 7, aplicado aqui sobre mobile vs desktop.
function simpsonCheck(segments) {
const bySegment = segments.map((s) => {
const rateControl = s.control.conv / s.control.n;
const rateVariant = s.variant.conv / s.variant.n;
const lift = (rateVariant - rateControl) / rateControl;
return { name: s.name, rateControl, rateVariant, lift, direction: lift > 0 ? 'positive' : lift < 0 ? 'negative' : 'flat' };
});
const totalControl = { n: segments.reduce((a, s) => a + s.control.n, 0), conv: segments.reduce((a, s) => a + s.control.conv, 0) };
const totalVariant = { n: segments.reduce((a, s) => a + s.variant.n, 0), conv: segments.reduce((a, s) => a + s.variant.conv, 0) };
const aggRateControl = totalControl.conv / totalControl.n;
const aggRateVariant = totalVariant.conv / totalVariant.n;
const aggLift = (aggRateVariant - aggRateControl) / aggRateControl;
const aggDirection = aggLift > 0 ? 'positive' : aggLift < 0 ? 'negative' : 'flat';
const reversals = bySegment.filter((s) => s.direction !== aggDirection && s.direction !== 'flat');
return { bySegment, aggregate: { rateControl: aggRateControl, rateVariant: aggRateVariant, lift: aggLift, direction: aggDirection }, reversals, paradoxDetected: reversals.length > 0 };
}
// Los mismos 12,000 usuarios por variante del experimento, desagregados por dispositivo
// -- suman exactamente los totales de la leccion 6 (384 y 456 conversiones). El split
// 40% mobile / 60% desktop es el MISMO que usa la auditoria del modulo 7 (leccion 8)
// sobre este mismo experimento -- una sola fuente de verdad para el desglose real.
const segments = [
{ name: 'mobile', control: { n: 4800, conv: 120 }, variant: { n: 4800, conv: 144 } },
{ name: 'desktop', control: { n: 7200, conv: 264 }, variant: { n: 7200, conv: 312 } },
];
console.log('=== Parte 1: simpsonCheck sobre mobile vs desktop ===\n');
const simpson = simpsonCheck(segments);
simpson.bySegment.forEach((s) => {
console.log(' ' + s.name.padEnd(10) + 'control: ' + (s.rateControl * 100).toFixed(2) + '%' +
' variant: ' + (s.rateVariant * 100).toFixed(2) + '%' +
' lift: ' + (s.lift * 100).toFixed(2) + '% (' + s.direction + ')');
});
console.log('\n agregado control: ' + (simpson.aggregate.rateControl * 100).toFixed(2) + '%' +
' variant: ' + (simpson.aggregate.rateVariant * 100).toFixed(2) + '%' +
' lift: ' + (simpson.aggregate.lift * 100).toFixed(2) + '% (' + simpson.aggregate.direction + ')');
console.log('\n paradoxDetected = ' + simpson.paradoxDetected +
(simpson.paradoxDetected ? ' <- ALERTA' : ' <- los dos segmentos coinciden con el agregado'));
Qué esperar. La salida de la Parte 1:
=== Parte 1: simpsonCheck sobre mobile vs desktop ===
mobile control: 2.50% variant: 3.00% lift: 20.00% (positive)
desktop control: 3.67% variant: 4.33% lift: 18.18% (positive)
agregado control: 3.20% variant: 3.80% lift: 18.75% (positive)
paradoxDetected = false <- los dos segmentos coinciden con el agregado
Leyendo Parte 1. Fíjate en que el agregado (3.20% → 3.80%, lift 18.75%) reproduce exactamente los números de la lección 6 — es la misma suma, solo que ahora abierta por segmento (el mismo split de 40% mobile / 60% desktop que usa la auditoría del módulo 7 sobre este experimento). Los dos segmentos —mobile y desktop— muestran un lift positivo, en la misma dirección que el agregado: mobile mejora un 20.00% relativo, desktop un 18.18% relativo. paradoxDetected: false confirma, con código, lo que la lectura visual ya sugiere: no hay ningún segmento que se mueva en sentido contrario al agregado, escondido detrás de un promedio que "arregla" las cosas combinando dos historias distintas. El lift de recommendations no es un artefacto de cómo se combinaron los datos — se sostiene en ambos canales por los que la gente compra en Mercado.
Parte 2 — La traza real, semana a semana: ¿qué habría visto alguien que espiaba?
Antes de simular nada, vale la pena mirar los datos reales del experimento, reconstruidos semana por semana (los mismos 12,000 usuarios por variante, acumulados en 6 semanas de ~2,000 por semana), y correr abTest() sobre el acumulado de cada semana — exactamente lo que alguien que "espiara" el experimento habría visto en cada momento:
// Reutilizamos erf/normalCdf/abTest EXACTAMENTE como quedaron en la leccion 6 del
// modulo 6 (via la leccion 6 de este capstone), aplicados semana a semana sobre la
// acumulacion real del experimento -- reconstruyendo que habria visto alguien que
// miraba el resultado cada semana, en vez de esperar a la semana 6.
function erf(x) {
const sign = x < 0 ? -1 : 1;
x = Math.abs(x);
const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741,
a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;
const t = 1 / (1 + p * x);
const y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}
function normalCdf(x) { return 0.5 * (1 + erf(x / Math.sqrt(2))); }
function abTest({ control, variant }) {
const { n: n1, conv: conv1 } = control, { n: n2, conv: conv2 } = variant;
const p1 = conv1 / n1, p2 = conv2 / n2;
const pooled = (conv1 + conv2) / (n1 + n2);
const sePooled = Math.sqrt(pooled * (1 - pooled) * (1 / n1 + 1 / n2));
const z = (p2 - p1) / sePooled;
const pValue = 2 * (1 - normalCdf(Math.abs(z)));
return { rateControl: p1, rateVariant: p2, z, pValue, significant: pValue < 0.05 };
}
// Acumulado real semana a semana -- termina exactamente en 12000/384 (control) y
// 12000/456 (variant), los mismos totales de la leccion 6.
const weeklyTrace = [
{ week: 1, controlN: 2000, controlConv: 58, variantN: 2000, variantConv: 80 },
{ week: 2, controlN: 4000, controlConv: 116, variantN: 4000, variantConv: 152 },
{ week: 3, controlN: 6000, controlConv: 180, variantN: 6000, variantConv: 228 },
{ week: 4, controlN: 8000, controlConv: 250, variantN: 8000, variantConv: 304 },
{ week: 5, controlN: 10000, controlConv: 318, variantN: 10000, variantConv: 380 },
{ week: 6, controlN: 12000, controlConv: 384, variantN: 12000, variantConv: 456 },
];
console.log('\n=== Parte 2: la traza real, semana a semana ===\n');
weeklyTrace.forEach((w) => {
const r = abTest({ control: { n: w.controlN, conv: w.controlConv }, variant: { n: w.variantN, conv: w.variantConv } });
console.log(' semana ' + w.week + ' n/grupo=' + String(w.controlN).padStart(5) +
' z=' + r.z.toFixed(4) + ' p=' + r.pValue.toFixed(4) +
' significant=' + r.significant);
});
Qué esperar. La salida de la Parte 2:
=== Parte 2: la traza real, semana a semana ===
semana 1 n/grupo= 2000 z=1.9059 p=0.0567 significant=false
semana 2 n/grupo= 4000 z=2.2368 p=0.0253 significant=true
semana 3 n/grupo= 6000 z=2.4178 p=0.0156 significant=true
semana 4 n/grupo= 8000 z=2.3350 p=0.0195 significant=true
semana 5 n/grupo=10000 z=2.3888 p=0.0169 significant=true
semana 6 n/grupo=12000 z=2.5289 p=0.0114 significant=true
Leyendo Parte 2. La semana 1, con apenas 2,000 usuarios por grupo, el resultado no era significativo (p = 0.0567, apenas por encima del umbral de 0.05). Alguien que hubiera mirado el experimento esa primera semana y hubiera concluido "no está funcionando, cancelemos" habría tomado una decisión prematura y equivocada — el efecto real (que la semana 6 confirma) todavía no tenía suficiente muestra para distinguirse del ruido. A partir de la semana 2, el resultado se vuelve significativo y se mantiene así el resto del experimento, aunque el p-value fluctúa semana a semana (0.0253, 0.0156, 0.0195, 0.0169, 0.0114) — nunca cruza de vuelta hacia arriba de 0.05, pero tampoco baja de forma perfectamente monótona. Este caso específico "salió bien": nadie tomó una decisión equivocada basada en un pico casual de significancia. Pero eso fue, en gran parte, buena suerte con estos datos particulares — no una propiedad garantizada de la práctica de mirar y decidir semana a semana.
Parte 3 — Cuantificando el riesgo: la simulación de peeking
Para ver por qué la práctica de "mirar cada semana y parar en el primer p < 0.05" es riesgosa en general —más allá de que esta vez no causó ningún daño—, simulamos miles de experimentos donde, por construcción, no hay ningún efecto real (control y variant con la misma tasa verdadera de conversión), y comparamos dos formas de decidir: mirar solo al final (correcto) contra mirar cada semana y parar en el primer p < 0.05 (peeking).
// Simulacion de peeking: bajo un efecto NULO conocido (control y variant con la misma
// tasa verdadera), compara la tasa de falsos positivos de "mirar solo al final" contra
// "mirar cada semana y parar en el primer p<0.05". Usa un generador pseudoaleatorio
// sembrado (mulberry32) para que la corrida sea reproducible con node -- y aproxima cada
// conversion semanal con una normal (Box-Muller), declarado como aproximacion estandar
// cuando n*p es razonablemente grande (aqui, ~64), igual que la CDF normal de abTest().
function mulberry32(seed) {
return function () {
seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function randomNormal(rng) {
const u1 = Math.max(rng(), 1e-12), u2 = rng();
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}
function simulateWeeklyConversions(rng, nPerWeek, trueRate) {
const mean = nPerWeek * trueRate;
const sd = Math.sqrt(nPerWeek * trueRate * (1 - trueRate));
const draw = Math.round(mean + sd * randomNormal(rng));
return Math.max(0, Math.min(nPerWeek, draw));
}
function peekingSimulation({ trials, weeks, nPerWeek, trueRate, alpha = 0.05, seed }) {
const rng = mulberry32(seed);
let peekingFalsePositives = 0;
let fixedFalsePositives = 0;
for (let t = 0; t < trials; t++) {
let cumControlN = 0, cumControlConv = 0, cumVariantN = 0, cumVariantConv = 0;
let anyWeekSignificant = false, finalWeekSignificant = false;
for (let w = 0; w < weeks; w++) {
cumControlN += nPerWeek; cumVariantN += nPerWeek;
cumControlConv += simulateWeeklyConversions(rng, nPerWeek, trueRate);
cumVariantConv += simulateWeeklyConversions(rng, nPerWeek, trueRate);
const r = abTest({ control: { n: cumControlN, conv: cumControlConv }, variant: { n: cumVariantN, conv: cumVariantConv } });
if (r.pValue < alpha) anyWeekSignificant = true;
if (w === weeks - 1) finalWeekSignificant = r.pValue < alpha;
}
if (anyWeekSignificant) peekingFalsePositives++;
if (finalWeekSignificant) fixedFalsePositives++;
}
return { trials, peekingFalseRate: peekingFalsePositives / trials, fixedFalseRate: fixedFalsePositives / trials };
}
console.log('\n=== Parte 3: cuanto infla el peeking el falso positivo (bajo H0 real) ===\n');
const sim = peekingSimulation({ trials: 4000, weeks: 6, nPerWeek: 2000, trueRate: 0.032, alpha: 0.05, seed: 20260801 });
console.log('trials = ' + sim.trials + ' (control y variant con la MISMA tasa verdadera, 3.2%)');
console.log('fixedFalseRate (mirar solo al final): ' + (sim.fixedFalseRate * 100).toFixed(2) + '% (nominal: 5%)');
console.log('peekingFalseRate (mirar cada semana, parar 1er p<0.05): ' + (sim.peekingFalseRate * 100).toFixed(2) + '%');
console.log('\nInflacion: ' + (sim.peekingFalseRate / sim.fixedFalseRate).toFixed(2) + 'x mas falsos positivos con peeking.');
Qué esperar. La salida de la Parte 3 (determinista: la semilla fija 20260801 produce siempre el mismo resultado):
=== Parte 3: cuanto infla el peeking el falso positivo (bajo H0 real) ===
trials = 4000 (control y variant con la MISMA tasa verdadera, 3.2%)
fixedFalseRate (mirar solo al final): 5.88% (nominal: 5%)
peekingFalseRate (mirar cada semana, parar 1er p<0.05): 16.80%
Inflacion: 2.86x mas falsos positivos con peeking.
Leyendo el resultado completo: por qué esto importa incluso cuando "salió bien"
La Parte 3 es la pieza que le da peso real a la advertencia sobre peeking. Bajo un escenario simulado donde, por construcción, no hay ningún efecto real entre control y variant (los dos tienen exactamente la misma tasa verdadera de conversión), la disciplina correcta —definir de antemano cuándo se va a mirar el resultado, y mirarlo una sola vez— produce una tasa de falsos positivos cercana al 5% nominal (5.88% en esta simulación, dentro del margen esperado de ruido de simulación). Pero la práctica de mirar cada semana y detenerse en el primer p < 0.05 que aparece infla esa tasa a 16.80% — casi 3 veces más falsos positivos de los que el umbral alpha = 0.05 promete.
La razón matemática es simple de enunciar, aunque contraintuitiva: cada semana que miras el resultado es, en efecto, una nueva oportunidad de que el ruido de muestreo produzca un p < 0.05 por pura casualidad, aunque no haya ningún efecto real. Mirar seis veces en vez de una no multiplica el riesgo por seis exactamente (las miradas están correlacionadas, porque cada una incluye los datos de la anterior), pero sí lo infla sustancialmente — el mismo fenómeno, en esencia, que el problema de comparaciones múltiples.
Esto conecta directamente con la Parte 2: el experimento real de Mercado, mirado semana a semana, habría dado un p < 0.05 a partir de la semana 2 y lo habría mantenido así el resto del experimento — en este caso particular, mirar temprano no habría llevado a una decisión equivocada, porque el efecto real (confirmado en la semana 6, con toda la muestra planeada) resultó ser genuino. Pero la Parte 3 demuestra que esa "suerte" no es garantía: si el equipo hubiera repetido esta misma práctica de peeking en, digamos, veinte experimentos distintos sin ningún efecto real detrás, casi 1 de cada 6 (16.80%, contra el 5% esperado) habría producido un falso "gana variant" solo por mirar en el momento equivocado y parar ahí. La disciplina de definir de antemano cuándo mirar —el mismo protocolo que ya viste en el módulo 5— no es un detalle burocrático: es lo que mantiene la tasa de falsos positivos en el 5% que el alpha promete, en vez de en el 16.80% que produce mirar sin disciplina.
Errores comunes
Confundir "en este caso no hubo daño" con "peeking no es un problema". Qué pasa: alguien revisa la Parte 2 de esta lección, nota que mirar semana a semana habría dado la misma conclusión final (significativo, enviar), y concluye que el peeking, en la práctica, no importa tanto. Por qué pasa: el resultado real de Mercado "salió bien" bajo cualquier disciplina de mirada, y es tentador generalizar de un solo caso a una regla general. Cómo detectarlo: si la conclusión es "no pasó nada, no hay que preocuparse por esto", ignora por completo la Parte 3 —la simulación bajo un efecto nulo real, donde el peeking sí infla el falso positivo de forma medible—. Cómo corregirlo: separa siempre "qué pasó esta vez" (Parte 2, un solo experimento, con un efecto real detrás) de "qué pasa en general" (Parte 3, miles de experimentos simulados, algunos con efecto real y otros —bajo H0— sin ninguno). La disciplina de no espiar protege contra el segundo escenario, no contra el primero.
Interpretar paradoxDetected: false como "ya no hace falta revisar por segmento nunca más". Qué pasa: alguien concluye que, como esta vez el lift se sostuvo en mobile y desktop, futuros experimentos de Mercado no necesitan repetir el chequeo de Simpson. Por qué pasa: un resultado tranquilizador se siente como una regla general, cuando en realidad es la verificación de un caso específico. Cómo detectarlo: si un experimento nuevo se reporta como "significativo, enviar" sin haber corrido simpsonCheck() sobre sus propios segmentos, la disciplina se abandonó apenas dejó de sentirse necesaria. Cómo corregirlo: simpsonCheck() es una verificación que se corre en cada experimento, no una confirmación única que se hereda de un experimento anterior — cada lanzamiento tiene su propia composición de segmentos y su propio riesgo de paradoja.
Tratar el chequeo de trampas como un paso final decorativo, después de que la decisión "ya está tomada". Qué pasa: el equipo decide enviar recommendations apenas ve significant: true en la lección 6, y corre simpsonCheck() y el chequeo de peeking solo para "completar el proceso", sin que el resultado de esos chequeos pudiera, en principio, cambiar la decisión. Por qué pasa: una vez que un resultado se siente "ganado", revisar más a fondo se siente como formalidad, no como parte real del análisis. Cómo detectarlo: si nadie en el equipo puede decir qué habría pasado con la decisión si paradoxDetected hubiera dado true, el chequeo se corrió sin intención real de dejarse influir por él. Cómo corregirlo: las trampas de esta lección son parte del mismo proceso de decisión que la significancia de la lección 6 — si un lift significativo se invirtiera al segmentar, o si el resultado dependiera de haber mirado en el momento justo, la decisión de la lección 8 tendría que cambiar, no solo anotarse como una curiosidad.
Ejercicios
Ejercicio 1 — Diseña un caso con paradoja real. Construye un ejemplo hipotético de dos segmentos donde el lift agregado sea positivo, pero ambos segmentos individuales tengan lift negativo o nulo (la firma clásica de la paradoja de Simpson: el agregado miente porque combina proporciones distintas de cada segmento entre control y variant). No hace falta ejecutar Node — describe los números con los que simpsonCheck() marcaría paradoxDetected: true.
Ver solución
Un ejemplo posible: mobile tiene control: {n: 9000, conv: 270} (3.0%) y variant: {n: 3000, conv: 87} (2.9%) — lift levemente negativo. desktop tiene control: {n: 1000, conv: 50} (5.0%) y variant: {n: 7000, conv: 343} (4.9%) — también levemente negativo. El truco de la paradoja está en que variant tiene mucho más peso en desktop (7,000 de sus 10,000 usuarios) que control (solo 1,000 de sus 10,000), y desktop convierte mejor en general (~5%) que mobile (~3%). Sumando: control total = (270+50)/10000 = 3.20%; variant total = (87+343)/10000 = 4.30% — un lift agregado positivo, a pesar de que ambos segmentos, por separado, empeoraron levemente. La causa no es ningún efecto real de variant: es que variant tiene más usuarios en el segmento que de por sí convierte mejor (desktop), inflando el promedio agregado sin que el carrusel haya ayudado en ningún segmento individual. simpsonCheck() marcaría paradoxDetected: true porque ambos segmentos tienen direction: 'negative' mientras el agregado tiene direction: 'positive'.
Ejercicio 2 — Recalcula la simulación con más semanas de peeking. Sin ejecutar Node todavía, ¿esperarías que la tasa de falsos positivos por peeking suba o baje si el equipo mirara el resultado 12 veces (dos veces por semana) en vez de 6? Justifica con el argumento de la sección "Leyendo el resultado completo", y después, si tienes Node a mano, verifica corriendo peekingSimulation({ trials: 4000, weeks: 12, nPerWeek: 1000, trueRate: 0.032, alpha: 0.05, seed: 20260801 }).
Ver solución
Debería subir. Cada mirada adicional es una nueva oportunidad de que el ruido de muestreo produzca, por casualidad, un p < 0.05 — más miradas, más oportunidades, mayor inflación del falso positivo, aunque el efecto no crece de forma lineal (las miradas sucesivas están correlacionadas entre sí, porque comparten la mayoría de los datos acumulados). Al correr la simulación con weeks: 12 y nPerWeek: 1000 (para mantener el mismo total de usuarios acumulados al final), el resultado da una peekingFalseRate más alta que el 16.80% de la simulación con 6 miradas — consistente con la intuición de que espiar con más frecuencia agrava el problema, no lo diluye.
Ejercicio 3 — Conecta las dos trampas con la decisión final. En una o dos frases, explica por qué el resultado de esta lección —paradoxDetected: false y una traza semanal que, en este caso, no habría llevado a una decisión equivocada por peeking— le da a la lección 8 una base más sólida para su decisión final que si esta lección no se hubiera corrido en absoluto.
Ver solución
Sin esta lección, la decisión de la lección 8 se apoyaría solo en un p-value significativo (lección 6) sin haber descartado dos formas concretas en que ese número pudiera estar mintiendo: que el efecto positivo agregado escondiera una realidad distinta por segmento (Simpson), o que el proceso de llegar a ese resultado hubiera estado contaminado por decisiones tomadas a mitad de camino (peeking). Con paradoxDetected: false y la confirmación de que el protocolo del módulo 5 —correr las 6 semanas completas, sin parar antes— se respetó, la lección 8 puede construir su decisión final sobre un resultado que no solo es significativo, sino que además resistió el escrutinio que este módulo enseña a aplicar antes de confiar en cualquier veredicto estadístico.
Resumen y siguiente paso
En esta lección pusiste a prueba el resultado significativo de la lección 6 desde dos ángulos que el z-test, por sí solo, no cubre. simpsonCheck() confirmó que el lift agregado (18.75%) se sostiene en ambos segmentos —mobile (20.00%) y desktop (18.18%)—, sin ninguna reversión: paradoxDetected: false. La traza real semana a semana mostró que, en este caso particular, mirar temprano no habría cambiado la conclusión final, pero la simulación bajo un efecto nulo conocido demostró, con números (5.88% contra 16.80%, una inflación de 2.86x), por qué esa disciplina de no espiar importa en general, más allá de que esta vez no causara daño.
Con esto, las seis capas del método están completas: qué medir y dónde (L2), retención (L3), North Star y guardrails (L4), tamaño de muestra (L5), significancia (L6), y trampas (esta lección). La lección 8 —el proyecto final— junta las seis en un solo reporte, corre la cadena completa en Node de principio a fin, y toma la decisión que toda la guía preparó: enviar, revertir, o iterar.
Recursos
- Evan Miller, "How Not To Run An A/B Test" — evanmiller.org/how-not-to-run-an-ab-test.html. El argumento original y más citado sobre por qué el peeking infla el falso positivo, la base conceptual de la Parte 3 de esta lección. En inglés.
- Evan Miller, "Simple Sequential A/B Testing" — evanmiller.org/ab-testing/sequential.html. Sobre los métodos que sí permiten mirar un experimento varias veces sin inflar el falso positivo —fuera del alcance de esta guía, pero el siguiente paso natural si el equipo de Mercado quisiera mirar sus experimentos con más frecuencia. En inglés.
- Wikipedia, "Simpson's paradox" — en.wikipedia.org/wiki/Simpson's_paradox. La referencia formal del fenómeno que
simpsonCheck()detecta, con ejemplos históricos reales del mismo patrón que construiste en el Ejercicio 1. En inglés.