{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ¿Movemos clientes a WhatsApp?\n",
    "\n",
    "Una pregunta de negocio de principio a fin, con el informe de una página que se puede defender en una reunión.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 22 de **Estadística desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/estadistica-desde-cero/proyecto-final/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Último capítulo con datos 🎓 Vamos a hacer un análisis entero, de la pregunta\n",
    "al informe, usando todo lo del libro.\n",
    "\n",
    "Y voy a hacerlo en el orden correcto, que es el que casi nunca se sigue: la\n",
    "pregunta y el criterio **primero**, los datos después.\n",
    "\n",
    "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 🎓"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 1: la pregunta y el criterio, antes de mirar nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**La pregunta.** Marketplace es el canal que peor cierra y\n",
    "WhatsApp el que mejor. ¿Vale la pena empujar a los clientes de Marketplace hacia\n",
    "WhatsApp?\n",
    "\n",
    "**El criterio, escrito antes.** Me lo tomo en serio si:\n",
    "\n",
    "- 📏 La diferencia es de al menos **5 puntos** de tasa de cierre.\n",
    "Menos de eso no paga el esfuerzo de cambiar un proceso.\n",
    "\n",
    "- 🎯 El intervalo del 95% **no toca el cero**.\n",
    "\n",
    "- 🧩 El efecto **aguanta dentro de cada segmento**. Si solo existe\n",
    "en el total, es Simpson y no me sirve.\n",
    "\n",
    "Escribir esto antes es lo que impide el p-hacking del capítulo 17. Si el\n",
    "resultado sale a 4 puntos, ya no puedo decir \"bueno, 4 también está bien\" 🙅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 2: cargar y limpiar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from scipy import stats\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "\n",
    "def carga_limpia(url):\n",
    "    \"\"\"La misma del capítulo 2.\"\"\"\n",
    "    v = pd.read_csv(url).drop_duplicates()\n",
    "    v['ciudad'] = (v['ciudad'].str.strip().str.lower()\n",
    "                   .str.normalize('NFKD')\n",
    "                   .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "    for col in ['fecha', 'fecha_ultima_compra']:\n",
    "        f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce')\n",
    "        falta = f.isna() & v[col].notna()\n",
    "        f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y',\n",
    "                                  errors='coerce')\n",
    "        v[col] = f\n",
    "    return v\n",
    "\n",
    "\n",
    "v = carga_limpia(URL)\n",
    "\n",
    "print('ventas: %d | clientes: %d | periodo: %s a %s'\n",
    "      % (len(v), v['cliente_id'].nunique(),\n",
    "         v['fecha'].min().date(), v['fecha'].max().date()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí ya anoto una limitación para el informe: **3.000 ventas de 617\n",
    "clientes**, o sea que las filas no son independientes (capítulo 17)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 3: describir antes de concluir"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wa = v[v['canal'] == 'WhatsApp']\n",
    "mp = v[v['canal'] == 'Marketplace']\n",
    "\n",
    "for nombre, g in [('WhatsApp', wa), ('Marketplace', mp)]:\n",
    "    print('%-12s n=%3d | cierre %.4f | monto medio %.2f | mediana %.2f'\n",
    "          % (nombre, len(g), g['compro'].mean(), g['monto'].mean(),\n",
    "             g['monto'].median()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Primer dato importante y que hay que decir en voz alta: **el monto es\n",
    "prácticamente igual en los dos canales** (789 contra 812, y las medianas\n",
    "también).\n",
    "\n",
    "Eso simplifica muchísimo el problema. Solo cambia la tasa de cierre, no el\n",
    "tamaño de la venta, así que todo el efecto se puede traducir a \"cuántas ventas\n",
    "más se cierran\" 💡"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 4: la comparación con su intervalo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dif = wa['compro'].mean() - mp['compro'].mean()\n",
    "ee = np.sqrt(wa['compro'].var(ddof=1) / len(wa)\n",
    "             + mp['compro'].var(ddof=1) / len(mp))\n",
    "\n",
    "print('diferencia: %.4f (%.2f puntos)' % (dif, 100 * dif))\n",
    "print('intervalo del 95%%: [%.4f, %.4f]' % (dif - 1.96 * ee, dif + 1.96 * ee))\n",
    "print('valor p: %.3g'\n",
    "      % stats.ttest_ind(wa['compro'], mp['compro'], equal_var=False).pvalue)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "19,37 puntos, con un intervalo que va de 14,41 a 24,34 y no se acerca al\n",
    "cero ✅\n",
    "\n",
    "Los dos primeros criterios se cumplen con muchísimo margen: pedía 5 puntos y\n",
    "hay 19, y el peor caso del intervalo sigue siendo 14 puntos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 5: el tamaño del efecto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def d_de_cohen(a, b):\n",
    "    na, nb = len(a), len(b)\n",
    "    juntas = np.sqrt(((na - 1) * a.var(ddof=1) + (nb - 1) * b.var(ddof=1))\n",
    "                     / (na + nb - 2))\n",
    "    return (a.mean() - b.mean()) / juntas\n",
    "\n",
    "\n",
    "print('d de Cohen: %.4f' % d_de_cohen(wa['compro'], mp['compro']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,3972, o sea **chico tirando a mediano** según los cortes del\n",
    "capítulo 14.\n",
    "\n",
    "Y esto conviene contarlo con cuidado, porque puede sonar a contradicción: 19\n",
    "puntos de diferencia y un efecto \"chico\". Las dos cosas son ciertas. El efecto\n",
    "es chico *en desviaciones* porque una variable 0/1 tiene una dispersión\n",
    "enorme; y 19 puntos son 19 puntos de negocio 💰\n",
    "\n",
    "Por eso hay que reportar los dos números, y por eso el que va primero en el\n",
    "informe es el de puntos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 6: el paso que casi nadie da"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de creerme nada, el control de Simpson del capítulo 17. ¿Aguanta el\n",
    "efecto dentro de cada segmento?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg in sorted(v['segmento'].unique()):\n",
    "    a = wa.loc[wa['segmento'] == seg, 'compro']\n",
    "    b = mp.loc[mp['segmento'] == seg, 'compro']\n",
    "    d = a.mean() - b.mean()\n",
    "    e = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))\n",
    "    print('%-11s WhatsApp %.4f (n=%3d) | Marketplace %.4f (n=%3d) | '\n",
    "          'dif %+.4f IC [%+.4f, %+.4f]'\n",
    "          % (seg, a.mean(), len(a), b.mean(), len(b), d,\n",
    "             d - 1.96 * e, d + 1.96 * e))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aguanta en los cuatro 🎉\n",
    "\n",
    "Las diferencias van de +15,28 a +27,61 puntos, y **ninguno de los\n",
    "cuatro intervalos toca el cero**. Esto no es Simpson: el efecto está\n",
    "dentro de cada grupo, no lo fabrica la mezcla.\n",
    "\n",
    "Tercer criterio cumplido. Y fíjate en que este paso podría haber tumbado el\n",
    "análisis entero después de cinco pasos de trabajo, que es exactamente por qué se\n",
    "hace 🛡️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 7: traducirlo a dinero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tickets = v.loc[v['compro'] == 1, 'monto_final_facturado']\n",
    "ticket = tickets.median()\n",
    "\n",
    "cierres_extra = dif * len(mp)\n",
    "valor = cierres_extra * ticket\n",
    "\n",
    "print('ticket mediano de una venta cerrada: %.2f' % ticket)\n",
    "print('ventas por Marketplace en el periodo: %d' % len(mp))\n",
    "print('cierres adicionales si tuvieran la tasa de WhatsApp: %.1f' % cierres_extra)\n",
    "print()\n",
    "print('valor central:   S/ %.0f' % valor)\n",
    "print('valor pesimista: S/ %.0f' % ((dif - 1.96 * ee) * len(mp) * ticket))\n",
    "print('valor optimista: S/ %.0f' % ((dif + 1.96 * ee) * len(mp) * ticket))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Entre **62.748 y 105.951 soles** en año y medio 💰\n",
    "\n",
    "Uso la mediana del ticket y no la media a propósito, por lo del capítulo 3: la\n",
    "media está inflada por los mayoristas y daría un número más bonito y menos\n",
    "defendible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El informe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo anterior cabe en esto, que es lo único que hay que llevar a la\n",
    "reunión 📄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Apartado | Contenido |\n",
    "|---|---|\n",
    "| **Pregunta** | ¿Conviene mover clientes de Marketplace a WhatsApp? |\n",
    "| **Hallazgo** | WhatsApp cierra 19,37 puntos más (65,51% contra 46,13%), IC 95%: 14,41 a 24,34 puntos |\n",
    "| **Robustez** | Se mantiene en los cuatro segmentos, de +15,28 a +27,61 puntos, ningún intervalo toca el cero |\n",
    "| **Tamaño** | d = 0,40; el monto por venta no cambia entre canales (789 contra 812) |\n",
    "| **Valor** | Entre S/ 62.748 y S/ 105.951 en 18 meses, con ticket mediano de S/ 570,61 |\n",
    "| **Datos** | 3.000 ventas de 617 clientes, enero 2025 a junio 2026 |\n",
    "| **Límites** | Es observacional, no un experimento. Las filas no son independientes. Junio de 2026 está incompleto. |\n",
    "| **Siguiente paso** | Prueba con 100 clientes de Marketplace durante 3 meses |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que casi me cuesta el informe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mientras armaba esto escribí el nombre del canal con minúscula, que es como\n",
    "lo escribe medio mundo 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    v.groupby('canal')['compro'].mean()['Whatsapp']\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "KeyError: 'Whatsapp'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Menos mal. Ahora mira exactamente el mismo despiste hecho con un filtro:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('con la W mal escrita: ', v.loc[v['canal'] == 'Whatsapp', 'compro'].mean())\n",
    "print('filas que trae:       ', (v['canal'] == 'Whatsapp').sum())\n",
    "print('bien escrito:         ', v.loc[v['canal'] == 'WhatsApp', 'compro'].mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cero filas y un `nan`, sin protestar. Es el mismo error del\n",
    "capítulo 7, y lo repito aquí a propósito porque en un informe de siete pasos hay\n",
    "veinte filtros, y basta con que uno esté mal 😖\n",
    "\n",
    "Mi regla, después de tropezarme muchas veces: **después de cada filtro,\n",
    "imprime cuántas filas quedaron**. Una línea que no cuesta nada y que caza\n",
    "esto en el segundo en que pasa."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La limitación que hay que decir en voz alta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora lo más importante del capítulo, que es lo que el análisis\n",
    "**no** demuestra 🗣️\n",
    "\n",
    "Todo esto es **observacional**. Yo no asigné clientes a canales:\n",
    "ellos eligieron. Así que la explicación alternativa está viva y es muy plausible:\n",
    "*quien escribe por WhatsApp ya venía decidido, y quien navega en Marketplace\n",
    "está comparando precios*.\n",
    "\n",
    "Si eso es lo que pasa, el canal no causa nada: refleja la intención que el\n",
    "cliente ya traía. Y mover a la gente de un sitio a otro no movería el resultado\n",
    "ni un punto.\n",
    "\n",
    "Controlar por segmento, que es lo que hicimos, descarta *ese* confusor.\n",
    "No descarta la intención de compra, que no está en los datos 🤷\n",
    "\n",
    "Por eso el siguiente paso del informe no es \"implementar\", es\n",
    "**probar**. Y con los números del capítulo 14 ya sabemos cuánta\n",
    "gente hace falta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, de los que salen montando el informe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    v.groupby(['ciudad', 'segmento'])['monto'].agg('promedio')\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "AttributeError: 'SeriesGroupBy' object has no attribute 'promedio'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    d_de_cohen(a, b, 0.8)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "TypeError: d_de_cohen() takes 2 positional arguments but 3 were given\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Diseña la prueba que hace falta\n",
    "\n",
    "Calcula cuántos clientes de Marketplace necesitas en una\n",
    "prueba real para detectar la mitad del efecto observado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "base = mp['compro'].mean()\n",
    "\n",
    "for objetivo in [base + 0.19, base + 0.10, base + 0.05]:\n",
    "    h = 2 * (np.arcsin(np.sqrt(objetivo)) - np.arcsin(np.sqrt(base)))\n",
    "    n = 2 * (1.96 + 0.84) ** 2 / h ** 2\n",
    "    print('de %.2f%% a %.2f%% (+%.0f puntos) -> %.0f clientes por grupo'\n",
    "          % (100 * base, 100 * objetivo, 100 * (objetivo - base), n))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "de 46.13% a 65.13% (+19 puntos) -> 106 clientes por grupo\n",
    "de 46.13% a 56.13% (+10 puntos) -> 390 clientes por grupo\n",
    "de 46.13% a 51.13% (+5 puntos) -> 1566 clientes por grupo\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 106 por grupo detectas el efecto completo; con 390, la mitad 📋\n",
    "\n",
    "Ese es el número que va en el informe. Y fíjate en lo útil que es: si en tu\n",
    "Marketplace pasan 200 clientes al mes, la prueba de 390 por grupo tarda cuatro\n",
    "meses. Se puede planificar.\n",
    "\n",
    "Y el escenario de 5 puntos, con 1.566 por grupo, dice algo también: si la\n",
    "mejora real fuera pequeña, **no la vas a poder demostrar** con un\n",
    "piloto razonable. Más vale saberlo antes de prometerlo 🗓️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. ¿Y si el efecto fuera la satisfacción?\n",
    "\n",
    "Comprueba si los clientes de WhatsApp vienen con más\n",
    "satisfacción de partida, que sería una explicación alternativa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sa = wa['satisfaccion'].dropna()\n",
    "sb = mp['satisfaccion'].dropna()\n",
    "\n",
    "print('satisfaccion media: WhatsApp %.4f (n=%d) | Marketplace %.4f (n=%d)'\n",
    "      % (sa.mean(), len(sa), sb.mean(), len(sb)))\n",
    "print('p = %.4f' % stats.ttest_ind(sa, sb, equal_var=False).pvalue)\n",
    "print('d = %.4f' % d_de_cohen(sa, sb))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "satisfaccion media: WhatsApp 3.0429 (n=652) | Marketplace 2.9677 (n=711)\n",
    "p = 0.3251\n",
    "d = 0.0534\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No hay diferencia: p = 0,3251 y d = 0,0534 🙂\n",
    "\n",
    "O sea que **la satisfacción no explica el efecto del canal**. Los\n",
    "clientes de los dos canales llegan igual de contentos.\n",
    "\n",
    "Este es un ejercicio de descarte, y son los que más valen. No añade nada\n",
    "positivo al informe, pero quita del medio una objeción que alguien iba a hacer en\n",
    "la reunión 🛡️\n",
    "\n",
    "Ojo con el matiz: descartar la satisfacción no descarta la intención de\n",
    "compra, que es otra cosa y no está medida. Descartar confusores es infinito;\n",
    "descartas los que puedes y dices cuáles quedan 📝"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. ¿Se mantiene a lo largo del tiempo?\n",
    "\n",
    "Un efecto que solo existe en unos meses es sospechoso.\n",
    "Comprueba trimestre a trimestre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v['trimestre'] = v['fecha'].dt.to_period('Q')\n",
    "\n",
    "for tri, g in v.groupby('trimestre'):\n",
    "    a = g.loc[g['canal'] == 'WhatsApp', 'compro']\n",
    "    b = g.loc[g['canal'] == 'Marketplace', 'compro']\n",
    "    if len(a) < 30 or len(b) < 30:\n",
    "        continue\n",
    "    print('%s  WhatsApp %.4f (n=%3d) | Marketplace %.4f (n=%3d) | dif %+.4f'\n",
    "          % (tri, a.mean(), len(a), b.mean(), len(b), a.mean() - b.mean()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "2025Q1  WhatsApp 0.6607 (n=112) | Marketplace 0.5000 (n=140) | dif +0.1607\n",
    "2025Q2  WhatsApp 0.7109 (n=128) | Marketplace 0.4148 (n=135) | dif +0.2961\n",
    "2025Q3  WhatsApp 0.5702 (n=114) | Marketplace 0.4874 (n=119) | dif +0.0828\n",
    "2025Q4  WhatsApp 0.5950 (n=121) | Marketplace 0.5000 (n=138) | dif +0.0950\n",
    "2026Q1  WhatsApp 0.7661 (n=124) | Marketplace 0.4348 (n=115) | dif +0.3313\n",
    "2026Q2  WhatsApp 0.6167 (n=120) | Marketplace 0.4224 (n=116) | dif +0.1943\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los seis trimestres, todos positivos, de +8,28 a +33,13 puntos 💪\n",
    "\n",
    "Esto es lo que más confianza da de todo el análisis. Seis mediciones\n",
    "independientes en el tiempo y las seis apuntan al mismo lado.\n",
    "\n",
    "Ahora mira cuánto bailan: 16,07, luego 29,61, luego 8,28, 9,50, 33,13 y 19,43.\n",
    "Con unas 120 ventas por celda, cada trimestre trae un margen de error enorme,\n",
    "así que ese baile es exactamente lo que espera el capítulo 10 🎢\n",
    "\n",
    "Y por eso hay que resistirse a contar historias con la serie. \"El efecto se\n",
    "disparó en 2026Q1\" o \"cayó en 2025Q3\" serían justo el p-hacking del capítulo 17:\n",
    "seis mediciones ruidosas siempre tienen una punta y un valle."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La versión honesta contando clientes\n",
    "\n",
    "Repite el análisis contando clientes en vez de ventas, para\n",
    "respetar la independencia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_cliente = (v[v['canal'].isin(['WhatsApp', 'Marketplace'])]\n",
    "               .groupby(['cliente_id', 'canal'])['compro'].mean().reset_index())\n",
    "\n",
    "ca = por_cliente.loc[por_cliente['canal'] == 'WhatsApp', 'compro']\n",
    "cb = por_cliente.loc[por_cliente['canal'] == 'Marketplace', 'compro']\n",
    "dc = ca.mean() - cb.mean()\n",
    "ec = np.sqrt(ca.var(ddof=1) / len(ca) + cb.var(ddof=1) / len(cb))\n",
    "\n",
    "print('por venta:   dif %.4f | IC [%.4f, %.4f] | n = %d y %d'\n",
    "      % (dif, dif - 1.96 * ee, dif + 1.96 * ee, len(wa), len(mp)))\n",
    "print('por cliente: dif %.4f | IC [%.4f, %.4f] | n = %d y %d'\n",
    "      % (dc, dc - 1.96 * ec, dc + 1.96 * ec, len(ca), len(cb)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "por venta:   dif 0.1937 | IC [0.1441, 0.2434] | n = 719 y 763\n",
    "por cliente: dif 0.2040 | IC [0.1486, 0.2593] | n = 428 y 443\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Casi el mismo resultado: 19,37 puntos por venta y 20,40 por cliente 🎯\n",
    "\n",
    "El intervalo se ensancha un poco (de 9,9 a 11,1 puntos de ancho), que es lo\n",
    "esperable al pasar de 719 a 428 casos. Pero la conclusión no se mueve, e incluso\n",
    "sale algo más grande contando clientes.\n",
    "\n",
    "Esta comprobación es la que convierte la limitación \"las filas no son\n",
    "independientes\" en una nota al pie en vez de un problema. Cuesta cinco líneas y\n",
    "te la va a preguntar cualquiera que sepa 🧾"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Lo que pasaría si te equivocaras\n",
    "\n",
    "Calcula el coste de la decisión en el peor caso: que el\n",
    "efecto sea cero y muevas a todos igual."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clientes_marketplace = mp['cliente_id'].nunique()\n",
    "coste_por_cliente = 15  # supuesto: tiempo del equipo comercial, en soles\n",
    "\n",
    "coste = clientes_marketplace * coste_por_cliente\n",
    "\n",
    "print('clientes de Marketplace: %d' % clientes_marketplace)\n",
    "print('coste si el efecto fuera cero: S/ %.0f' % coste)\n",
    "print('valor si el efecto es el pesimista: S/ %.0f'\n",
    "      % ((dif - 1.96 * ee) * len(mp) * ticket))\n",
    "print()\n",
    "print('el efecto tendria que ser mayor que %.4f puntos para pagar el coste'\n",
    "      % (coste / (len(mp) * ticket)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "clientes de Marketplace: 443\n",
    "coste si el efecto fuera cero: S/ 6645\n",
    "valor si el efecto es el pesimista: S/ 62748\n",
    "\n",
    "el efecto tendria que ser mayor que 0.0153 puntos para pagar el coste\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El punto de equilibrio está en **1,53 puntos** de mejora, y el\n",
    "peor caso del intervalo es 14,41 puntos 🎉\n",
    "\n",
    "O sea que la decisión es cómoda: incluso equivocándonos en nueve de cada diez\n",
    "partes del efecto, sigue saliendo a cuenta.\n",
    "\n",
    "Ese coste de 15 soles por cliente me lo inventé yo y hay que decirlo así de\n",
    "claro en el informe. Lo importante no es el número sino la estructura: cuando\n",
    "pones el punto de equilibrio al lado del intervalo, **la decisión se toma\n",
    "sola** ⚖️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El análisis entero, en una función\n",
    "\n",
    "Empaqueta todo para poder repetirlo con cualquier par de\n",
    "canales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compara_canales(df, canal_a, canal_b, control='segmento'):\n",
    "    a = df[df['canal'] == canal_a]['compro']\n",
    "    b = df[df['canal'] == canal_b]['compro']\n",
    "    d = a.mean() - b.mean()\n",
    "    e = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))\n",
    "\n",
    "    aguanta = 0\n",
    "    grupos = df[control].nunique()\n",
    "    for g in df[control].unique():\n",
    "        sub = df[df[control] == g]\n",
    "        x = sub[sub['canal'] == canal_a]['compro']\n",
    "        y = sub[sub['canal'] == canal_b]['compro']\n",
    "        ee_g = np.sqrt(x.var(ddof=1) / len(x) + y.var(ddof=1) / len(y))\n",
    "        if (x.mean() - y.mean()) - 1.96 * ee_g > 0:\n",
    "            aguanta += 1\n",
    "\n",
    "    return ('%-12s vs %-12s dif %+.4f IC [%+.4f, %+.4f] | aguanta en %d/%d %ss'\n",
    "            % (canal_a, canal_b, d, d - 1.96 * e, d + 1.96 * e,\n",
    "               aguanta, grupos, control))\n",
    "\n",
    "\n",
    "for a, b in [('WhatsApp', 'Marketplace'), ('WhatsApp', 'Tienda'),\n",
    "             ('Tienda', 'Web'), ('Web', 'Marketplace')]:\n",
    "    print(compara_canales(v, a, b))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "WhatsApp     vs Marketplace  dif +0.1937 IC [+0.1441, +0.2434] | aguanta en 4/4 segmentos\n",
    "WhatsApp     vs Tienda       dif +0.0352 IC [-0.0143, +0.0848] | aguanta en 2/4 segmentos\n",
    "Tienda       vs Web          dif +0.0390 IC [-0.0103, +0.0883] | aguanta en 1/4 segmentos\n",
    "Web          vs Marketplace  dif +0.1195 IC [+0.0701, +0.1688] | aguanta en 4/4 segmentos\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las cuatro comparaciones en cuatro líneas 🙌\n",
    "\n",
    "Y sale una segunda historia que no habíamos visto: **Web contra\n",
    "Marketplace son casi 12 puntos**, y aguanta en los 4 segmentos. Otro\n",
    "candidato, y bueno.\n",
    "\n",
    "Mientras que WhatsApp contra Tienda y Tienda contra Web son ruido: sus\n",
    "intervalos contienen el cero, aunque algún segmento suelto salga positivo. Eso\n",
    "de \"aguanta en 2 de 4\" es justo lo que produce el azar cuando no hay nada.\n",
    "\n",
    "O sea que el archivo no dice \"hay cuatro canales de calidad distinta\": dice\n",
    "que **Marketplace está solo abajo** y los otros tres son\n",
    "equivalentes. Y esa frase es mucho más accionable que un ranking 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. ¿Qué columna explica el cierre?\n",
    "\n",
    "Antes de cerrar el informe, mira cuánto separa cada columna la tasa de cierre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['ciudad', 'canal', 'segmento', 'categoria']:\n",
    "    t = pd.crosstab(v[col], v['compro'], normalize='index')[1]\n",
    "    print('%-11s cierra de %.1f%% a %.1f%%, o sea %.1f puntos de diferencia'\n",
    "          % (col, t.min() * 100, t.max() * 100, (t.max() - t.min()) * 100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ciudad      cierra de 56.9% a 59.2%, o sea 2.3 puntos de diferencia\n",
    "canal       cierra de 46.1% a 65.5%, o sea 19.4 puntos de diferencia\n",
    "segmento    cierra de 37.5% a 72.4%, o sea 34.9 puntos de diferencia\n",
    "categoria   cierra de 55.9% a 59.1%, o sea 3.1 puntos de diferencia\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es la tabla con la que yo abriría el informe 💼\n",
    "\n",
    "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.\n",
    "\n",
    "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 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. ¿Cambió algo con el tiempo?\n",
    "\n",
    "Parte los datos por la mitad del periodo y comprueba si la tasa de cierre se movió."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ordenado = v.sort_values('fecha')\n",
    "mitad = len(ordenado) // 2\n",
    "antes, despues = ordenado.iloc[:mitad], ordenado.iloc[mitad:]\n",
    "print('primera mitad del tiempo: cierra %.4f (n=%d)'\n",
    "      % (antes['compro'].mean(), len(antes)))\n",
    "print('segunda mitad:            cierra %.4f (n=%d)'\n",
    "      % (despues['compro'].mean(), len(despues)))\n",
    "print('p = %.4f' % stats.chi2_contingency(\n",
    "    pd.crosstab(ordenado['fecha'] >= antes['fecha'].max(),\n",
    "                ordenado['compro']))[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "primera mitad del tiempo: cierra 0.5800 (n=1500)\n",
    "segunda mitad:            cierra 0.5753 (n=1500)\n",
    "p = 0.8399\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nada. Y eso es una buena noticia para el informe 💚\n",
    "\n",
    "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.\n",
    "\n",
    "Es una línea en el informe y es de las que más credibilidad dan, porque enseña que miraste antes de afirmar 🗓️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La recomendación que le baja la conversión a un segmento"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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 😰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "La conclusión es clara: WhatsApp convierte 3,5 puntos más que Tienda. Propones mover a todos los clientes a WhatsApp.\n",
    "\n",
    "```\n",
    "# en total\n",
    "# WhatsApp 0.655   Tienda 0.620\n",
    "\n",
    "# dentro de Horeca\n",
    "# WhatsApp 0.658   Tienda 0.759\n",
    "# n = 187 y 166      p = 0.0492\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "En Horeca, mover clientes a WhatsApp les baja la conversión diez puntos 😰 Y el número general dice lo contrario.\n",
    "\n",
    "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á\".\n",
    "\n",
    "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**.\n",
    "\n",
    "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**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "WhatsApp cierra 19,37 puntos más que Marketplace y aguanta en los cuatro segmentos. ¿Qué recomiendas?\n",
    "\n",
    "a) Probar con un piloto, porque esto es observacional y no un experimento\n",
    "\n",
    "b) Mover a todos los clientes de Marketplace a WhatsApp\n",
    "\n",
    "c) Cerrar Marketplace\n",
    "\n",
    "d) Nada, porque el efecto es chico (d = 0,40)\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* El efecto es sólido, pero nadie asignó los canales: los clientes eligieron. ¿Y si por WhatsApp escribe quien ya venía decidido?\n",
    "\n",
    "*c)* Marketplace trae ventas que se cierran igual. El hallazgo es sobre la tasa, no sobre el volumen.\n",
    "\n",
    "*d)* La d es chica en desviaciones y son 19 puntos de negocio. Las dos cosas son ciertas a la vez.\n",
    "\n",
    "Controlar por segmento descarta ese confusor, no la intención de compra, que no está en los datos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📝 Pregunta y criterio antes de mirar. Escribir \"me lo tomo en serio a partir\n",
    "de 5 puntos\" es lo que impide el p-hacking.\n",
    "\n",
    "- 📊 WhatsApp cierra 19,37 puntos más que Marketplace, IC de 14,41 a 24,34, y\n",
    "el monto por venta no cambia entre canales.\n",
    "\n",
    "- 🧩 El efecto aguanta en los cuatro segmentos, de +15,28 a +27,61. No es\n",
    "Simpson.\n",
    "\n",
    "- 💰 Traducido: entre 62.748 y 105.951 soles en 18 meses, con el ticket\n",
    "mediano y no con la media.\n",
    "\n",
    "- 🗣️ Es observacional. Controlar por segmento descarta ese confusor, no la\n",
    "intención de compra. Por eso el siguiente paso es probar, no implementar.\n",
    "\n",
    "- ⚖️ Con el punto de equilibrio al lado del intervalo (1,53 puntos contra\n",
    "14,41 del peor caso), la decisión se toma sola.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pregunta y el criterio se escriben antes de mirar los datos. Después,\n",
    "ya no vale."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si quieres hacer este mismo recorrido con los datos de tu empresa y\n",
    "acompañada, eso es justo lo que hacemos en el\n",
    "[Full Day IA](https://missyera.com/cursos/full-day-ia/) 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se acabó 🎉 En el capítulo 23 te dejo las referencias: qué leer después, qué\n",
    "herramientas usar y de dónde salió cada cosa de este libro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La estadística es la mitad del trabajo, y esta es la otra"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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.\n",
    "\n",
    "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.\n",
    "\n",
    "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/) 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 22 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/proyecto-final/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
