Pruebas A/B y Experimentación
Las pruebas A/B miden si un cambio mejora los resultados (conversión, latencia o ingresos) con divisiones de tráfico controladas. Los backends de Python asignan variantes del lado del servidor, registran exposiciones y monitorean las métricas de protección para que los experimentos no se conviertan en interrupciones.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
import hashlib
def variant(user_id: str, experiment: str) -> str:
bucket = int(hashlib.sha256(f"{experiment}:{user_id}".encode()).hexdigest()[:8], 16) % 100
return "B" if bucket < 50 else "A"log.info("experiment_exposure", experiment="checkout_cta", variant=variant(uid, "checkout_cta"))Cuándo usar esto:
- El producto pregunta "¿el nuevo flujo convierte mejor?"
- El cambio en el algoritmo de precios o clasificación necesita evidencia
- Cambio de texto en la UX con un paso medible en el embudo
- Intercambio de modelo o heurística con KPI de negocio
Ejemplo de Trabajo
"""ab_experiment.py - asignación, registro de exposiciones, métricas de protección."""
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from enum import Enum
class Variant(str, Enum):
A = "A"
B = "B"
@dataclass(frozen=True)
class Experiment:
name: str
traffic_pct: int # B recibe este porcentaje
success_metric: str
guardrail_metrics: tuple[str, ...]
def assign_variant(user_id: str, exp: Experiment) -> Variant:
digest = hashlib.sha256(f"{exp.name}:{user_id}".encode()).hexdigest()
bucket = int(digest[:8], 16) % 100
return Variant.B if bucket < exp.traffic_pct else Variant.A
@dataclass
class ExposureEvent:
experiment: str
variant: Variant
user_id: str
def checkout_cta_label(user_id: str, exp: Experiment) -> tuple[str, ExposureEvent]:
v = assign_variant(user_id, exp)
label = "Complete purchase" if v == Variant.B else "Checkout"
return label, ExposureEvent(exp.name, v, user_id)
if __name__ == "__main__":
exp = Experiment("checkout_cta", 50, "checkout_completed", ("p95_latency", "error_rate"))
print(checkout_cta_label("user-42", exp))Lo que esto demuestra:
- Asignación determinista y persistente por usuario y experimento
- Eventos de exposición registrados para los denominadores de análisis
- Métricas de protección definidas de antemano (latencia, errores)
- La lógica de la variante permanece del lado del servidor en los manejadores de Python
Inmersión Profunda
Cómo Funciona
- Hipótesis - Escrita antes del código; la métrica de éxito es un KPI principal.
- Ratio de muestra - 50/50 es común; el análisis de potencia establece la duración cuando es posible.
- Métricas de Protección - Abortar si la tasa de error o la latencia p95 se degradan más allá del umbral.
- Análisis - Usar los denominadores correctos (usuarios expuestos), no todo el tráfico.
- Ética/Cumplimiento - Documentar PII en los registros de experimentos; honrar las regiones de exclusión voluntaria.
Experimento vs. Feature Flag
| Aspecto | Rampa de Feature Flag | Prueba A/B |
|---|---|---|
| Objetivo | Entrega segura | Medir el resultado |
| Duración | Hasta ser estable | Ventana fija |
| Análisis | Opcional | Requerido |
| Rampa | 0→100% | División fija a menudo |
Notas de Python
# Almacenar exposiciones en un esquema amigable para el almacén de datos
EXPOSURE_SCHEMA = {
"experiment": str,
"variant": str,
"user_id": str,
"timestamp": str,
}Trampas Comunes
- Observar los resultados diariamente y detenerse temprano - Infla los falsos positivos. Solución: Registrar previamente la duración o usar métodos de prueba secuencial.
- Asignación no persistente - Los usuarios cambian de variante; los datos son inútiles. Solución: Hashear un ID de usuario estable.
- Asignación del lado del cliente - Sesgada y falsificable. Solución: El servidor asigna; el cliente renderiza.
- Ignorar las métricas de protección - Los ingresos aumentan, las interrupciones aumentan. Solución: Pausar automáticamente el experimento ante la quema del SLO.
- Múltiples experimentos colisionando - Efectos de interacción. Solución: Registro de experimentos; limitar la superposición por superficie.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Bandit multi-brazo | Optimización continua | Necesidad de informe regulatorio fijo |
| Reproducción offline | Ranking de ML | Texto de UX dirigido al usuario |
| Entrevistas a usuarios | El porqué cualitativo | Prueba de impacto estadístico |
| Modo Sombra | Backends de riesgo | Cambios en la UI que requieren interacción |
Preguntas Frecuentes
¿Cuánto tiempo ejecutar experimentos de checkout?
Hasta que el tamaño de la muestra alcance el objetivo de potencia o un máximo de 2-4 semanas con revisión semanal.
¿Podemos hacer pruebas A/B de cambios en API asíncronas?
Sí: asignar en el middleware; medir las métricas de protección de latencia por variante.
¿Qué herramientas se integran?
PostHog, Optimizely del lado del servidor, o almacén de datos + scipy stats en un trabajo por lotes.
¿Cómo manejar a los usuarios de la UE?
Respetar los banners de consentimiento; excluir o anonimizar según la guía legal.
¿Un experimento por endpoint?
Preferir uno principal por paso del embudo; documentar superposiciones en el registro.
¿Experimento fallido - y luego qué?
Enviar el control; archivar aprendizajes; eliminar el código de la variante en una PR de limpieza.
¿Bayesiano vs. Frecuentista?
Cualquiera funciona si el equipo está de acuerdo; la consistencia importa más que el dogma.
¿Experimentar con plantillas de Django?
Asignar en la vista; pasar la variante al contexto de la plantilla; registrar la exposición al renderizar.
¿Prueba A/B de modelos de ML?
Modo sombra o canary de bajo tráfico con métrica offline + métricas de protección online.
¿Quién es el propietario de los experimentos?
El producto define la métrica; la ingeniería implementa la asignación y el registro.
Relacionado
- Feature Flags y Entrega Progresiva - entrega vs. medición
- Métricas DORA - proteger la velocidad de entrega
- Registro Estructurado - eventos de exposición
- Métricas - paneles de métricas de protección
- Priorización de Plataforma y Deuda Técnica - backlog de experimentos
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.