{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Los errores que invalidan un análisis entero\n",
    "\n",
    "Simpson fabricado con ventas de verdad, regresión a la media, p-hacking y las filas que no son independientes.\n",
    "\n",
    "Cuaderno de práctica del capítulo 17 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/errores-que-invalidan/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"RU4gVE9UQUwKICBNYXJrZXRwbGFjZSAwLjMwMTAgKG49MTk2KSB8IFdoYXRzQXBwIDAuNzk1NyAobj0xODYpCgpCb2RlZ2EgICAgIE1hcmtldHBsYWNlIDAuMjc2MiB8IFdoYXRzQXBwIDAuNTMzMwpNYXlvcmlzdGEgIE1hcmtldHBsYWNlIDAuNjAwMCB8IFdoYXRzQXBwIDAuODE4Nw==\",\n",
    "    2: \"bWVkaWEgZ2VuZXJhbDogOTE2LjU0CmVsIDEwJSBtZWpvcjogZXN0ZSBtZXMgMjgxMy44MiAtPiBlbCBzaWd1aWVudGUgOTA1LjUxCmNhaWRhIGFwYXJlbnRlOiAtNjcuOCU=\",\n",
    "    3: \"cHJvYmFuZG8gMjAgc3ViZ3J1cG9zIHNpbiBuaW5ndW4gZWZlY3RvIHJlYWw6CiAgZWwgbWVqb3IgcCBlcyBtZW5vciBxdWUgMC4wNSBlbiBlbCA2NC4wJSBkZSBsb3MgaW50ZW50b3MKICBtZWRpYW5hIGRlbCBtZWpvciBwOiAwLjAzMzE=\",\n",
    "    4: \"dG90YWwgZGVsIGFyY2hpdm86ICAgICAyNDExMjgzLjgzCnN1bWFuZG8gcG9yIGdydXBvczogICAgMjIxMzcyOS4yMwpzZSBwZXJkaWVyb246ICAgICAgICAgIDE5NzU1NC42MCBzb2xlcyAoOC4yJSk=\",\n",
    "    5: \"Y2xpZW50ZXMgZW4gbGEgdGFibGEgbnVldmE6IDEwIChwZXJvIHNvbG8gNSBkaXN0aW50b3MpCnZlbnRhcyBkZSBlc29zIGNsaWVudGVzOiAgICAzNApmaWxhcyBkZXNwdWVzIGRlbCBtZXJnZTogICAgNjgKCm1lZGlhIGRlbCBtb250byBhbnRlczogIDYyNy44MAptZWRpYSBkZWwgbW9udG8gZGVzcHVlczogNjI3Ljgw\",\n",
    "    6: \"LSAzMDAwIGZpbGFzIHBhcmEgNjE3IGNsaWVudGVfaWQgZGlzdGludG9zOiBsYXMgZmlsYXMgbm8gc29uIGluZGVwZW5kaWVudGVzCi0gZGVzY3VlbnRvIHRpZW5lIDU5NSBudWxvczogdW4gZ3JvdXBieSBwb3IgZXNhIGNvbHVtbmEgcGVyZGVyYSBlc2FzIGZpbGFzCi0gZmVjaGFfdWx0aW1hX2NvbXByYSB0aWVuZSA1NDQgbnVsb3M6IHVuIGdyb3VwYnkgcG9yIGVzYSBjb2x1bW5hIHBlcmRlcmEgZXNhcyBmaWxhcwotIHNhdGlzZmFjY2lvbiB0aWVuZSAyMzEgbnVsb3M6IHVuIGdyb3VwYnkgcG9yIGVzYSBjb2x1bW5hIHBlcmRlcmEgZXNhcyBmaWxhcw==\",\n",
    "    7: \"Y29sdW1uYXMgZGUgcHVybyBydWlkbyBwcm9iYWRhczogNDAKc2FsZW4gc2lnbmlmaWNhdGl2YXMgYWwgNSU6IDIKbG8gZXNwZXJhZG8gcG9yIGF6YXIgZXMgMg==\",\n",
    "    8: \"cGVkaWRvcyBlbiB0b3RhbDogMzAwMApjbGllbnRlcyBkaXN0aW50b3M6IDYxNwpwZWRpZG9zIHBvciBjbGllbnRlOiA0Ljg2Cgpwcm9tZWRpbyBjb24gdG9kYXMgbGFzIGZpbGFzOiAgICAgIDgwMy43Ngpwcm9tZWRpbyBjb24gdW4gcGVkaWRvIHBvciBjbGllbnRlOiA4MTMuNDM=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todos los errores de este capítulo tienen algo en común: **no dan\n",
    "error** 😶\n",
    "\n",
    "Los cálculos salen, los números se ven bien, el informe se entrega. Y la\n",
    "conclusión es la contraria de la verdad."
   ]
  },
  {
   "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",
    "print('filas:', len(v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una pregunta incómoda: **¿alguna vez seguiste partiendo los datos hasta que salió algo?** Yo sí, y sin mala intención. Este capítulo es sobre todas las formas de engañarte sola 🎩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. La paradoja de Simpson"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Empiezo por la buena noticia: **este archivo no la tiene**. Lo\n",
    "comprobé antes de escribir el capítulo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tasas = v.pivot_table(index='canal', columns='segmento',\n",
    "                      values='compro', aggfunc='mean')\n",
    "print(tasas.round(4))\n",
    "print()\n",
    "print('composicion de cada canal:')\n",
    "print(pd.crosstab(v['canal'], v['segmento'], normalize='index').round(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la segunda tabla: los cuatro canales tienen prácticamente la misma\n",
    "mezcla de segmentos, entre el 21% y el 29% de cada uno. Cuando la composición\n",
    "está equilibrada, Simpson no puede aparecer 🛡️\n",
    "\n",
    "Así que vamos a fabricarlo. Y lo voy a hacer con **ventas de verdad de\n",
    "este archivo**: no invento ni un dato, solo elijo cuáles miro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mp = v[v['canal'] == 'Marketplace']\n",
    "wa = v[v['canal'] == 'WhatsApp']\n",
    "\n",
    "# Marketplace casi solo mayoristas, WhatsApp casi solo bodegas.\n",
    "marketplace = pd.concat([mp[mp['segmento'] == 'Mayorista'],\n",
    "                         mp[mp['segmento'] == 'Bodega'].head(20)])\n",
    "whatsapp = pd.concat([wa[wa['segmento'] == 'Bodega'],\n",
    "                      wa[wa['segmento'] == 'Mayorista'].head(20)])\n",
    "\n",
    "print('EN TOTAL')\n",
    "print('  Marketplace: %.4f (n=%d)' % (marketplace['compro'].mean(), len(marketplace)))\n",
    "print('  WhatsApp:    %.4f (n=%d)' % (whatsapp['compro'].mean(), len(whatsapp)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Conclusión evidente: **Marketplace cierra mejor que WhatsApp**,\n",
    "60,00% contra 50,51%. Casi diez puntos.\n",
    "\n",
    "Ahora lo mismo, mirando dentro de cada segmento:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg in ['Bodega', 'Mayorista']:\n",
    "    a = marketplace[marketplace['segmento'] == seg]\n",
    "    b = whatsapp[whatsapp['segmento'] == seg]\n",
    "    print('%-10s Marketplace %.4f (n=%3d) | WhatsApp %.4f (n=%3d)'\n",
    "          % (seg, a['compro'].mean(), len(a), b['compro'].mean(), len(b)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se dio la vuelta 🤯\n",
    "\n",
    "En Bodega gana WhatsApp: 45,45% contra 25,00%. En Mayorista gana WhatsApp:\n",
    "95,00% contra 63,68%. **WhatsApp gana en los dos grupos y pierde en el\n",
    "total.**\n",
    "\n",
    "Eso es la paradoja de Simpson, y lo que la produce está en la composición:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('composicion de Marketplace:')\n",
    "print((marketplace['segmento'].value_counts(normalize=True) * 100).round(1))\n",
    "print()\n",
    "print('composicion de WhatsApp:')\n",
    "print((whatsapp['segmento'].value_counts(normalize=True) * 100).round(1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Marketplace está lleno de mayoristas, que cierran mucho de por sí. WhatsApp\n",
    "está lleno de bodegas, que cierran poco. El total no compara canales:\n",
    "**compara clientelas** 🎭\n",
    "\n",
    "Y cuidado con la moraleja fácil, que suele ser \"hay que mirar por grupos\". No\n",
    "siempre 🙅 Si el canal fuera la causa de que lleguen unos u otros clientes,\n",
    "partir por segmento estaría borrando justo el efecto que buscas.\n",
    "\n",
    "La regla honesta: **hay que decidir qué se controla antes de mirar los\n",
    "datos**, y con una razón. Si eliges después, siempre puedes encontrar el\n",
    "corte que te da la respuesta que querías."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. La regresión a la media"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este me parece el más bonito de todos, y el que más decisiones estropea 📉\n",
    "\n",
    "Ordeno las ventas de cada cliente y miro qué pasa *después* de una\n",
    "venta muy grande o muy chica:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "orden = v.sort_values(['cliente_id', 'fecha']).copy()\n",
    "orden['siguiente'] = orden.groupby('cliente_id')['monto'].shift(-1)\n",
    "con_siguiente = orden.dropna(subset=['siguiente'])\n",
    "\n",
    "alto = con_siguiente['monto'].quantile(0.8)\n",
    "bajo = con_siguiente['monto'].quantile(0.2)\n",
    "mejores = con_siguiente[con_siguiente['monto'] >= alto]\n",
    "peores = con_siguiente[con_siguiente['monto'] <= bajo]\n",
    "\n",
    "print('media de todas: %.2f' % con_siguiente['monto'].mean())\n",
    "print()\n",
    "print('el 20%% mas alto: esta venta %.2f -> la siguiente %.2f'\n",
    "      % (mejores['monto'].mean(), mejores['siguiente'].mean()))\n",
    "print('el 20%% mas bajo: esta venta %.2f -> la siguiente %.2f'\n",
    "      % (peores['monto'].mean(), peores['siguiente'].mean()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los que compraron 2.108 bajan a 794. Los que compraron 131 suben a 782. Los\n",
    "dos aterrizan en la media de 800,52 🛬\n",
    "\n",
    "Y ahora imagina el informe: *\"los clientes que atendimos con el programa\n",
    "premium bajaron un 62%\"*. O al revés: *\"la campaña de recuperación subió\n",
    "las ventas de los clientes flojos un 494%\"*.\n",
    "\n",
    "Las dos frases serían falsas, y las dos saldrían de datos correctos. No pasó\n",
    "nada: **los extremos vuelven al centro solos**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('correlacion entre una venta y la siguiente del mismo cliente: %.4f'\n",
    "      % stats.pearsonr(con_siguiente['monto'], con_siguiente['siguiente'])[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cero. La venta de hoy no dice nada de la siguiente, así que después de una\n",
    "venta enorme lo esperable es una venta *normal*, que comparada con la\n",
    "enorme parece una caída 📊\n",
    "\n",
    "La defensa: **si seleccionaste a alguien por ser extremo, necesitas un\n",
    "grupo de control**. Sin comparar contra clientes igual de extremos que no\n",
    "recibieron el programa, no se puede decir nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. El p-hacking, o buscar hasta encontrar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya lo vimos por partes en los capítulos 12, 13 y 15. Aquí está entero:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import product\n",
    "\n",
    "encontrados = []\n",
    "for col, col2 in product(['ciudad', 'canal', 'categoria'], repeat=2):\n",
    "    if col == col2:\n",
    "        continue\n",
    "    for val, val2 in product(v[col].unique(), v[col2].unique()):\n",
    "        sub = v[(v[col] == val) & (v[col2] == val2)]\n",
    "        if len(sub) < 80:\n",
    "            continue\n",
    "        a = sub.loc[sub['segmento'] == 'Mayorista', 'compro']\n",
    "        b = sub.loc[sub['segmento'] == 'Bodega', 'compro']\n",
    "        pv = stats.ttest_ind(a, b, equal_var=False).pvalue\n",
    "        encontrados.append((pv, col, val, col2, val2, len(sub)))\n",
    "\n",
    "encontrados.sort()\n",
    "print('subgrupos probados: %d' % len(encontrados))\n",
    "print()\n",
    "for pv, col, val, col2, val2, n in encontrados[:3]:\n",
    "    print('%s=%s y %s=%s (n=%d) -> p = %.3g' % (col, val, col2, val2, n, pv))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\"En Web, categoría Snacks, la diferencia entre mayoristas y bodegas es\n",
    "abrumadora: p = 0,00000000035\" 🎉\n",
    "\n",
    "Suena a hallazgo. Y es basura, porque probé **144 subgrupos** y\n",
    "reporté el mejor. Con 144 intentos al 5%, encontrar algo espectacular está\n",
    "garantizado.\n",
    "\n",
    "Lo peor de este error es que casi nadie lo hace a propósito. Se hace probando\n",
    "cosas, que es lo que uno debe hacer, y luego contando solo lo que salió.\n",
    "\n",
    "Las defensas, por orden de eficacia:\n",
    "\n",
    "- 📝 Escribe la hipótesis **antes** de mirar.\n",
    "\n",
    "- 🔢 Di cuántas cosas probaste. Siempre.\n",
    "\n",
    "- ➗ Corrige el alfa (Bonferroni, capítulo 13).\n",
    "\n",
    "- 🔁 Guarda datos que no miraste y comprueba el hallazgo ahí."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Las filas que no son independientes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todas las pruebas del libro suponen que cada fila es un caso independiente. Y\n",
    "en el capítulo 2 vimos que aquí hay **617 clientes en 3.000 ventas**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_cliente = (v.groupby(['cliente_id', 'segmento'])['compro']\n",
    "               .mean().reset_index())\n",
    "\n",
    "a_ventas = v.loc[v['segmento'] == 'Horeca', 'compro']\n",
    "b_ventas = v.loc[v['segmento'] == 'Minimarket', 'compro']\n",
    "a_clientes = por_cliente.loc[por_cliente['segmento'] == 'Horeca', 'compro']\n",
    "b_clientes = por_cliente.loc[por_cliente['segmento'] == 'Minimarket', 'compro']\n",
    "\n",
    "print('contando ventas:   n = %d y %d -> p = %.5f'\n",
    "      % (len(a_ventas), len(b_ventas),\n",
    "         stats.ttest_ind(a_ventas, b_ventas, equal_var=False).pvalue))\n",
    "print('contando clientes: n = %d y %d -> p = %.5f'\n",
    "      % (len(a_clientes), len(b_clientes),\n",
    "         stats.ttest_ind(a_clientes, b_clientes, equal_var=False).pvalue))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí me llevé una sorpresa y la publico tal cual 😅\n",
    "\n",
    "Yo esperaba que al contar clientes en vez de ventas el resultado se debilitara,\n",
    "porque la muestra baja de 742 a 438. Y pasó al revés: el p mejoró, de 0,01181 a\n",
    "0,00716.\n",
    "\n",
    "La explicación es que promediar las ventas de cada cliente **quita\n",
    "ruido**, y ese ruido pesaba más que las filas perdidas.\n",
    "\n",
    "Pero que aquí saliera bien no salva la regla, y es importante: en general,\n",
    "tratar 3.000 ventas de 617 clientes como 3.000 casos independientes\n",
    "**infla la confianza**. Aquí no lo hizo, y en tu tabla puede\n",
    "hacerlo 🎯\n",
    "\n",
    "La comprobación cuesta cinco líneas, como acabas de ver. Hazla siempre que la\n",
    "misma persona, tienda o máquina aparezca varias veces."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. El sesgo de selección"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este ya salió en el capítulo 8 y fue culpa mía, así que lo repito aquí como\n",
    "ejemplo 🙋"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dias_con_ventas = v.groupby(v['fecha'].dt.date).size()\n",
    "rango = (v['fecha'].max() - v['fecha'].min()).days + 1\n",
    "\n",
    "print('dias que aparecen en el archivo: %d' % len(dias_con_ventas))\n",
    "print('dias que hay en el periodo:      %d' % rango)\n",
    "print('dias sin ninguna venta:          %d' % (rango - len(dias_con_ventas)))\n",
    "print()\n",
    "print('ventas por dia contando solo los dias con ventas: %.4f'\n",
    "      % dias_con_ventas.mean())\n",
    "print('ventas por dia contando todos los dias:           %.4f'\n",
    "      % (len(v) / rango))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres días desaparecidos, y con ellos la respuesta cambia: 5,5866 ventas\n",
    "diarias contando solo los días con actividad, 5,5556 contando todos.\n",
    "\n",
    "Aquí la diferencia es chica porque solo faltan 3 días de 540. Pero el\n",
    "mecanismo es el importante: **agrupar por lo que existe borra lo que no\n",
    "existe**, y nadie lo ve porque las filas que faltan no están ahí para\n",
    "protestar 👻\n",
    "\n",
    "Es el mismo error que mirar solo a los clientes que siguen contigo, o solo a\n",
    "los proyectos que terminaron, o solo a las empresas que sobrevivieron."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El que más análisis rompe de verdad, y no da error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('filas del archivo:              %d' % len(v))\n",
    "print('filas que ve un groupby normal: %d' % v.groupby('satisfaccion').size().sum())\n",
    "print('con dropna=False:               %d'\n",
    "      % v.groupby('satisfaccion', dropna=False).size().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`groupby` tira las filas cuya clave es nula, en silencio 🕳️\n",
    "\n",
    "231 ventas desaparecen de cualquier análisis que agrupe por satisfacción, y no\n",
    "hay ningún aviso. Si además sumas montos por grupo, el total no cuadra con el\n",
    "total del archivo y te vas a volver loca buscando dónde.\n",
    "\n",
    "Y el que sí frena, por comparar:"
   ]
  },
  {
   "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",
    "    otro = pd.DataFrame({'cliente_id': range(10), 'nivel': 1})\n",
    "    v.merge(otro, on='cliente_id')\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",
    "ValueError: You are trying to merge on str and int64 columns for key 'cliente_id'. If you wish to proceed you should use pd.concat\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese te para porque los tipos no coinciden. Menos mal, porque un merge mal\n",
    "hecho es de los que más daño hacen: si la tabla de la derecha tiene claves\n",
    "repetidas, **te devuelve más filas de las que tenías** y todos los\n",
    "promedios cambian sin que nadie lo note.\n",
    "\n",
    "La costumbre que salva: `print(len(df))` antes y después de cada\n",
    "merge 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, y el segundo es el error número cuatro otra vez"
   ]
  },
  {
   "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').apply(lambda t: t['monto'].mean(), include_groups='si')\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",
    "ValueError: include_groups=True is no longer allowed.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Otra opción que quiere `True` o `False`. Y de paso te enseña algo de las librerías: esa opción **existía y la quitaron**. El código que copies de un tutorial de hace tres años puede fallar por esto y no porque esté mal pensado 🕰️"
   ]
  },
  {
   "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",
    "    stats.ttest_rel(v['monto'].head(10), v['monto'].head(9))\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",
    "ValueError: Array shapes are incompatible for broadcasting.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La prueba pareada compara **el antes y el después de la misma unidad**, así que necesita la misma cantidad de los dos lados. Que se queje del tamaño es la versión amable del error número cuatro de este capítulo: cuando las filas están emparejadas, tratarlas como si fueran independientes cambia el resultado."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Fabrica tú un Simpson al revés\n",
    "\n",
    "Construye el caso contrario: que Marketplace gane en los dos\n",
    "segmentos y pierda en el total."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. La regresión a la media, mes a mes\n",
    "\n",
    "Repite el experimento con meses en vez de ventas: coge los\n",
    "mejores clientes de un mes y mira el siguiente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Cuánto infla el p-hacking, medido\n",
    "\n",
    "Con datos donde no hay ningún efecto, prueba veinte\n",
    "subgrupos y quédate con el mejor. ¿Qué p obtienes?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El groupby que se come las filas, en euros\n",
    "\n",
    "Suma el monto agrupando por satisfacción y comprueba si\n",
    "cuadra con el total del archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. El merge que multiplica filas\n",
    "\n",
    "Fabrica una tabla de clientes con claves repetidas y\n",
    "comprueba qué le hace a tu archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Tu lista de comprobación\n",
    "\n",
    "Escribe una función que reciba un DataFrame y avise de las\n",
    "cosas de este capítulo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Cuarenta columnas de ruido puro\n",
    "\n",
    "Genera cuarenta columnas al azar y cuenta cuántas correlacionan con el monto al 5%."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Las filas que son el mismo cliente\n",
    "\n",
    "Cuenta cuántos clientes distintos hay de verdad y compara el promedio con todas las filas contra el de un pedido por cliente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cien hallazgos hechos con una moneda"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora la trampa, que es el error que invalida más análisis de todos los de este capítulo, y el que menos se ve 🪙"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Buscas dónde está la diferencia. Pruebas por ciudad, por segmento, por canal, por categoría, por combinaciones, y al final aparece un grupo con p menor que 0,05.\n",
    "\n",
    "```\n",
    "import numpy as np\n",
    "from scipy import stats\n",
    "\n",
    "rng = np.random.default_rng(2026)\n",
    "ps = []\n",
    "for _ in range(100):\n",
    "    marca = rng.random(len(v)) < 0.5\n",
    "    ps.append(prueba(v['compro'][marca],\n",
    "                     v['compro'][~marca]))\n",
    "\n",
    "print(sum(p < 0.05 for p in ps), round(min(ps), 4))\n",
    "# 10 0.0143\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Elegiste a los mejores clientes del trimestre y el siguiente bajaron un 62,6%. ¿Qué pasó?\n",
    "\n",
    "a) Nada: los extremos vuelven al centro solos, y hacía falta un grupo de control\n",
    "\n",
    "b) El programa que se les aplicó les hizo daño\n",
    "\n",
    "c) Se cansaron de comprar\n",
    "\n",
    "d) Hay un error en los datos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎭 Simpson: WhatsApp ganaba en los dos segmentos (45,45% contra 25,00% y\n",
    "95,00% contra 63,68%) y perdía en el total. Lo produce la composición, no los\n",
    "canales.\n",
    "\n",
    "- 📉 Regresión a la media: el 20% de ventas más altas promediaba 2.108,22 y su\n",
    "siguiente venta 794,42. La correlación entre una venta y la siguiente es\n",
    "-0,0099. Por meses, el 10% mejor \"cae\" un 67,8%.\n",
    "\n",
    "- 🎰 P-hacking: probando 20 subgrupos sin ningún efecto real, el 64,0% de las\n",
    "veces encuentras algo significativo, y la mediana del mejor p es 0,0331.\n",
    "\n",
    "- 👥 3.000 ventas de 617 clientes no son 3.000 casos independientes. Aquí\n",
    "contar por cliente mejoró el p, y en general lo empeora.\n",
    "\n",
    "- 👻 `groupby` tira las filas con clave nula sin avisar: 231 ventas\n",
    "y 197.554,60 soles, el 8,2% de la facturación.\n",
    "\n",
    "- 🔑 Un merge con claves repetidas duplica filas. 34 entraron, 68 salieron.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ninguno de estos errores se arregla con más estadística. Se arreglan\n",
    "pensando antes de calcular."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ojo con el de las filas que no son independientes, porque la forma más común\n",
    "de fabricarlo es un JOIN mal hecho que duplica filas sin avisar. Ese está medido\n",
    "en el [libro de SQL](https://missyera.com/guias/sql-desde-cero/) 🔗"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se acabó la teoría 🎓 En el capítulo 22 hacemos el proyecto completo de\n",
    "principio a fin: una pregunta de negocio, los datos, el análisis, y el informe\n",
    "que se puede defender en una reunión."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 17 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/errores-que-invalidan/\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
}
