# ¿Movemos clientes a WhatsApp?

URL canónica: https://missyera.com/guias/estadistica-desde-cero/proyecto-final/
Última actualización: 2026-11-14
Autora: Gera Flores (Miss Yera)

> **Respuesta corta**
>
> Un análisis completo lo hago en siete pasos: escribir la pregunta y el criterio antes de mirar, limpiar, describir, comparar con su intervalo, medir el tamaño del efecto, controlar los confusores y traducir a dinero. Acá WhatsApp cierra 19,37 puntos más que Marketplace, el efecto aguanta dentro de los cuatro segmentos, y vale entre 62.748 y 105.951 soles en año y medio.

Capítulo 22 de 23 del libro [Estadística desde cero](https://missyera.com/guias/estadistica-desde-cero.md).

Último capítulo con datos 🎓 Vamos a hacer un análisis entero, de la pregunta al informe, usando todo lo del libro.

Y voy a hacerlo en el orden correcto, que es el que casi nunca se sigue: la pregunta y el criterio **primero**, los datos después.

Y antes de tocar los datos, contéstate lo mismo que me contesto yo: **¿qué decisión se va a tomar con este análisis?** Si no hay una decisión detrás, lo que te están pidiendo no es un análisis, es un reporte 🎓

## Paso 1: la pregunta y el criterio, antes de mirar nada

**La pregunta.** Marketplace es el canal que peor cierra y WhatsApp el que mejor. ¿Vale la pena empujar a los clientes de Marketplace hacia WhatsApp?

**El criterio, escrito antes.** Me lo tomo en serio si:

- 📏 La diferencia es de al menos **5 puntos** de tasa de cierre. Menos de eso no paga el esfuerzo de cambiar un proceso.
- 🎯 El intervalo del 95% **no toca el cero**.
- 🧩 El efecto **aguanta dentro de cada segmento**. Si solo existe en el total, es Simpson y no me sirve.

Escribir esto antes es lo que impide el p-hacking del capítulo 17. Si el resultado sale a 4 puntos, ya no puedo decir "bueno, 4 también está bien" 🙅

![Los siete pasos de un análisis completo: la pregunta y el criterio antes de mirar, limpiar, describir, comparar con su intervalo, medir el tamaño del efecto, controlar los confusores y traducir a soles.](https://missyera.com/static/diagramas/webp/est-06-encargo.webp)

*El paso rosado es el que casi nunca se hace y el único que no se puede recuperar después. Escribir el criterio antes de mirar es lo que separa un análisis de una búsqueda.*

## Paso 2: cargar y limpiar

```
import pandas as pd
import numpy as np
from scipy import stats

URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'

def carga_limpia(url):
    """La misma del capítulo 2."""
    v = pd.read_csv(url).drop_duplicates()
    v['ciudad'] = (v['ciudad'].str.strip().str.lower()
                   .str.normalize('NFKD')
                   .str.encode('ascii', 'ignore').str.decode('utf-8'))
    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))
    for col in ['fecha', 'fecha_ultima_compra']:
        f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce')
        falta = f.isna() & v[col].notna()
        f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y',
                                  errors='coerce')
        v[col] = f
    return v

v = carga_limpia(URL)

print('ventas: %d | clientes: %d | periodo: %s a %s'
      % (len(v), v['cliente_id'].nunique(),
         v['fecha'].min().date(), v['fecha'].max().date()))
```

```text
ventas: 3000 | clientes: 617 | periodo: 2025-01-01 a 2026-06-24
```

Y aquí ya anoto una limitación para el informe: **3.000 ventas de 617 clientes**, o sea que las filas no son independientes (capítulo 17).

## Paso 3: describir antes de concluir

```
wa = v[v['canal'] == 'WhatsApp']
mp = v[v['canal'] == 'Marketplace']

for nombre, g in [('WhatsApp', wa), ('Marketplace', mp)]:
    print('%-12s n=%3d | cierre %.4f | monto medio %.2f | mediana %.2f'
          % (nombre, len(g), g['compro'].mean(), g['monto'].mean(),
             g['monto'].median()))
```

```text
WhatsApp     n=719 | cierre 0.6551 | monto medio 789.14 | mediana 521.76
Marketplace  n=763 | cierre 0.4613 | monto medio 812.27 | mediana 551.29
```

Primer dato importante y que hay que decir en voz alta: **el monto es prácticamente igual en los dos canales** (789 contra 812, y las medianas también).

Eso simplifica muchísimo el problema. Solo cambia la tasa de cierre, no el tamaño de la venta, así que todo el efecto se puede traducir a "cuántas ventas más se cierran" 💡

## Paso 4: la comparación con su intervalo

```
dif = wa['compro'].mean() - mp['compro'].mean()
ee = np.sqrt(wa['compro'].var(ddof=1) / len(wa)
             + mp['compro'].var(ddof=1) / len(mp))

print('diferencia: %.4f (%.2f puntos)' % (dif, 100 * dif))
print('intervalo del 95%%: [%.4f, %.4f]' % (dif - 1.96 * ee, dif + 1.96 * ee))
print('valor p: %.3g'
      % stats.ttest_ind(wa['compro'], mp['compro'], equal_var=False).pvalue)
```

```text
diferencia: 0.1937 (19.37 puntos)
intervalo del 95%: [0.1441, 0.2434]
valor p: 3.51e-14
```

19,37 puntos, con un intervalo que va de 14,41 a 24,34 y no se acerca al cero ✅

Los dos primeros criterios se cumplen con muchísimo margen: pedía 5 puntos y hay 19, y el peor caso del intervalo sigue siendo 14 puntos.

## Paso 5: el tamaño del efecto

```
def d_de_cohen(a, b):
    na, nb = len(a), len(b)
    juntas = np.sqrt(((na - 1) * a.var(ddof=1) + (nb - 1) * b.var(ddof=1))
                     / (na + nb - 2))
    return (a.mean() - b.mean()) / juntas

print('d de Cohen: %.4f' % d_de_cohen(wa['compro'], mp['compro']))
```

```text
d de Cohen: 0.3972
```

0,3972, o sea **chico tirando a mediano** según los cortes del capítulo 14.

Y esto conviene contarlo con cuidado, porque puede sonar a contradicción: 19 puntos de diferencia y un efecto "chico". Las dos cosas son ciertas. El efecto es chico *en desviaciones* porque una variable 0/1 tiene una dispersión enorme; y 19 puntos son 19 puntos de negocio 💰

Por eso hay que reportar los dos números, y por eso el que va primero en el informe es el de puntos.

## Paso 6: el paso que casi nadie da

Antes de creerme nada, el control de Simpson del capítulo 17. ¿Aguanta el efecto dentro de cada segmento?

```
for seg in sorted(v['segmento'].unique()):
    a = wa.loc[wa['segmento'] == seg, 'compro']
    b = mp.loc[mp['segmento'] == seg, 'compro']
    d = a.mean() - b.mean()
    e = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))
    print('%-11s WhatsApp %.4f (n=%3d) | Marketplace %.4f (n=%3d) | '
          'dif %+.4f IC [%+.4f, %+.4f]'
          % (seg, a.mean(), len(a), b.mean(), len(b), d,
             d - 1.96 * e, d + 1.96 * e))
```

```text
Bodega      WhatsApp 0.4545 (n=176) | Marketplace 0.2762 (n=181) | dif +0.1783 IC [+0.0798, +0.2768]
Horeca      WhatsApp 0.6578 (n=187) | Marketplace 0.5050 (n=202) | dif +0.1528 IC [+0.0557, +0.2499]
Mayorista   WhatsApp 0.8187 (n=171) | Marketplace 0.6368 (n=190) | dif +0.1819 IC [+0.0921, +0.2716]
Minimarket  WhatsApp 0.6919 (n=185) | Marketplace 0.4158 (n=190) | dif +0.2761 IC [+0.1792, +0.3730]
```

Aguanta en los cuatro 🎉

Las diferencias van de +15,28 a +27,61 puntos, y **ninguno de los cuatro intervalos toca el cero**. Esto no es Simpson: el efecto está dentro de cada grupo, no lo fabrica la mezcla.

Tercer criterio cumplido. Y fíjate en que este paso podría haber tumbado el análisis entero después de cinco pasos de trabajo, que es exactamente por qué se hace 🛡️

## Paso 7: traducirlo a dinero

```
tickets = v.loc[v['compro'] == 1, 'monto_final_facturado']
ticket = tickets.median()

cierres_extra = dif * len(mp)
valor = cierres_extra * ticket

print('ticket mediano de una venta cerrada: %.2f' % ticket)
print('ventas por Marketplace en el periodo: %d' % len(mp))
print('cierres adicionales si tuvieran la tasa de WhatsApp: %.1f' % cierres_extra)
print()
print('valor central:   S/ %.0f' % valor)
print('valor pesimista: S/ %.0f' % ((dif - 1.96 * ee) * len(mp) * ticket))
print('valor optimista: S/ %.0f' % ((dif + 1.96 * ee) * len(mp) * ticket))
```

```text
ticket mediano de una venta cerrada: 570.61
ventas por Marketplace en el periodo: 763
cierres adicionales si tuvieran la tasa de WhatsApp: 147.8

valor central:   S/ 84349
valor pesimista: S/ 62748
valor optimista: S/ 105951
```

Entre **62.748 y 105.951 soles** en año y medio 💰

Uso la mediana del ticket y no la media a propósito, por lo del capítulo 3: la media está inflada por los mayoristas y daría un número más bonito y menos defendible.

## El informe

Todo lo anterior cabe en esto, que es lo único que hay que llevar a la reunión 📄

| Apartado | Contenido |
|---|---|
| **Pregunta** | ¿Conviene mover clientes de Marketplace a WhatsApp? |
| **Hallazgo** | WhatsApp cierra 19,37 puntos más (65,51% contra 46,13%), IC 95%: 14,41 a 24,34 puntos |
| **Robustez** | Se mantiene en los cuatro segmentos, de +15,28 a +27,61 puntos, ningún intervalo toca el cero |
| **Tamaño** | d = 0,40; el monto por venta no cambia entre canales (789 contra 812) |
| **Valor** | Entre S/ 62.748 y S/ 105.951 en 18 meses, con ticket mediano de S/ 570,61 |
| **Datos** | 3.000 ventas de 617 clientes, enero 2025 a junio 2026 |
| **Límites** | Es observacional, no un experimento. Las filas no son independientes. Junio de 2026 está incompleto. |
| **Siguiente paso** | Prueba con 100 clientes de Marketplace durante 3 meses |

## El error que casi me cuesta el informe

Mientras armaba esto escribí el nombre del canal con minúscula, que es como lo escribe medio mundo 🙃

```
v.groupby('canal')['compro'].mean()['Whatsapp']
```

```
KeyError: 'Whatsapp'
```

Menos mal. Ahora mira exactamente el mismo despiste hecho con un filtro:

```
print('con la W mal escrita: ', v.loc[v['canal'] == 'Whatsapp', 'compro'].mean())
print('filas que trae:       ', (v['canal'] == 'Whatsapp').sum())
print('bien escrito:         ', v.loc[v['canal'] == 'WhatsApp', 'compro'].mean())
```

```text
con la W mal escrita:  nan
filas que trae:        0
bien escrito:          0.655076495132128
```

Cero filas y un `nan`, sin protestar. Es el mismo error del capítulo 7, y lo repito aquí a propósito porque en un informe de siete pasos hay veinte filtros, y basta con que uno esté mal 😖

Mi regla, después de tropezarme muchas veces: **después de cada filtro, imprime cuántas filas quedaron**. Una línea que no cuesta nada y que caza esto en el segundo en que pasa.

## La limitación que hay que decir en voz alta

Y ahora lo más importante del capítulo, que es lo que el análisis **no** demuestra 🗣️

Todo esto es **observacional**. Yo no asigné clientes a canales: ellos eligieron. Así que la explicación alternativa está viva y es muy plausible: *quien escribe por WhatsApp ya venía decidido, y quien navega en Marketplace está comparando precios*.

Si eso es lo que pasa, el canal no causa nada: refleja la intención que el cliente ya traía. Y mover a la gente de un sitio a otro no movería el resultado ni un punto.

Controlar por segmento, que es lo que hicimos, descarta *ese* confusor. No descarta la intención de compra, que no está en los datos 🤷

Por eso el siguiente paso del informe no es "implementar", es **probar**. Y con los números del capítulo 14 ya sabemos cuánta gente hace falta.

## Y dos más, de los que salen montando el informe

```
v.groupby(['ciudad', 'segmento'])['monto'].agg('promedio')
```

```
AttributeError: 'SeriesGroupBy' object has no attribute 'promedio'
```

Se llama `mean`. Lo pongo aquí, al final del libro, porque es el error que más veces vas a cometer y nunca deja de dar rabia: uno escribe en español sin darse cuenta 🙃

```
d_de_cohen(a, b, 0.8)
```

```
TypeError: d_de_cohen() takes 2 positional arguments but 3 were given
```

El 0,8 es el umbral de "efecto grande" de la tabla de Cohen, y no es un parámetro de la función: es algo que decides tú al leer el número. Este error es bonito porque nace de una confusión de fondo, **la de mezclar lo que se calcula con lo que se interpreta**, y esa confusión es medio libro.

## Practica 💪

**1. Diseña la prueba que hace falta**

Calcula cuántos clientes de Marketplace necesitas en una prueba real para detectar la mitad del efecto observado.

```
base = mp['compro'].mean()

for objetivo in [base + 0.19, base + 0.10, base + 0.05]:
    h = 2 * (np.arcsin(np.sqrt(objetivo)) - np.arcsin(np.sqrt(base)))
    n = 2 * (1.96 + 0.84) ** 2 / h ** 2
    print('de %.2f%% a %.2f%% (+%.0f puntos) -> %.0f clientes por grupo'
          % (100 * base, 100 * objetivo, 100 * (objetivo - base), n))
```

```text
de 46.13% a 65.13% (+19 puntos) -> 106 clientes por grupo
de 46.13% a 56.13% (+10 puntos) -> 390 clientes por grupo
de 46.13% a 51.13% (+5 puntos) -> 1566 clientes por grupo
```

Con 106 por grupo detectas el efecto completo; con 390, la mitad 📋

Ese es el número que va en el informe. Y fíjate en lo útil que es: si en tu Marketplace pasan 200 clientes al mes, la prueba de 390 por grupo tarda cuatro meses. Se puede planificar.

Y el escenario de 5 puntos, con 1.566 por grupo, dice algo también: si la mejora real fuera pequeña, **no la vas a poder demostrar** con un piloto razonable. Más vale saberlo antes de prometerlo 🗓️

### 2. ¿Y si el efecto fuera la satisfacción?

Comprueba si los clientes de WhatsApp vienen con más satisfacción de partida, que sería una explicación alternativa.

```
sa = wa['satisfaccion'].dropna()
sb = mp['satisfaccion'].dropna()

print('satisfaccion media: WhatsApp %.4f (n=%d) | Marketplace %.4f (n=%d)'
      % (sa.mean(), len(sa), sb.mean(), len(sb)))
print('p = %.4f' % stats.ttest_ind(sa, sb, equal_var=False).pvalue)
print('d = %.4f' % d_de_cohen(sa, sb))
```

```text
satisfaccion media: WhatsApp 3.0429 (n=652) | Marketplace 2.9677 (n=711)
p = 0.3251
d = 0.0534
```

No hay diferencia: p = 0,3251 y d = 0,0534 🙂

O sea que **la satisfacción no explica el efecto del canal**. Los clientes de los dos canales llegan igual de contentos.

Este es un ejercicio de descarte, y son los que más valen. No añade nada positivo al informe, pero quita del medio una objeción que alguien iba a hacer en la reunión 🛡️

Ojo con el matiz: descartar la satisfacción no descarta la intención de compra, que es otra cosa y no está medida. Descartar confusores es infinito; descartas los que puedes y dices cuáles quedan 📝

### 3. ¿Se mantiene a lo largo del tiempo?

Un efecto que solo existe en unos meses es sospechoso. Comprueba trimestre a trimestre.

```
v['trimestre'] = v['fecha'].dt.to_period('Q')

for tri, g in v.groupby('trimestre'):
    a = g.loc[g['canal'] == 'WhatsApp', 'compro']
    b = g.loc[g['canal'] == 'Marketplace', 'compro']
    if len(a) < 30 or len(b) < 30:
        continue
    print('%s  WhatsApp %.4f (n=%3d) | Marketplace %.4f (n=%3d) | dif %+.4f'
          % (tri, a.mean(), len(a), b.mean(), len(b), a.mean() - b.mean()))
```

```text
2025Q1  WhatsApp 0.6607 (n=112) | Marketplace 0.5000 (n=140) | dif +0.1607
2025Q2  WhatsApp 0.7109 (n=128) | Marketplace 0.4148 (n=135) | dif +0.2961
2025Q3  WhatsApp 0.5702 (n=114) | Marketplace 0.4874 (n=119) | dif +0.0828
2025Q4  WhatsApp 0.5950 (n=121) | Marketplace 0.5000 (n=138) | dif +0.0950
2026Q1  WhatsApp 0.7661 (n=124) | Marketplace 0.4348 (n=115) | dif +0.3313
2026Q2  WhatsApp 0.6167 (n=120) | Marketplace 0.4224 (n=116) | dif +0.1943
```

Los seis trimestres, todos positivos, de +8,28 a +33,13 puntos 💪

Esto es lo que más confianza da de todo el análisis. Seis mediciones independientes en el tiempo y las seis apuntan al mismo lado.

Ahora mira cuánto bailan: 16,07, luego 29,61, luego 8,28, 9,50, 33,13 y 19,43. Con unas 120 ventas por celda, cada trimestre trae un margen de error enorme, así que ese baile es exactamente lo que espera el capítulo 10 🎢

Y por eso hay que resistirse a contar historias con la serie. "El efecto se disparó en 2026Q1" o "cayó en 2025Q3" serían justo el p-hacking del capítulo 17: seis mediciones ruidosas siempre tienen una punta y un valle.

**4. La versión honesta contando clientes**

Repite el análisis contando clientes en vez de ventas, para respetar la independencia.

```
por_cliente = (v[v['canal'].isin(['WhatsApp', 'Marketplace'])]
               .groupby(['cliente_id', 'canal'])['compro'].mean().reset_index())

ca = por_cliente.loc[por_cliente['canal'] == 'WhatsApp', 'compro']
cb = por_cliente.loc[por_cliente['canal'] == 'Marketplace', 'compro']
dc = ca.mean() - cb.mean()
ec = np.sqrt(ca.var(ddof=1) / len(ca) + cb.var(ddof=1) / len(cb))

print('por venta:   dif %.4f | IC [%.4f, %.4f] | n = %d y %d'
      % (dif, dif - 1.96 * ee, dif + 1.96 * ee, len(wa), len(mp)))
print('por cliente: dif %.4f | IC [%.4f, %.4f] | n = %d y %d'
      % (dc, dc - 1.96 * ec, dc + 1.96 * ec, len(ca), len(cb)))
```

```text
por venta:   dif 0.1937 | IC [0.1441, 0.2434] | n = 719 y 763
por cliente: dif 0.2040 | IC [0.1486, 0.2593] | n = 428 y 443
```

Casi el mismo resultado: 19,37 puntos por venta y 20,40 por cliente 🎯

El intervalo se ensancha un poco (de 9,9 a 11,1 puntos de ancho), que es lo esperable al pasar de 719 a 428 casos. Pero la conclusión no se mueve, e incluso sale algo más grande contando clientes.

Esta comprobación es la que convierte la limitación "las filas no son independientes" en una nota al pie en vez de un problema. Cuesta cinco líneas y te la va a preguntar cualquiera que sepa 🧾

**5. Lo que pasaría si te equivocaras**

Calcula el coste de la decisión en el peor caso: que el efecto sea cero y muevas a todos igual.

```
clientes_marketplace = mp['cliente_id'].nunique()
coste_por_cliente = 15  # supuesto: tiempo del equipo comercial, en soles

coste = clientes_marketplace * coste_por_cliente

print('clientes de Marketplace: %d' % clientes_marketplace)
print('coste si el efecto fuera cero: S/ %.0f' % coste)
print('valor si el efecto es el pesimista: S/ %.0f'
      % ((dif - 1.96 * ee) * len(mp) * ticket))
print()
print('el efecto tendria que ser mayor que %.4f puntos para pagar el coste'
      % (coste / (len(mp) * ticket)))
```

```text
clientes de Marketplace: 443
coste si el efecto fuera cero: S/ 6645
valor si el efecto es el pesimista: S/ 62748

el efecto tendria que ser mayor que 0.0153 puntos para pagar el coste
```

El punto de equilibrio está en **1,53 puntos** de mejora, y el peor caso del intervalo es 14,41 puntos 🎉

O sea que la decisión es cómoda: incluso equivocándonos en nueve de cada diez partes del efecto, sigue saliendo a cuenta.

Ese coste de 15 soles por cliente me lo inventé yo y hay que decirlo así de claro en el informe. Lo importante no es el número sino la estructura: cuando pones el punto de equilibrio al lado del intervalo, **la decisión se toma sola** ⚖️

**6. El análisis entero, en una función**

Empaqueta todo para poder repetirlo con cualquier par de canales.

```
def compara_canales(df, canal_a, canal_b, control='segmento'):
    a = df[df['canal'] == canal_a]['compro']
    b = df[df['canal'] == canal_b]['compro']
    d = a.mean() - b.mean()
    e = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))

    aguanta = 0
    grupos = df[control].nunique()
    for g in df[control].unique():
        sub = df[df[control] == g]
        x = sub[sub['canal'] == canal_a]['compro']
        y = sub[sub['canal'] == canal_b]['compro']
        ee_g = np.sqrt(x.var(ddof=1) / len(x) + y.var(ddof=1) / len(y))
        if (x.mean() - y.mean()) - 1.96 * ee_g > 0:
            aguanta += 1

    return ('%-12s vs %-12s dif %+.4f IC [%+.4f, %+.4f] | aguanta en %d/%d %ss'
            % (canal_a, canal_b, d, d - 1.96 * e, d + 1.96 * e,
               aguanta, grupos, control))

for a, b in [('WhatsApp', 'Marketplace'), ('WhatsApp', 'Tienda'),
             ('Tienda', 'Web'), ('Web', 'Marketplace')]:
    print(compara_canales(v, a, b))
```

```text
WhatsApp     vs Marketplace  dif +0.1937 IC [+0.1441, +0.2434] | aguanta en 4/4 segmentos
WhatsApp     vs Tienda       dif +0.0352 IC [-0.0143, +0.0848] | aguanta en 2/4 segmentos
Tienda       vs Web          dif +0.0390 IC [-0.0103, +0.0883] | aguanta en 1/4 segmentos
Web          vs Marketplace  dif +0.1195 IC [+0.0701, +0.1688] | aguanta en 4/4 segmentos
```

Las cuatro comparaciones en cuatro líneas 🙌

Y sale una segunda historia que no habíamos visto: **Web contra Marketplace son casi 12 puntos**, y aguanta en los 4 segmentos. Otro candidato, y bueno.

Mientras que WhatsApp contra Tienda y Tienda contra Web son ruido: sus intervalos contienen el cero, aunque algún segmento suelto salga positivo. Eso de "aguanta en 2 de 4" es justo lo que produce el azar cuando no hay nada.

O sea que el archivo no dice "hay cuatro canales de calidad distinta": dice que **Marketplace está solo abajo** y los otros tres son equivalentes. Y esa frase es mucho más accionable que un ranking 📊

### 7. ¿Qué columna explica el cierre?

Antes de cerrar el informe, mira cuánto separa cada columna la tasa de cierre.

```
for col in ['ciudad', 'canal', 'segmento', 'categoria']:
    t = pd.crosstab(v[col], v['compro'], normalize='index')[1]
    print('%-11s cierra de %.1f%% a %.1f%%, o sea %.1f puntos de diferencia'
          % (col, t.min() * 100, t.max() * 100, (t.max() - t.min()) * 100))
```

```text
ciudad      cierra de 56.9% a 59.2%, o sea 2.3 puntos de diferencia
canal       cierra de 46.1% a 65.5%, o sea 19.4 puntos de diferencia
segmento    cierra de 37.5% a 72.4%, o sea 34.9 puntos de diferencia
categoria   cierra de 55.9% a 59.1%, o sea 3.1 puntos de diferencia
```

Esta es la tabla con la que yo abriría el informe 💼

Dos columnas mueven la aguja y dos no. Y el orden importa: el segmento vale 34,9 puntos y el canal 19,4, así que si hay que elegir una sola palanca, ya sabes cuál.

Fíjate en que esto no lleva ninguna prueba estadística. Es solo contar bien, y contesta el 80% de lo que te van a preguntar. Las pruebas vienen después, para saber si te lo puedes creer 🎯

### 8. ¿Cambió algo con el tiempo?

Parte los datos por la mitad del periodo y comprueba si la tasa de cierre se movió.

```
ordenado = v.sort_values('fecha')
mitad = len(ordenado) // 2
antes, despues = ordenado.iloc[:mitad], ordenado.iloc[mitad:]
print('primera mitad del tiempo: cierra %.4f (n=%d)'
      % (antes['compro'].mean(), len(antes)))
print('segunda mitad:            cierra %.4f (n=%d)'
      % (despues['compro'].mean(), len(despues)))
print('p = %.4f' % stats.chi2_contingency(
    pd.crosstab(ordenado['fecha'] >= antes['fecha'].max(),
                ordenado['compro']))[1])
```

```text
primera mitad del tiempo: cierra 0.5800 (n=1500)
segunda mitad:            cierra 0.5753 (n=1500)
p = 0.8399
```

Nada. Y eso es una buena noticia para el informe 💚

Esta comprobación se llama estabilidad y casi nadie la hace: **si la tasa hubiera cambiado a mitad del periodo, todo lo que concluiste sobre el conjunto estaría mezclando dos mundos**. Como no cambió, puedes hablar de "la tasa de cierre" en singular.

Es una línea en el informe y es de las que más credibilidad dan, porque enseña que miraste antes de afirmar 🗓️

## La recomendación que le baja la conversión a un segmento

Y para terminar el libro, la trampa que está en el salto del número a la decisión, que es donde de verdad se pierde el dinero 😰

La trampa

La conclusión es clara: WhatsApp convierte 3,5 puntos más que Tienda. Propones mover a todos los clientes a WhatsApp.

```
# en total
# WhatsApp 0.655   Tienda 0.620

# dentro de Horeca
# WhatsApp 0.658   Tienda 0.759
# n = 187 y 166      p = 0.0492
```

**Qué está mal**

En Horeca, mover clientes a WhatsApp les baja la conversión diez puntos 😰 Y el número general dice lo contrario.

WhatsApp gana en Bodega, en Mayorista y en Minimarket. Pierde en Horeca, y pierde de forma significativa. Así que la recomendación correcta no es "muevan a todos": es "muevan a estos tres y a Horeca déjenlo donde está".

Ojo con lo que no dice esta trampa. Contra Marketplace, que es la comparación del capítulo, WhatsApp gana en los cuatro segmentos y ahí la recomendación general sí aguanta. La diferencia entre las dos comparaciones es exactamente el trabajo: **comprobar, no suponer**.

Y el error de fondo no está en el cálculo, está en el salto del promedio a la decisión. Un promedio describe a un grupo. Una decisión se aplica a personas concretas, y ninguna de ellas es el promedio. Si la conclusión aguanta en todos los cortes, tienes una recomendación. Si no aguanta, tienes algo mejor: **una recomendación con nombre y apellido**.

Comprueba que lo tienes

WhatsApp cierra 19,37 puntos más que Marketplace y aguanta en los cuatro segmentos. ¿Qué recomiendas?

- Probar con un piloto, porque esto es observacional y no un experimento
- Mover a todos los clientes de Marketplace a WhatsApp
- Cerrar Marketplace
- Nada, porque el efecto es chico (d = 0,40)

## Lo que te llevas

- 📝 Pregunta y criterio antes de mirar. Escribir "me lo tomo en serio a partir de 5 puntos" es lo que impide el p-hacking.
- 📊 WhatsApp cierra 19,37 puntos más que Marketplace, IC de 14,41 a 24,34, y el monto por venta no cambia entre canales.
- 🧩 El efecto aguanta en los cuatro segmentos, de +15,28 a +27,61. No es Simpson.
- 💰 Traducido: entre 62.748 y 105.951 soles en 18 meses, con el ticket mediano y no con la media.
- 🗣️ Es observacional. Controlar por segmento descarta ese confusor, no la intención de compra. Por eso el siguiente paso es probar, no implementar.
- ⚖️ Con el punto de equilibrio al lado del intervalo (1,53 puntos contra 14,41 del peor caso), la decisión se toma sola.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

La pregunta y el criterio se escriben antes de mirar los datos. Después, ya no vale.

Y si quieres hacer este mismo recorrido con los datos de tu empresa y acompañada, eso es justo lo que hacemos en el [Full Day IA](https://missyera.com/cursos/full-day-ia/) 💜

## Qué viene ahora

Se acabó 🎉 En el capítulo 23 te dejo las referencias: qué leer después, qué herramientas usar y de dónde salió cada cosa de este libro.

## La estadística es la mitad del trabajo, y esta es la otra

Si terminaste esto, ya sabes lo que separa un número que significa algo de un número que suena bien:

- De dónde salió la muestra
- Si la diferencia es real o es ruido
- Y qué prueba tocaba usar

Ese criterio es exactamente el que falta en la mayoría de los sistemas de IA que veo en empresas: nadie sabe decir si el sistema funciona o solo lo parece. Es lo que se construye en [el curso de ingeniería de IA en producción](https://missyera.com/cursos/ingenieria-de-ia/), donde los evals son el centro y no un adorno del final.

Y si lo tuyo no es construir sistemas sino analizar bien, esto que acabas de terminar ya te habilita para eso, que es un oficio entero y con demanda. El capítulo de referencias te deja por dónde seguir sin gastar un sol, y lo que le falta a la estadística para que sea trabajo pagado son las herramientas: sacar el dato tú misma y presentarlo. Eso está en [las clases grabadas de Excel y Power BI](https://missyera.com/cursos/datos/) 💪

## Seguir leyendo

- Anterior: [Las seis preguntas que te van a hacer](https://missyera.com/guias/estadistica-desde-cero/las-preguntas-de-la-reunion.md)
- Siguiente: [Referencias y dónde seguir](https://missyera.com/guias/estadistica-desde-cero/referencias.md)

## Contacto

- Diagnóstico de 30 minutos con Miss Yera: https://calendly.com/soymissyera/reunion-con-yera
- Correo: hola@missyera.com
- Esta página en HTML: https://missyera.com/guias/estadistica-desde-cero/proyecto-final/
- Índice de todas las páginas en texto: https://missyera.com/llms-index.txt
