Prueba A/B
En resumen: Una prueba A/B compara dos variantes en visitantes repartidos al azar y comprueba si la diferencia en la conversión es real o solo ruido.
Problema
«¿Es mejor el carrito nuevo?»
El equipo hizo una página de carrito nueva: más corta, con menos campos. Nadie sabe si es mejor. Así que durante dos semanas cada visita al sitio ve, al azar, el carrito viejo (old) o el nuevo (new). La columna ordered es 1 si la visita termina con un pedido:
Esto es una prueba A/B: dos versiones, visitantes repartidos al azar, una medida. La medida aquí es la conversión: la proporción de visitas que terminan con un pedido. El carrito viejo tiene el 3,10 %, el nuevo el 3,72 %.
La diferencia es de 0,62 puntos porcentuales: una diferencia entre dos porcentajes. Eso es cerca de una quinta parte más de pedidos. Pero ¿es ruido?
La idea
¿Es ruido la diferencia?
Esta es la pregunta de la lección sobre hipótesis. La hipótesis nula: el carrito no cambia nada. Si es cierta, las etiquetas old y new no importan y puedes mezclarlas. ¿Con qué frecuencia dan los datos mezclados una diferencia de al menos 0,62 puntos?
En 2000 mezclas la mayor diferencia es de 0,76 puntos, pero solo 6 veces es al menos tan grande como la real. p es 0,003. Una diferencia así rara vez viene del ruido.
Más precisamente, para curiosos
ordered es 0 o 1, así que la media de la columna es exactamente la proporción de pedidos. La mezcla aquí es un bucle: la tabla tiene 28 176 filas, y 2000 mezclas a la vez en una matriz necesitarían mucha memoria en el navegador.
Pruébalo
¿En cada dispositivo?
¿La mejora viene de los teléfonos o de las computadoras de escritorio? Guarda en rates la conversión en porcentaje para cada dispositivo (filas) y variante (columnas), y en lift la diferencia new − old para cada dispositivo, en puntos.
Solución
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
rates = visits.pivot_table(index='device', columns='variant', values='ordered', aggfunc='mean') * 100
lift = rates['new'] - rates['old']
print(rates.round(2))
print(lift.round(2))visits.pivot_table(index='device', columns='variant', values='ordered', aggfunc='mean') * 100 da una tabla de 2 × 2. En escritorio la conversión es del 4,20 % con el carrito viejo y del 5,26 % con el nuevo, en móvil del 2,51 % y del 2,90 %. La mejora está en los dos dispositivos: 1,05 y 0,39 puntos. Con la columna revenue calcularías los ingresos por visita, no la conversión.
La idea
El camino más rápido: chi2_contingency
Para dos proporciones hay una comprobación lista. Primero pd.crosstab cuenta las parejas: cuántas visitas de cada variante tienen pedido y cuántas no. Luego scipy.stats.chi2_contingency dice cuán rara es una tabla así si la variante no cambia nada:
p es 0,0046, cerca de 0,005. La mezcla dio 0,003: los dos caminos dicen lo mismo. La pequeña diferencia se debe a que 2000 mezclas miden p de forma aproximada (6 casos), y chi2_contingency hace una pequeña corrección para una tabla de 2 × 2, que sube p un poco.
La idea
¿Cuán grande es la mejora?
p solo dice «difícilmente ruido». El jefe pregunta otra cosa: ¿cuánto mejor es el carrito nuevo? Para eso necesitas un intervalo. Como en la lección sobre intervalos, sacas de cada grupo muestras nuevas con reemplazo y observas cómo cambia la diferencia:
La mejora es de 0,62 puntos, y el intervalo del 95 % va de 0,21 a 1,04 puntos. El cero queda fuera del intervalo: es el mismo «difícilmente ruido». Pero el intervalo dice también cuánto: puede ser algo más de 0,2, puede ser más de 1 punto.
Más precisamente, para curiosos
Aquí son 2000 repeticiones, no 10 000, para que corra rápido: cada una saca 14 000 filas. Según la fórmula, el intervalo de una diferencia entre dos proporciones es la diferencia ± 1,96 × √(p₁(1 − p₁)/n₁ + p₂(1 − p₂)/n₂). Da casi lo mismo:
La idea
Cuántas visitas necesitas
¿Cuánto debe durar la prueba? Eso lo decides antes de empezar, a partir de dos números: la conversión hasta ahora (3,1 %) y la menor mejora que te importa (0,6 puntos). Hay una regla práctica para una probabilidad del 80 % de ver la mejora con el umbral de 0,05:
visitas por grupo ≈ 16 × p × (1 − p) / mejora²
Necesitas 13 351 visitas por grupo, 26 701 en total. La probabilidad de que la prueba detecte una mejora real se llama potencia. Puedes comprobarla con una simulación: inventa 2000 pruebas con conversiones del 3,1 % y del 3,7 % y cuenta con qué frecuencia p sale por debajo de 0,05. rng.binomial(n, 0.031) da cuántos pedidos salen de n visitas, cada una con un 3,1 % de probabilidad:
Con 2000 visitas por grupo la prueba detecta la mejora solo en el 18 % de los casos, con 5000 en el 37 %. Con 13 351 es el 78 %, cerca del 80 % de la regla. Una prueba pequeña no dice «no hay diferencia»: simplemente no puede verla.
Más precisamente, para curiosos
p_value es la comprobación de dos proporciones con una fórmula (una prueba z). Trabaja con arreglos enteros a la vez, así que 2000 pruebas tardan un momento. Para una tabla de 2 × 2 da casi lo mismo que chi2_contingency. El 16 de la regla viene del umbral de 0,05 y una potencia del 80 %: (1,96 + 0,84)² × 2 ≈ 16.
Pruébalo
Una mejora mayor
El equipo de producto dice: «Solo nos importa una mejora de al menos 1 punto.» ¿Cuántas visitas por grupo necesita la regla? Guárdalas en per_group como un número entero.
Solución
base = 0.031
lift = 0.01
per_group = round(16 * base * (1 - base) / lift ** 2)
print(per_group)round(16 * 0.031 * (1 - 0.031) / 0.01 ** 2) da 4806 por grupo. La mejora es 0.01, una proporción, no 1. Una mejora mayor se ve con menos datos: 1 punto necesita unas 2,8 veces menos visitas que 0,6 puntos, porque (1 / 0,6)² ≈ 2,8.
La idea
No mires antes del final
La tentación es mirar el resultado cada día. Aquí está p calculada con todas las visitas hasta cada día:
El día tres p es 0,053, el día cuatro 0,077, el día cinco 0,041. Si hubieras parado el día cinco, habrías declarado una victoria. Aquí el carrito nuevo de verdad es mejor, pero eso se ve por la prueba entera. La regla «paro en cuanto p baje de 0,05» es el error mismo.
¿Por qué? Compruébalo con una prueba A/A: los dos grupos ven lo mismo, y no hay diferencia. Simulamos 1000 pruebas así de 14 días y miramos p cada día:
Si solo miras al final, las falsas «victorias» son el 4,9 %, tanto como promete el umbral de 0,05. Si miras cada día y paras en la primera p por debajo de 0,05, son el 21,7 %: cuatro veces más. El ruido tiene 14 oportunidades de caer por debajo de la línea.
La idea
Muchas métricas, muchos segmentos
El carrito nuevo trae más pedidos. ¿Son también más grandes? Compara el valor por pedido:
26,30 € con el carrito nuevo y 26,71 € con el viejo, p = 0,73. No hay evidencia de que el carrito cambie el valor del pedido. Eso no es «hemos demostrado que son iguales»: puede haber una diferencia pequeña, pero esta prueba no la ve.
Imagina ahora un informe con 20 métricas: pedidos, valor, devoluciones, tiempo en la página… Cada métrica sin una diferencia real tiene un 5 % de probabilidad de salir con p por debajo de 0,05. ¿Con qué frecuencia «gana» al menos una?
Una métrica «gana» en el 5,1 % de los casos, como se esperaba. Pero al menos una de 20 «gana» en el 64,1 % de las pruebas. Lo mismo vale para los segmentos: móvil, escritorio, clientes nuevos, cada día de la semana. Por eso la métrica principal se elige antes de la prueba. Una «victoria» casual en la métrica número 20 es una idea para la próxima prueba, no una conclusión.
La idea
Por qué la prueba dice «causa»
¿Por qué crees que el carrito causa más pedidos y no solo los acompaña? Porque la variante se asignó al azar. El azar iguala todo lo que no mides. Aquí están los dispositivos en los dos grupos:
En los dos grupos los de escritorio son el 35 % y los teléfonos el 65 %. Lo mismo vale para la hora, el día, los hábitos de la gente: los grupos se diferencian solo en el carrito. Así que la diferencia en los pedidos viene de él.
Cuando los datos no son de una prueba, no es así. En la próxima lección un banner parece subir los pedidos, pero en cada dispositivo por separado no lo hace.
Ejercicio
Un informe para el jefe
El jefe pregunta: «¿Trae el carrito nuevo más dinero por visita?» Guarda en per_visit los ingresos medios por visita de cada variante (todas las visitas, también las que no tienen pedido) y en p el valor p de la comparación con scipy.stats.ttest_ind.
Solución
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
from scipy import stats
per_visit = visits.groupby('variant')['revenue'].mean()
new = visits.loc[visits['variant'] == 'new', 'revenue']
old = visits.loc[visits['variant'] == 'old', 'revenue']
p = stats.ttest_ind(new, old).pvalue
print(per_visit.round(2))
print(round(p, 3))visits.groupby('variant')['revenue'].mean() da 0,98 € por visita con el carrito nuevo y 0,83 € con el viejo, y ttest_ind da p ≈ 0,032. Las visitas sin pedido aportan 0 € y cuentan en la media. Si tomas solo los pedidos, obtienes 26,30 € y 26,71 € y p = 0,73: otra pregunta. La conclusión para el informe: el carrito nuevo trae más dinero porque trae más pedidos, no más grandes.
Si lo hubieras planificado desde el principio: ¿cuántos días debería haber durado la prueba? Las visitas por día son de media len(visits) / 14. Guarda en per_group las visitas por grupo según la regla (una mejora de 0,6 puntos con un 3,1 %), en per_day las visitas por día y en days los días, redondeados hacia arriba.
Solución
import math
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
base = 0.031
lift = 0.006
per_group = 16 * base * (1 - base) / lift ** 2
per_day = len(visits) / 14
days = math.ceil(2 * per_group / per_day)
print(round(per_day), days)Necesitas unas 13 351 visitas por grupo, y hay dos grupos: unas 26 701 en total. Entran unas 2013 visitas al día, así que los días son math.ceil(26701 / 2013) = 14. La prueba se planificó exactamente para ese tiempo. Con per_group / per_day saldrían 7 días: suficiente para un grupo.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: El primer gráfico · Muestra y población · Intervalos de confianza · Contrastar una hipótesis
- Continúa en: Correlación y causa
- Ejercicios sobre el tema: Estadística para decidir