Capítulo 21 de 23 9 secciones 10 min

Compartir

Las seis preguntas que te van a hacer

Cada pregunta de una reunión tiene una cuenta detrás. Llevarlas hechas es la diferencia entre defender tu análisis y volver la semana que viene.

Las preguntas que hacen en una reunión son casi siempre las mismas seis, y cada una tiene una cuenta detrás: el intervalo, la línea base, la traducción a dinero, la apertura por segmento, si la muestra alcanzaba y si hubo asignación al azar. Llevarlas calculadas de antemano es lo que separa defender un análisis de volver la semana que viene 🧾

Ya tienes el titular y ya elegiste qué mostrar. Ahora te van a preguntar.

Y la buena noticia es que las preguntas son casi siempre las mismas seis. La mala es que cada una necesita una cuenta que no está en tu cuaderno todavía 🌸

import pandas as pd
import numpy as np
from math import ceil

URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'
df = pd.read_csv(URL)
df['monto'] = pd.to_numeric(df['monto'], errors='coerce')
d = df[['canal', 'compro', 'segmento', 'ciudad', 'monto']].dropna(
    subset=['canal', 'compro'])
sub = d[d['canal'].isin(['WhatsApp', 'Web'])]

w = sub[sub['canal'] == 'WhatsApp']['compro']
b = sub[sub['canal'] == 'Web']['compro']
dif = float(w.mean() - b.mean())
se = float(np.sqrt(w.var(ddof=1) / len(w) + b.var(ddof=1) / len(b)))
print('diferencia:', round(dif, 4), '| error estandar:', round(se, 4))
diferencia: 0.0694 | error estandar: 0.0248

1. "¿Y qué tan seguro estás?"

print('entre', round((dif - 1.96 * se) * 100, 1), 'y',
      round((dif + 1.96 * se) * 100, 1), 'puntos')
entre 2.1 y 11.8 puntos

Es la pregunta más frecuente y la más fácil, porque ya la contestaste en el capítulo 10.

Lo que hay que resistir es la tentación de contestar "el p-valor es 0,0053". Nadie sabe qué hacer con eso. "Entre 2 y 12 puntos" lo entiende todo el mundo y dice exactamente lo mismo, con la ventaja de que trae el tamaño dentro 🧾

2. "¿Y si no hacemos nada?"

print('la web ya convierte:', round(float(b.mean()), 4))
print('con 1.000 clientes ya compran', round(float(b.mean()) * 1000))
la web ya convierte: 0.583
con 1.000 clientes ya compran 583

Esta es la que más análisis hunde, y casi nadie la lleva preparada.

La web ya convierte 583 de cada 1.000. Tu propuesta no trae 652 ventas nuevas: trae 69 más de las que ya había. Sin la línea base al lado, el número grande parece todo el mérito del proyecto.

Se llama línea base y es la respuesta a "¿comparado con qué?". Cuando alguien te presente una mejora sin decirte la suya, esa es la pregunta que hay que hacer.

3. "¿Cuánta plata es?"

ticket = float(d['monto'].dropna().mean())
print('centro    :', round(dif * 1000 * ticket), 'soles')
print('borde bajo:', round((dif - 1.96 * se) * 1000 * ticket), 'soles')
centro    : 56086 soles
borde bajo: 16733 soles

Los dos números, siempre. Si llevas solo el 56.086, alguien va a poner eso en un presupuesto y tú vas a ser la responsable de que no llegue.

Y la frase que hay que decir en voz alta: "esto asume 1.000 clientes y el ticket medio actual, y las dos cosas pueden cambiar".

4. "¿Funciona para todos?"

for seg in sorted(sub['segmento'].dropna().unique()):
    s = sub[sub['segmento'] == seg]
    a = s[s['canal'] == 'WhatsApp']['compro']
    c = s[s['canal'] == 'Web']['compro']
    print(seg.ljust(11), 'dif', round(float(a.mean() - c.mean()), 3))
Bodega      dif 0.063
Horeca      dif 0.016
Mayorista   dif 0.075
Minimarket  dif 0.123

No. Horeca casi no se mueve, y eso es lo que trabajamos en el capítulo 20.

Si esta pregunta te la hacen y no la tienes hecha, la reunión se acaba ahí: "vuelve cuando lo hayas abierto".

5. "¿Te alcanzó la muestra?"

Esta la hacen poco y es la que más duele cuando llega:

def muestra_necesaria(p1, p2):
    pb = (p1 + p2) / 2
    return ceil((1.96 * np.sqrt(2 * pb * (1 - pb))
                 + 0.84 * np.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2
                / (p1 - p2) ** 2)

n = muestra_necesaria(float(w.mean()), float(b.mean()))
print('hacian falta por grupo:', n)
print('tengo                 :', len(w), 'y', len(b))
hacian falta por grupo: 768
tengo                 : 725 y 801

768 hacían falta, y en WhatsApp tengo 725. Me quedé corta en uno de los dos grupos.

Esto no invalida el análisis: la diferencia salió significativa igual. Lo que dice es que estaba justo en el límite, y que si el efecto hubiera sido un poco más chico, no lo habría visto. Es exactamente la advertencia del capítulo 18, y decirla tú antes de que la pregunten vale mucho.

6. "¿Esto es causa o solo pasa que...?"

La pregunta difícil, y la única que no se contesta con una cuenta.

mezcla = sub.groupby('canal')['segmento'].value_counts(normalize=True).round(3)
print(mezcla.to_string())
canal     segmento  
Web       Minimarket    0.267
          Mayorista     0.250
          Bodega        0.247
          Horeca        0.236
WhatsApp  Horeca        0.261
          Minimarket    0.257
          Bodega        0.244
          Mayorista     0.239

Los dos canales tienen prácticamente la misma mezcla de segmentos, entre 24% y 27% cada uno. O sea que el segmento no explica la diferencia.

Y aun así la respuesta correcta es: no lo sé. Porque el canal lo eligió el cliente, no yo. Puede que quien usa WhatsApp ya venga con más ganas de comprar, y eso no lo mide ninguna de mis columnas.

Lo que sí puedo decir es qué haría falta para saberlo: repartir clientes al azar entre los dos canales, que es la prueba A/B del capítulo 18. Sin eso, es correlación, y llamarla causa es lo que trabajamos en el capítulo 15.

Y cuando el resultado no es el que querían

Pasa, y es el momento donde más se nota quién sabe hacer esto.

Lo que no funciona: suavizarlo, buscar el corte que sí dio, o decir "hay una tendencia" cuando el intervalo cruza el cero.

Lo que funciona son tres frases, en este orden:

  1. El resultado, sin adornos. "No encontramos diferencia."
  2. Qué descarta. "Con esta muestra podríamos haber visto diferencias de 7 puntos o más, así que si existe algo, es menor que eso." Eso es información y vale dinero.
  3. Qué harías ahora. "Con este presupuesto yo probaría otra cosa antes que insistir acá."

Un no medido es un resultado. Un no disfrazado de sí es un problema que aparece seis meses después, cuando ya nadie se acuerda de tu lámina 💛

Lo que te llevas

  • Las seis preguntas son casi siempre las mismas: certeza, línea base, dinero, segmentos, muestra y causa.
  • El intervalo se dice en puntos, no en p-valor.
  • Sin línea base, cualquier mejora parece más grande de lo que es.
  • El dinero va con su borde bajo y con sus supuestos dichos.
  • La muestra necesaria se calcula, y decir que te quedaste corta te da crédito.
  • La causa la da el diseño, no la prueba.
  • Un resultado negativo se presenta con lo que descarta y con qué harías ahora.

Comprueba que lo tienes

Presentas que WhatsApp sube 6,9 puntos y alguien pregunta "¿y eso es causa o solo pasa que los mejores clientes usan WhatsApp?". ¿Qué contestas?

  • Que el canal lo eligió el cliente, así que es correlación, y qué haría falta para saberlo
  • Que el valor p de 0,0053 demuestra que es causa
  • Que sí, porque comprobé que los segmentos están parejos
  • Que con más datos se sabría

Ejercicios

1. Cuánta muestra para ver diferencias más chicas

La cuenta que hay que hacer antes de empezar, no después.

base = float(b.mean())
for efecto in (0.07, 0.05, 0.03, 0.01):
    n = muestra_necesaria(base + efecto, base)
    print('para ver', round(efecto * 100, 1), 'puntos hacen falta',
          n, 'por grupo')
para ver 7.0 puntos hacen falta 755 por grupo
para ver 5.0 puntos hacen falta 1494 por grupo
para ver 3.0 puntos hacen falta 4187 por grupo
para ver 1.0 puntos hacen falta 37984 por grupo

Para ver un punto hacen falta casi 38.000 por grupo. Cuando alguien te pida "medir si el cambio del botón mejora algo", esta tabla es la que te dice si es posible con el tráfico que tienen. Casi siempre no lo es, y saberlo antes ahorra un trimestre.

2. El error de pedir certeza sobre nada

Pregúntale a la fórmula cuánta muestra hace falta para detectar una diferencia de cero.

muestra_necesaria(0.60, 0.60)
OverflowError: cannot convert float infinity to integer

Infinita. Y no es un capricho de la fórmula: no existe una muestra que te permita demostrar que dos cosas son exactamente iguales.

Por eso "no encontramos diferencia" nunca quiere decir "son iguales". Lo que se puede decir es lo del punto 2 de arriba: cuál es la diferencia más chica que habrías podido ver.

3. Qué descarta tu no

Convierte un resultado negativo en información.

n_real = min(len(w), len(b))
for efecto in (0.10, 0.07, 0.05, 0.03):
    hace_falta = muestra_necesaria(float(b.mean()) + efecto, float(b.mean()))
    print('efecto de', round(efecto * 100), 'puntos ->',
          'lo habria visto' if hace_falta <= n_real else 'no me alcanzaba')
efecto de 10 puntos -> lo habria visto
efecto de 7 puntos -> no me alcanzaba
efecto de 5 puntos -> no me alcanzaba
efecto de 3 puntos -> no me alcanzaba

Esa es la frase entera: "si hubiera un efecto de 10 puntos o más lo habría visto; de 7 para abajo, con esta muestra no puedo decir nada".

Y fíjate en lo justo que quedó: 7 puntos necesitaban 755 por grupo y tengo 725. Por treinta filas. El efecto que encontré, 6,9 puntos, está exactamente en esa zona donde la muestra no daba garantías, y salió igual. Tuve suerte, y eso también se dice.

Convertir un no en un límite superior es lo que hace que un resultado negativo sirva para decidir en vez de tirarse a la basura.

4. La línea base que nadie te dio

Compara la mejora contra lo que ya pasaba, no contra cero.

base = float(b.mean())
print('sin hacer nada, de cada 1000:', round(base * 1000))
print('con el cambio, de cada 1000 :', round(float(w.mean()) * 1000))
print('ventas nuevas               :', round(dif * 1000))
print('la mejora es el', round(dif / float(w.mean()) * 100, 1),
      '% de lo que se atribuye el proyecto')
sin hacer nada, de cada 1000: 583
con el cambio, de cada 1000 : 652
ventas nuevas               : 69
la mejora es el 10.6 % de lo que se atribuye el proyecto

De las 652 ventas que el proyecto va a poder mostrar como suyas, 583 iban a pasar igual. Solo el 10,6% es mérito del cambio. Esa cuenta es la que evita que un proyecto se cuelgue medallas ajenas, y sirve para leer las de otros.

5. Comprueba si un factor te está confundiendo

El chequeo del punto 6, hecho función.

def mezcla_pareja(datos, factor, grupo='canal', tolerancia=0.05):
    t = datos.groupby(grupo)[factor].value_counts(normalize=True).unstack()
    brecha = float((t.max() - t.min()).max())
    return round(brecha, 4), brecha < tolerancia

print('segmento:', mezcla_pareja(sub, 'segmento'))
print('ciudad  :', mezcla_pareja(sub, 'ciudad'))
segmento: (0.0247, True)
ciudad  : (0.0129, True)

Las dos brechas son menores de 5 puntos, así que ni el segmento ni la ciudad están repartidos de forma que expliquen la diferencia. Es una buena señal y no es una prueba de causa: solo descarta dos sospechosos de los que sí mediste.

6. La hoja de respuestas, generada

Las seis, calculadas de una vez, para llevar impresas.

ticket = float(d['monto'].dropna().mean())
respuestas = [
    ('certeza', 'entre ' + str(round((dif - 1.96 * se) * 100, 1)) + ' y '
                + str(round((dif + 1.96 * se) * 100, 1)) + ' puntos'),
    ('linea base', str(round(float(b.mean()) * 1000)) + ' de cada 1000 ya compran'),
    ('dinero', str(round((dif - 1.96 * se) * 1000 * ticket)) + ' a '
               + str(round((dif + 1.96 * se) * 1000 * ticket)) + ' soles'),
    ('segmentos', 'Horeca casi no se mueve'),
    ('muestra', 'hacian falta 768 por grupo, tengo ' + str(len(w))),
    ('causa', 'el canal lo eligio el cliente, es correlacion'),
]
for pregunta, respuesta in respuestas:
    print(pregunta.ljust(11), respuesta)
certeza     entre 2.1 y 11.8 puntos
linea base  583 de cada 1000 ya compran
dinero      16733 a 95439 soles
segmentos   Horeca casi no se mueve
muestra     hacian falta 768 por grupo, tengo 725
causa       el canal lo eligio el cliente, es correlacion

Seis líneas. Eso es lo que llevo a una reunión debajo de la lámina, y en el 90% de los casos no necesito ninguna otra cosa.

La costumbre de generarlas con código y no de memoria tiene un motivo concreto: cuando alguien pregunte y tú contestes con el número exacto sin dudar, la conversación cambia de sitio 💛

Practica este capítulo 📓

Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.

¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.

¿Le sirve a alguien que conoces?

Pásale el libro. Es gratis, está entero y no pide registro 🐣

Instagram y TikTok no dejan compartir enlaces desde la web: esos dos copian la URL para que la pegues en tu historia.

¿Tienes alguna duda o consulta?