{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Los tres métodos que no se ponen de acuerdo\n",
    "\n",
    "32, 203 o 222 filas raras según a quién le preguntes. Y las 21 ventas negativas que resultaron tener una cosa en común.\n",
    "\n",
    "Cuaderno de práctica del capítulo 6 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/atipicos/\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: \"ICAgICAgICAgICBjb24gICAgICAgIHNpbiAgICAgIGNhbWJpbwptZWRpYSAgICAgIDgwMy43NiAgIDc3OS41MiAgICAtMjQuMjQKbWVkaWFuYSAgICA1MzMuNjMgICA1MjYuNDQgICAgIC03LjE5CmRlc3YuICAgICAgNzUxLjk5ICAgNzAyLjczICAgIC00OS4yNQ==\",\n",
    "    2: \"ZmlsYXM6IG9yaWdpbmFsIDMwMDAgfCBjYXBhZG8gMzAwMAptZWRpYSAgICAgIDgwMy43NiAtPiAgIDgwMy40MwpkZXN2LiAgICAgIDc1MS45OSAtPiAgIDczMi44OQptaW5pbW8gICAtMjQ5Ny43MiAtPiAgICAzNS42NAptYXhpbW8gICAgNDIzNi43MSAtPiAgMzA1OC41OQ==\",\n",
    "    3: \"cG9yIHo6ICAgNwpwb3IgSVFSOiAxMApwb3IgTUFEOiAw\",\n",
    "    4: \"Y291bnQgICAgMzAwMC4wMAptZWFuICAgICAgMTIxLjI4CnN0ZCAgICAgICAyNTUuMTUKbWluICAgICAgLTU5OS45MAoyNSUgICAgICAgIDIzLjk0CjUwJSAgICAgICAgNTMuMDIKNzUlICAgICAgIDEyMS43NgptYXggICAgICAzNTg2LjI2CmR0eXBlOiBmbG9hdDY0Cgp2ZW50YXMgYSBtZW5vcyBkZSA1IHNvbGVzIGxhIHVuaWRhZDogMzkKICAgIHNlZ21lbnRvICB1bmlkYWRlcyAgbW9udG8KNDA0ICAgQm9kZWdhICAgICAgICAxMyAgNjIuNjAKNTM0ICAgQm9kZWdhICAgICAgICAgOSAgMzYuNDkKNTY1ICAgQm9kZWdhICAgICAgICAxMyAgNTYuOTAKNTcwICAgQm9kZWdhICAgICAgICAyNCAgMzUuNTMKODg5ICAgQm9kZWdhICAgICAgICAyMSAgNzMuOTc=\",\n",
    "    6: \"bW9udG8sIG1pcmFuZG8gc2VnbWVudG8KICBhdGlwaWNvcyBnbG9iYWxlczogIDMyCiAgYXRpcGljb3MgcG9yIHNlZ21lbnRvOiAgMjEKICBtYXJjYWRvcyBzb2xvIHBvciBlbCBnbG9iYWw6ICAyOAogIEFWSVNPOiBlbCBtZXRvZG8gZ2xvYmFsIGVzdGEgbWFyY2FuZG8gZ3J1cG9zIGVudGVyb3MKCnVuaWRhZGVzLCBtaXJhbmRvIHNlZ21lbnRvCiAgYXRpcGljb3MgZ2xvYmFsZXM6ICAgNwogIGF0aXBpY29zIHBvciBzZWdtZW50bzogICA2CiAgbWFyY2Fkb3Mgc29sbyBwb3IgZWwgZ2xvYmFsOiAgIDE=\",\n",
    "    7: \"bW9udG8gICAgICAgICAgMjAzIGF0aXBpY29zIHBvciBJUVIsIG8gc2VhIGVsIDYuOCUKdW5pZGFkZXMgICAgICAgIDEwIGF0aXBpY29zIHBvciBJUVIsIG8gc2VhIGVsIDAuMyUKZGVzY3VlbnRvICAgICAgICAwIGF0aXBpY29zIHBvciBJUVIsIG8gc2VhIGVsIDAuMCU=\",\n",
    "    8: \"Y29uIGsgPSAxLjUgZWwgY29ydGUgZXN0YSBlbiAgMjI5Mi42OCB5IHNvYnJhbiAgMjAwIGZpbGFzCmNvbiBrID0gMi4wIGVsIGNvcnRlIGVzdGEgZW4gIDI3MDAuNjcgeSBzb2JyYW4gICA3NyBmaWxhcwpjb24gayA9IDMuMCBlbCBjb3J0ZSBlc3RhIGVuICAzNTE2LjY3IHkgc29icmFuICAgIDkgZmlsYXM=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un atípico no es un número grande. Es **un número que no pertenece al\n",
    "grupo** 🎯\n",
    "\n",
    "Y esa diferencia, que parece filosófica, decide todo lo que viene. Una venta\n",
    "de 4.000 soles es enorme para una bodega y es martes por la tarde para un\n",
    "mayorista.\n",
    "\n",
    "Vamos a verlo con los tres métodos que se usan, aplicados a las mismas\n",
    "3.000 filas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\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",
    "m = v['monto']\n",
    "\n",
    "print('filas: %d | de %.2f a %.2f' % (len(m), m.min(), m.max()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una pregunta que decide medio capítulo: **¿el dato raro de tu archivo es un error o es tu mejor cliente?** Porque se quitan de formas distintas, y uno de los dos no se quita 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Método 1: la puntuación z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El clásico. Cuenta a cuántas desviaciones está cada fila del promedio, y\n",
    "marca las que pasen de 3:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "zi=xi−x¯s\n",
    "\n",
    "a cuántas desviaciones del centro está cada dato"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "z = (m - m.mean()) / m.std()\n",
    "\n",
    "print('a mas de 3 desviaciones: %d filas' % (z.abs() > 3).sum())\n",
    "print('a mas de 2 desviaciones: %d filas' % (z.abs() > 2).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "32 filas sospechosas de 3.000, un 1,07%. Suena razonable.\n",
    "\n",
    "Antes de seguir, vamos a mirarlas. Esto es lo que casi nadie hace y es lo\n",
    "único que hay que hacer siempre 🔍"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "raras = v[z.abs() > 3]\n",
    "print(raras['segmento'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las **32**. Todas 😳\n",
    "\n",
    "Ahí se cae el método entero. La z no encontró ventas raras: encontró\n",
    "**el segmento que vende más caro**. Un mayorista está a más de tres\n",
    "desviaciones del promedio de la empresa porque el promedio de la empresa está\n",
    "lleno de bodegas que venden a 178 soles.\n",
    "\n",
    "Y si le hubieras hecho caso a la receta (\"quita los atípicos y sigue\"),\n",
    "habrías borrado a tus mejores clientes por ser tus mejores clientes 😬\n",
    "\n",
    "Es exactamente el mismo fenómeno de los capítulos 3, 4 y 5: **estamos\n",
    "metiendo cuatro poblaciones en el mismo saco**. Ya nos rompió la media,\n",
    "la dispersión y la forma. Ahora nos rompe la detección de atípicos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Método 2: el rango intercuartílico"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La versión robusta. Marca lo que caiga a más de 1,5 IQR fuera de los\n",
    "cuartiles, que es la regla del diagrama de caja de toda la vida:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q1, q3 = m.quantile(0.25), m.quantile(0.75)\n",
    "iqr = q3 - q1\n",
    "bajo, alto = q1 - 1.5 * iqr, q3 + 1.5 * iqr\n",
    "\n",
    "print('limites: %.2f a %.2f' % (bajo, alto))\n",
    "print('por debajo: %d | por encima: %d | total: %d'\n",
    "      % ((m < bajo).sum(), (m > alto).sum(), ((m < bajo) | (m > alto)).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "203 filas, seis veces más que la z 😵\n",
    "\n",
    "Y mira el reparto: 200 por arriba y 3 por abajo. Ese desequilibrio es la cola\n",
    "derecha del capítulo 5 asomando otra vez."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Método 3: el MAD, el más robusto de todos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La z usa media y desviación, que son justo las dos medidas que los atípicos\n",
    "estropean. Es un poco absurdo si lo piensas: le pides a un dato raro que se\n",
    "delate usando un promedio que él mismo está inflando 🙃\n",
    "\n",
    "El MAD (desviación absoluta mediana) arregla eso usando mediana en los dos\n",
    "sitios:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "zirob=0,6745(xi−x~)mediana(|x−x~|)\n",
    "\n",
    "lo mismo que la z pero con medianas en los dos sitios, para que los propios atípicos no inflen la vara con la que se les mide"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mediana = m.median()\n",
    "mad = (m - mediana).abs().median()\n",
    "z_robusta = 0.6745 * (m - mediana) / mad\n",
    "\n",
    "print('MAD: %.2f' % mad)\n",
    "print('a mas de 3.5 z robustas: %d filas' % (z_robusta.abs() > 3.5).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "222. Otro número distinto 🤯\n",
    "\n",
    "El 0,6745 es una constante de conversión para que la escala se parezca a la\n",
    "de una desviación estándar normal, y el umbral de 3,5 es la convención que\n",
    "acompaña a este método."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los tres a la vez"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_z = set(v.index[z.abs() > 3])\n",
    "por_iqr = set(v.index[(m < bajo) | (m > alto)])\n",
    "por_mad = set(v.index[z_robusta.abs() > 3.5])\n",
    "\n",
    "print('z: %d | IQR: %d | MAD: %d' % (len(por_z), len(por_iqr), len(por_mad)))\n",
    "print()\n",
    "print('en z y en IQR:   %d' % len(por_z & por_iqr))\n",
    "print('en IQR y en MAD: %d' % len(por_iqr & por_mad))\n",
    "print('en los tres:     %d' % len(por_z & por_iqr & por_mad))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en el patrón, que es limpísimo: **las 32 de la z están dentro de\n",
    "las 203 del IQR, y casi todas las 203 están dentro de las 222 del MAD**.\n",
    "\n",
    "No es que los métodos se contradigan. Es que son **tres niveles de\n",
    "severidad del mismo criterio**. La z es la más permisiva porque los\n",
    "propios atípicos inflan la desviación que se usa para juzgarlos; el MAD es el\n",
    "más estricto porque no se deja inflar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Método | Usa | Encontró | Cuándo usarlo |\n",
    "|---|---|---|---|\n",
    "| Puntuación z | Media y desviación | 32 | Solo si los datos son acampanados |\n",
    "| 1,5 x IQR | Cuartiles | 203 | El de por defecto, funciona casi siempre |\n",
    "| MAD | Mediana dos veces | 222 | Cuando sospechas que hay muchos atípicos |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ahora hazlo bien: dentro de cada grupo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que había que hacer desde el principio: comparar cada venta contra las de\n",
    "su propio segmento 🏪"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg, g in v.groupby('segmento'):\n",
    "    zz = (g['monto'] - g['monto'].mean()) / g['monto'].std()\n",
    "    print('%-11s %3d atipicos de %d' % (seg, (zz.abs() > 3).sum(), len(g)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "21 filas repartidas entre los cuatro segmentos, en vez de 32 concentradas en\n",
    "uno 🙌\n",
    "\n",
    "Y ahora sí son atípicos de verdad: ventas que no se parecen a las de su\n",
    "propio grupo. Mayorista pasa de 32 a 4, que es lo que tenía que pasar.\n",
    "\n",
    "**Regla para llevarte:** antes de buscar atípicos, pregúntate\n",
    "contra qué se está comparando cada fila. Si tus datos tienen grupos con niveles\n",
    "distintos, la detección va por grupo o no vale nada 📌"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las ventas negativas, y lo que escondían"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Llevan saliendo desde el capítulo 2. Vamos a por ellas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "negativas = v[v['monto'] < 0]\n",
    "\n",
    "print('ventas negativas: %d' % len(negativas))\n",
    "print('suman: %.2f soles' % negativas['monto'].sum())\n",
    "print()\n",
    "print(negativas.groupby('segmento')['monto'].agg(['count', 'mean', 'min']).round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "21 filas y casi 9.000 soles en negativo. Repartidas por los cuatro\n",
    "segmentos, y con montos proporcionales al tamaño de cada uno.\n",
    "\n",
    "Y ahora la pregunta que lo resolvió todo. ¿Qué tienen en común?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(negativas['compro'].value_counts())\n",
    "print()\n",
    "print('de las 3000 filas, cuantas no compraron:', (v['compro'] == 0).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Las 21 son ventas que no se cerraron.** Ni una sola\n",
    "excepción 🔎\n",
    "\n",
    "Eso cambia por completo la interpretación. No son devoluciones repartidas al\n",
    "azar: son operaciones que quedaron en negativo *y* no llegaron a\n",
    "concretarse. Lo más probable es que sean anulaciones o notas de crédito que se\n",
    "colaron en la tabla de ventas.\n",
    "\n",
    "Si hubiera sido casualidad, con 21 filas y un 42% de no-compras esperaríamos\n",
    "unas nueve, no veintiuna. Que salgan las 21 no es casualidad, es una regla del\n",
    "sistema que nadie documentó.\n",
    "\n",
    "Y fíjate en lo que acaba de pasar: **buscando datos que estorbaban,\n",
    "encontramos algo del negocio**. Por eso los atípicos se miran antes de\n",
    "borrarlos. Suele haber más información en las 21 filas raras que en las 2.979\n",
    "normales 💎"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este lo he cometido yo mil veces. Quiero filtrar por una columna que tiene\n",
    "nulos, así que la limpio antes:"
   ]
  },
  {
   "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[v['satisfaccion'].dropna() > 4]\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",
    "IndexingError: Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match).\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que pasa es que `dropna()` devuelve una serie de 2.769 filas, y\n",
    "estás usándola para filtrar un DataFrame de 3.000. pandas intenta alinearlas por\n",
    "índice, ve que no encajan y se planta.\n",
    "\n",
    "Y menos mal que se planta 🙏 porque la alternativa habría sido rellenar los\n",
    "huecos con algo y devolverte filas que no pediste.\n",
    "\n",
    "La forma correcta es filtrar sobre el original, que trata los nulos como\n",
    "falsos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('satisfaccion mayor que 4:', (v['satisfaccion'] > 4).sum())\n",
    "print('filas con satisfaccion nula:', v['satisfaccion'].isna().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "563 filas, y los 231 nulos quedaron fuera sin drama. Que es lo que querías."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora, ¿qué hago con ellos?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pregunta que casi nadie hace. Hay cuatro respuestas y solo una es\n",
    "automática:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Qué haces | Cuándo | En este archivo |\n",
    "|---|---|---|\n",
    "| **Investigar** | Siempre, primero | Las 21 negativas resultaron ser todas no-compras |\n",
    "| **Corregir** | Si es un error de captura demostrable | Un monto con la coma mal puesta |\n",
    "| **Dejar** | Si son reales y te importan | Las ventas de 4.000 soles de Mayorista |\n",
    "| **Capar** | Si estorban al modelo pero existen | Ponerles el valor del percentil 99 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que **nunca** se hace es borrarlos sin mirarlos. Es la\n",
    "diferencia entre limpiar los datos y maquillarlos 💄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, los dos de los cuartiles"
   ]
  },
  {
   "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",
    "    m.between(bajo, alto, inclusive='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: Inclusive has to be either string of 'both','left', 'right', or 'neither'.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí el mensaje es de los buenos: te dice las cuatro opciones válidas. Y esas cuatro no son decoración, son la decisión de si el valor que cae justo en el borde entra o no entra, que con atípicos importa."
   ]
  },
  {
   "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['monto'].quantile(1.25)\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: percentiles should all be in the interval [0, 1]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En pandas los cuantiles van de 0 a 1, así que el 25 se escribe 0,25 y el 1,25 no existe. Ojo con esto porque es el gemelo del error de `np.percentile`: **las dos librerías usan escalas distintas para lo mismo** 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. ¿Cuánto cambia todo si los quitas?\n",
    "\n",
    "Quita las 32 filas de la puntuación z y mira qué le pasa a\n",
    "la media, a la mediana y a la desviación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Capar en vez de borrar\n",
    "\n",
    "Prueba la alternativa: en vez de quitar las filas\n",
    "extremas, ponles el valor del percentil 1 y del 99."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Los atípicos de una columna que no tiene cola\n",
    "\n",
    "Aplica los tres métodos a `unidades`, que en el\n",
    "capítulo 5 era la más acampanada. ¿Siguen discrepando tanto?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Un atípico que no está en ninguna punta\n",
    "\n",
    "Todos los métodos de arriba miran una columna sola. Busca\n",
    "filas raras mirando dos a la vez: ventas con muchas unidades y monto bajo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Los atípicos por grupo, todos a la vez\n",
    "\n",
    "Escribe algo que marque cada fila como atípica respecto a\n",
    "su propio segmento, y devuelve el DataFrame con esa columna nueva."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Tu informe de atípicos\n",
    "\n",
    "Junta el capítulo en una función que reciba una columna y\n",
    "un grupo, y te diga qué se encontró antes de decidir nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Atípicos en tres columnas\n",
    "\n",
    "Aplica el criterio del IQR a tres columnas y compara cuántos salen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Mueve el 1,5 y mira qué pasa\n",
    "\n",
    "Prueba el criterio con k igual a 1,5, 2 y 3, y cuenta cuántas filas sobran en cada caso."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El filtro simétrico que solo cortó de un lado"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora la trampa, que es justo lo que pasa cuando limpias atípicos sin mirar de dónde los estás quitando 📉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Limpias los atípicos antes de calcular, como manda el manual: fuera todo lo que se aleje más de tres desviaciones. Vuelves a sacar el promedio.\n",
    "\n",
    "```\n",
    "mu, sd = v['monto'].mean(), v['monto'].std()\n",
    "arriba = (v['monto'] > mu + 3 * sd).sum()\n",
    "abajo  = (v['monto'] < mu - 3 * sd).sum()\n",
    "\n",
    "print(arriba, abajo)\n",
    "# 30 2\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",
    "La puntuación z marca 32 ventas como atípicas y las 32 son del segmento Mayorista. ¿Qué haces?\n",
    "\n",
    "a) Comparo cada venta contra su propio segmento antes de decidir\n",
    "\n",
    "b) Las quito, porque el método las marcó\n",
    "\n",
    "c) Uso el IQR, que es más robusto\n",
    "\n",
    "d) Las dejo porque son pocas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 Un atípico no es un número grande, es un número que no pertenece al\n",
    "grupo. Todo depende de contra qué lo compares.\n",
    "\n",
    "- 🔢 Los tres métodos dan 32, 203 y 222 sobre los mismos datos. No se\n",
    "contradicen: son tres niveles de severidad, y la z es la más permisiva porque los\n",
    "atípicos inflan la desviación con la que se les juzga.\n",
    "\n",
    "- 🏪 Los 32 atípicos globales eran los 32 mayoristas más grandes: 28 de ellos\n",
    "no son raros dentro de su propio segmento. Comparando por grupo salen 21\n",
    "repartidos entre los cuatro.\n",
    "\n",
    "- 🧮 Dos totales iguales no son el mismo conjunto. Los 21 atípicos por grupo y\n",
    "las 21 ventas negativas coincidían en el número y compartían solo 15 filas.\n",
    "\n",
    "- 💎 Las 21 negativas tienen las tres en común: ninguna se cerró. Buscando\n",
    "basura encontramos una regla del sistema que nadie había documentado.\n",
    "\n",
    "- 🔀 Los atípicos multivariantes no salen en ninguna punta. 39 ventas a menos\n",
    "de 5 soles la unidad son normales en las dos columnas por separado.\n",
    "\n",
    "- 🚫 Nunca borrar sin mirar. Investigar, corregir, dejar o capar, en ese\n",
    "orden.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un atípico no es un dato equivocado. Es un dato raro, y antes de quitarlo\n",
    "hay que mirar qué es."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y qué hacer al final con los raros (quitarlos, recortarlos o dejarlos) no es\n",
    "una decisión de estadística, es de preprocesamiento, y está contada con sus\n",
    "consecuencias en el\n",
    "[libro de machine learning](https://missyera.com/guias/machine-learning-desde-cero/) 🤖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cerramos la parte descriptiva. A partir del capítulo 7 dejamos de describir\n",
    "lo que tenemos y empezamos a afirmar cosas sobre lo que no medimos, que es donde\n",
    "la estadística se pone interesante y peligrosa a la vez. Empezamos por la\n",
    "probabilidad, y solo la que hace falta 🎲"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué son los outliers?Datos que se salen del grupo. En castellano se les dice valores atípicos, y los dos nombres se usan igual.\n",
    "\n",
    "¿Cómo se detectan los valores atípicos?Con la regla del rango intercuartílico, que marca lo que cae más allá de vez y media el rango por debajo o por encima de los cuartos. Con puntuación z también, y esa se deja engañar por los propios atípicos.\n",
    "\n",
    "¿Se eliminan los outliers?Casi nunca sin mirarlos. Un dato raro puede ser un error de tipeo, que se corrige, o el mejor cliente del año, que hay que dejar. Borrarlos por norma es cómo desaparecen las ventas grandes de un reporte.\n",
    "\n",
    "¿Qué es la puntuación z?A cuántas desviaciones estándar de la media está un dato. Un z de 3 quiere decir tres desviaciones por encima."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 6 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/atipicos/\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
}
