{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cada columna, mirada de una en una\n",
    "\n",
    "La ficha de cuatro números que decide qué escalar y qué transformar, y los tres métodos que cuentan 203, 32 o 222 atípicos en la misma columna.\n",
    "\n",
    "Cuaderno de práctica del capítulo 6 de **Machine learning 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/machine-learning-desde-cero/eda-univariado/\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: \"ICAgICAgICAgICAgICBjb2x1bW5hICBhc2ltZXRyaWEgICAgY3YgICAgICAgICAgICAgICAgIHF1ZV9oYWNlcgogICAgICAgICAgICAgdW5pZGFkZXMgICAgICAwLjE4NCAwLjQ5OCAgICAgICAgICAgZXNjYWxhciB5IGxpc3RvCiAgICAgICAgICAgICAgICBtb250byAgICAgIDEuMzQwIDAuOTM2ICAgICAgIHNlc2dhZGE6IHByb2JhciBsb2cKICAgICAgICAgICAgZGVzY3VlbnRvICAgICAtMC4wMTYgMC41NzMgICAgICAgICAgIGVzY2FsYXIgeSBsaXN0bwogICAgICAgICBzYXRpc2ZhY2Npb24gICAgIC0wLjAxNSAwLjQ3MiAgICAgICAgICAgZXNjYWxhciB5IGxpc3RvCiAgICAgIHByZWNpb191bml0YXJpbyAgICAgIDYuNzM0IDIuMTA0IG11eSBzZXNnYWRhOiBsb2cgbyB0cmFtb3MKbW9udG9fZmluYWxfZmFjdHVyYWRvICAgICAgMS43NjQgMS40MTEgICAgICAgc2VzZ2FkYTogcHJvYmFyIGxvZw==\",\n",
    "    2: \"b3JpZ2luYWwgICAgICAgIGFzaW1ldHJpYSA2Ljc2NiAgY3VydG9zaXMgNjAuMTYxCmxvZyAgICAgICAgICAgICBhc2ltZXRyaWEgMC4yNzEgIGN1cnRvc2lzIDAuMjcyCnJhaXogY3VhZHJhZGEgICBhc2ltZXRyaWEgMi44MDcgIGN1cnRvc2lzIDEyLjE3NwoKZmlsYXMgcXVlIGVsIGxvZyBkZWphIGZ1ZXJhIHBvciBubyBzZXIgcG9zaXRpdmFzOiAyMQ==\",\n",
    "    3: \"ICAgICAgICAgICAgICAgICAgbiAgICAgIG1lZGlhICAgIG1lZGlhbmEgICAgICAgICBkcyAgICAgYXNpbQpjb24gdG9kbyAgICAgICAzMDAwICAgICA4MDMuNzYgICAgIDUzMy42MyAgICAgNzUxLjk5ICAgIDEuMzQwCnNpbiBhdGlwaWNvcyAgIDI3OTcgICAgIDY3MC45MyAgICAgNDk0LjQ3ICAgICA1NTguNzEgICAgMS4yMTQKCnNvbGVzIHF1ZSBzZSB2YW46IDUzNDY3OS40Nwpwb3JjZW50YWplIGRlbCBkaW5lcm8gdG90YWw6IDIyLjE3ICU=\",\n",
    "    4: \"ICAgICAgICAgICAgICBuICAgICBtZWRpYSAgIG1lZGlhbmEgICAgICAgZHMgIGFzaW1ldHJpYSAgICAgY3YKc2VnbWVudG8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKQm9kZWdhICAgICAgNzA3ICAgMTc4LjY5OCAgIDE4My4yNjAgICA2OS41ODcgICAgIC0wLjc3NCAgMC4zODkKSG9yZWNhICAgICAgNzQyICAgNzU1LjE3OSAgIDc0Mi45OTUgIDI3Ny4yNzYgICAgIC0wLjQyNyAgMC4zNjcKTWF5b3Jpc3RhICAgNzUwICAxODU2LjMzNyAgMTg2OS4yNTUgIDcxOS41NTEgICAgIC0wLjQ1NSAgMC4zODgKTWluaW1hcmtldCAgODAxICAgNDE0LjkxNyAgIDQxMy43NDAgIDE0OC42ODAgICAgIC0wLjY3OCAgMC4zNTgKCmdsb2JhbDogbWVkaWEgODAzLjc2IGFzaW1ldHJpYSAxLjM0\",\n",
    "    5: \"dW5pZGFkZXMgICAgICAgICAgICAgICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgICAxMy4wMCB5IHNhbGUgICAyMDggdmVjZXMgKCA2LjkzJSkKbW9udG8gICAgICAgICAgICAgICAgICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgIDIwNC40NyB5IHNhbGUgICAgIDMgdmVjZXMgKCAwLjEwJSkKZGVzY3VlbnRvICAgICAgICAgICAgICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgICAgMC4wNSB5IHNhbGUgICAgMjAgdmVjZXMgKCAwLjgzJSkKc2F0aXNmYWNjaW9uICAgICAgICAgICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgICAgNS4wMCB5IHNhbGUgICA1NjMgdmVjZXMgKDIwLjMzJSkKcHJlY2lvX3VuaXRhcmlvICAgICAgICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgIDE4NS4wMyB5IHNhbGUgICAgIDIgdmVjZXMgKCAwLjA3JSkKbW9udG9fZmluYWxfZmFjdHVyYWRvICBlbCB2YWxvciBtYXMgcmVwZXRpZG8gZXMgICAgICAgMC4wMCB5IHNhbGUgIDEyNjcgdmVjZXMgKDQyLjIzJSk=\",\n",
    "    6: \"ZmFsdGFfc2F0aXNmYWNjaW9uICBGYWxzZSAgVHJ1ZSAKZmFsdGFfZGVzY3VlbnRvICAgICAgICAgICAgICAgICAKRmFsc2UgICAgICAgICAgICAgICAgMjIwOSAgICAxOTYKVHJ1ZSAgICAgICAgICAgICAgICAgIDU2MCAgICAgMzUKCnNpIGZhbHRhcmFuIGluZGVwZW5kaWVudGVzLCBjb2luY2lkaXJpYW4gZWwgMS41MyAlCmNvaW5jaWRlbiBkZSB2ZXJkYWQgZWwgICAgICAgICAgICAgICAgICAgICAgMS4xNyAl\",\n",
    "    7: \"ICBtb250bzogMjEgdmFsb3JlcyBuZWdhdGl2b3MKICBkZXNjdWVudG86IGZhbHRhIGVsIDE5LjglCiAgcHJlY2lvX3VuaXRhcmlvOiBhc2ltZXRyaWEgNi43MywgbXV5IHNlc2dhZGEKICBwcmVjaW9fdW5pdGFyaW86IDIxIHZhbG9yZXMgbmVnYXRpdm9zCiAgbW9udG9fZmluYWxfZmFjdHVyYWRvOiBlbCB2YWxvciAwLjAgc2UgbGxldmEgZWwgNDIuMiU=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya está limpio y ya sabemos qué es cada columna. Toca mirarlas\n",
    "**una por una**, sin cruzarlas con nada 🔎\n",
    "\n",
    "Y esto no es un trámite. Lo que salga aquí decide cosas muy concretas más\n",
    "adelante: qué se escala, qué se transforma, qué se parte en tramos, qué imputar\n",
    "con la media y qué con la mediana. Saltarse este capítulo es tomar todas esas\n",
    "decisiones a ojo.\n",
    "\n",
    "Tranqui, que son cuatro números por columna y una función que los saca\n",
    "todos 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La ficha de una columna"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El resumen que yo saco de cada columna numérica antes de decidir nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy import stats\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "def carga_limpia(url):\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', errors='coerce')\n",
    "        v[col] = f\n",
    "    return v\n",
    "\n",
    "ventas = carga_limpia(URL)\n",
    "ventas['precio_unitario'] = ventas['monto'] / ventas['unidades']\n",
    "\n",
    "NUMERICAS = ['unidades', 'monto', 'descuento', 'satisfaccion',\n",
    "             'precio_unitario', 'monto_final_facturado']\n",
    "\n",
    "def ficha(v, columnas):\n",
    "    filas = []\n",
    "    for c in columnas:\n",
    "        s = v[c].dropna()\n",
    "        filas.append({\n",
    "            'columna': c,\n",
    "            'n': len(s),\n",
    "            'nulos_%': round(100 * v[c].isna().mean(), 2),\n",
    "            'media': round(s.mean(), 2),\n",
    "            'mediana': round(s.median(), 2),\n",
    "            'ds': round(s.std(), 2),\n",
    "            'cv': round(s.std() / s.mean(), 3) if s.mean() else np.nan,\n",
    "            'asimetria': round(stats.skew(s), 3),\n",
    "            'curtosis': round(stats.kurtosis(s), 3),\n",
    "            'min': round(s.min(), 2),\n",
    "            'max': round(s.max(), 2),\n",
    "        })\n",
    "    return pd.DataFrame(filas)\n",
    "\n",
    "print(ficha(ventas, NUMERICAS).to_string(index=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa tabla ya cuenta media docena de cosas. Vamos por partes 🧵"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los cuatro números que de verdad se miran"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Media contra mediana.** Si se parecen, la columna es\n",
    "simétrica. Si la media es mucho mayor, hay cola por la derecha:\n",
    "`monto` tiene media 803,76 y mediana 533,63, o sea que la mitad de\n",
    "las ventas está por debajo de 534 y unas pocas grandes tiran del promedio.\n",
    "\n",
    "**El coeficiente de variación**, que es la desviación dividida\n",
    "entre la media."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "CV=sx¯\n",
    "\n",
    "la desviación dividida entre la media, que sirve para comparar cuánto varían dos columnas medidas en unidades distintas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sirve para lo que la desviación sola no puede: comparar columnas medidas en\n",
    "unidades distintas. `unidades` tiene CV 0,498 y\n",
    "`precio_unitario` tiene 2,104, o sea que el precio por unidad varía\n",
    "**cuatro veces más** en términos relativos, aunque los dos números\n",
    "crudos no se puedan comparar.\n",
    "\n",
    "**Asimetría y curtosis**, que en una campana perfecta valen\n",
    "cero las dos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "g1=1n∑(xi−x¯)3s3,g2=1n∑(xi−x¯)4s4−3\n",
    "\n",
    "la primera dice hacia qué lado se estira la cola y la segunda cuánto pesan los extremos, y las dos valen cero en una campana perfecta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí salta la que se lleva el premio: `precio_unitario`, con\n",
    "asimetría 6,734 y curtosis 60,015. Eso no es una cola larga, eso es un cohete\n",
    "🚀\n",
    "\n",
    "Una columna así rompe cualquier modelo lineal si entra tal cual, y encima el\n",
    "escalado estándar no la arregla: restarle la media y dividir por la desviación\n",
    "no cambia la forma, solo la mueve de sitio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que se comete recorriendo columnas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo natural cuando quieres la ficha de todo es meter un bucle sobre\n",
    "`ventas.columns`. No acaba bien."
   ]
  },
  {
   "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.shapiro(ventas['segmento'])\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: could not convert string to float: 'Horeca'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por eso la función de arriba recibe una lista de columnas en vez de\n",
    "recorrerlas todas. La lista sale de la auditoría de tipos del capítulo\n",
    "5, que es exactamente para lo que servía 🗂️\n",
    "\n",
    "Y ojo con la otra versión del mismo problema, que es peor porque no da error:\n",
    "si le pasas una columna con nulos, `shapiro` devuelve\n",
    "`nan` tan tranquilo. De ahí el `dropna()` dentro de la\n",
    "ficha."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Normalidad: la prueba contesta otra pregunta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo el mundo aprende a preguntar si una columna es normal. Preguntémoslo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for c in NUMERICAS:\n",
    "    s = ventas[c].dropna()\n",
    "    w, p = stats.shapiro(s.sample(min(4000, len(s)), random_state=7))\n",
    "    ad = stats.anderson(s, dist='norm')\n",
    "    print(f'{c:22s} shapiro W {w:.4f} p {p:.3e}   anderson {ad.statistic:8.2f} '\n",
    "          f'(critico al 5% {ad.critical_values[2]:.3f})')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las seis rechazadas, y no por poco: la más suave sale con p de 5,18e-16 😅\n",
    "\n",
    "Aquí es donde casi todo el mundo concluye \"es que con muchos datos la prueba\n",
    "rechaza siempre\". Yo también lo pensaba, así que lo medí."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(7)\n",
    "\n",
    "print('datos normales de verdad:')\n",
    "for n in (30, 100, 1000, 3000, 20000):\n",
    "    p = stats.shapiro(rng.normal(0, 1, n)).pvalue\n",
    "    print(f'  n {n:6d}  p {p:.4f}  {\"pasa\" if p > 0.05 else \"RECHAZA\"}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pues no. Los datos normales pasan la prueba con 30 y con 20.000 🤨\n",
    "\n",
    "Así que la conclusión honesta es la incómoda: **estas seis columnas\n",
    "sencillamente no son normales**. No es un artefacto del tamaño.\n",
    "\n",
    "Donde sí manda el tamaño es en las desviaciones chicas. Aquí una mezcla que\n",
    "es 95% normal y 5% de algo con más dispersión, veinte veces por cada tamaño:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('95% normal y 5% de otra cosa, 20 intentos por tamano:')\n",
    "for n in (50, 200, 1000, 3000, 10000):\n",
    "    ps = []\n",
    "    for semilla in range(20):\n",
    "        g = np.random.default_rng(semilla)\n",
    "        mezcla = np.where(g.random(n) < 0.95, g.normal(0, 1, n), g.normal(0, 3, n))\n",
    "        ps.append(stats.shapiro(mezcla).pvalue)\n",
    "    ps = np.array(ps)\n",
    "    print(f'  n {n:6d}  p mediana {np.median(ps):.2e}  rechaza en '\n",
    "          f'{int((ps < 0.05).sum())}/20')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el efecto del tamaño, dicho bien: con 50 filas esa desviación se\n",
    "cuela la mitad de las veces y con 1.000 no se le escapa ninguna.\n",
    "\n",
    "O sea que la prueba no rechaza de más con muchos datos: **rechaza de\n",
    "menos con pocos**. Es lo mismo visto al revés y cambia la conclusión\n",
    "entera.\n",
    "\n",
    "Y aun así yo casi nunca uso la prueba, por una razón práctica: contesta\n",
    "\"¿es exactamente normal?\" cuando la pregunta útil es \"¿cuánto se desvía?\". Eso\n",
    "lo contestan la asimetría y la curtosis, que además te dicen hacia dónde 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuántos atípicos hay, según a quién preguntes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres formas de contarlos, las tres estándar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for c in NUMERICAS:\n",
    "    s = ventas[c].dropna()\n",
    "    q1, q3 = s.quantile([0.25, 0.75])\n",
    "    ri = q3 - q1\n",
    "    tukey = ((s < q1 - 1.5 * ri) | (s > q3 + 1.5 * ri)).sum()\n",
    "    z = (np.abs(stats.zscore(s)) > 3).sum()\n",
    "    mad = stats.median_abs_deviation(s)\n",
    "    robusto = (np.abs(0.6745 * (s - s.median()) / mad) > 3.5).sum() if mad else 0\n",
    "    print(f'{c:22s} tukey {tukey:5d}   z>3 {z:5d}   z robusto {robusto:5d}   '\n",
    "          f'de {len(s)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira `monto`: 203, 32 y 222. Siete veces más según el método 😬\n",
    "\n",
    "Y no es que uno esté mal, es que miden cosas distintas. El z clásico usa la\n",
    "media y la desviación, que **los propios atípicos inflan**, así que\n",
    "se le esconden. El z robusto usa la mediana y la MAD, que no se dejan mover, y\n",
    "por eso encuentra más.\n",
    "\n",
    "La consecuencia práctica es dura de aceptar: **no existe \"cuántos\n",
    "atípicos tiene esta columna\"**. Existe cuántos encuentra el método que\n",
    "elegiste, y ese método hay que decirlo cuando se reporta el número.\n",
    "\n",
    "Fíjate además en las dos que salen en cero por los tres caminos:\n",
    "`descuento` y `satisfaccion`. Cuando los tres métodos\n",
    "coinciden, ahí sí se puede afirmar algo 👍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que ninguna ficha resume"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for c in ('monto', 'precio_unitario', 'monto_final_facturado'):\n",
    "    s = ventas[c]\n",
    "    print(f'{c:22s} negativos {int((s < 0).sum()):4d}   '\n",
    "          f'ceros {int((s == 0).sum()):5d}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veintiún montos negativos. Son las devoluciones del capítulo\n",
    "4, y aparecen aquí otra vez porque el mínimo de la ficha ya\n",
    "lo cantaba: −2497,72.\n",
    "\n",
    "Y `monto_final_facturado` con 1.267 ceros, que es el 42,23% de las\n",
    "ventas. Esa columna se delata sola sin cruzarla con nada:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ventas['monto_final_facturado'].describe().round(2).to_string())\n",
    "print()\n",
    "print('valen exactamente 0:', int((ventas['monto_final_facturado'] == 0).sum()),\n",
    "      f\"({100 * (ventas['monto_final_facturado'] == 0).mean():.2f}%)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El primer cuartil vale 0 y la mediana 191,89. Una columna de dinero donde el\n",
    "25% inferior es exactamente cero no es una columna de dinero: es dos cosas\n",
    "metidas en una, y una de ellas es \"no pasó nada\" 🚩\n",
    "\n",
    "Todavía no sabemos que es una fuga (eso es el capítulo\n",
    "12), pero ya sabemos que hay que preguntar por ella. Y\n",
    "eso lo dio el análisis de una sola columna."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las categóricas, que aquí no dan guerra"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for c in ('ciudad', 'segmento', 'canal', 'categoria'):\n",
    "    cuenta = ventas[c].value_counts(dropna=False)\n",
    "    print(f'{c:12s} niveles {len(cuenta):3d}   '\n",
    "          f'el mas comun {cuenta.index[0]!r} {100 * cuenta.iloc[0] / len(ventas):5.2f}%   '\n",
    "          f'el mas raro {cuenta.index[-1]!r} {100 * cuenta.iloc[-1] / len(ventas):5.2f}%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todas repartidas entre el 15,70% y el 26,70%, y ninguna pasa de seis\n",
    "niveles. Aburrido, y lo digo como un elogio 😌\n",
    "\n",
    "Lo aburrido aquí significa que no hay que hacer nada: sin categorías raras\n",
    "no hay que agrupar niveles, y con seis niveles el one-hot no infla la tabla.\n",
    "\n",
    "Cuando esta tabla sale fea (una categoría con el 95%, o cuarenta niveles con\n",
    "tres filas cada uno) es cuando empiezan las decisiones difíciles, y esas están\n",
    "en el capítulo 5."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La ficha completa, con recomendación\n",
    "\n",
    "Amplía la ficha para que además diga qué hacer con cada\n",
    "columna según su forma."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El logaritmo, a ver si arregla el cohete\n",
    "\n",
    "La receta clásica para una columna sesgada es el\n",
    "logaritmo. Compruébalo con `precio_unitario`, que tenía asimetría\n",
    "6,734."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Cuánto cambia la ficha si quitas los atípicos\n",
    "\n",
    "Antes de borrar un atípico conviene ver cuánto de la\n",
    "columna se lleva por delante."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La misma columna, mirada por trozos\n",
    "\n",
    "Una ficha global puede esconder dos poblaciones. Saca la\n",
    "ficha de `monto` dentro de cada segmento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Los valores repetidos, que delatan cosas\n",
    "\n",
    "Cuando un valor concreto se repite mucho en una columna\n",
    "continua, casi siempre es un relleno o un tope."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Cuánto pesa lo que falta\n",
    "\n",
    "Los nulos ya salieron en el capítulo\n",
    "4. Aquí la pregunta es otra: ¿faltan juntos?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. La ficha como control de calidad automático\n",
    "\n",
    "Convierte todo esto en una función que devuelve avisos, no\n",
    "números. Es lo que se corre cada vez que llega un archivo nuevo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "La prueba de normalidad rechaza tus seis columnas numéricas con p por debajo de 1e-15. ¿Qué haces?\n",
    "\n",
    "a) Mirar la asimetría y la curtosis, que dicen cuánto se desvía\n",
    "\n",
    "b) Transformar las seis con logaritmo para normalizarlas\n",
    "\n",
    "c) Nada: casi ningún modelo del libro pide normalidad\n",
    "\n",
    "d) Quitar los atípicos hasta que la prueba pase"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El número que acaba en una diapositiva"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Te piden el ticket medio para una presentación. Una línea, sale un número redondo, y ese número acaba en una diapositiva.\n",
    "\n",
    "```\n",
    "print(ventas['monto_final_facturado'].mean())\n",
    "\n",
    "# 480.23\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📋 Cuatro números por columna: media contra mediana, coeficiente de\n",
    "variación, asimetría y curtosis. Con eso ya se decide qué escalar y qué\n",
    "transformar.\n",
    "\n",
    "- 🚀 `precio_unitario` tiene asimetría 6,734 y curtosis 60,015. El\n",
    "escalado estándar no arregla eso: mueve la columna de sitio pero no le cambia la\n",
    "forma.\n",
    "\n",
    "- 📏 El CV compara columnas con unidades distintas: 0,498 en\n",
    "`unidades` contra 2,104 en `precio_unitario`.\n",
    "\n",
    "- 🧪 Las seis columnas fallan la prueba de normalidad, y no es cosa del\n",
    "tamaño: los datos normales de verdad la pasan con 30 filas y con 20.000.\n",
    "\n",
    "- 🔬 Donde sí manda el tamaño es al revés: una desviación chica se cuela la\n",
    "mitad de las veces con 50 filas y no se le escapa ninguna con 1.000. La prueba\n",
    "no rechaza de más con muchos datos, rechaza de menos con pocos.\n",
    "\n",
    "- 😬 Los atípicos de `monto` son 203, 32 o 222 según el método. No\n",
    "existe \"cuántos atípicos tiene\", existe cuántos encuentra el que elegiste.\n",
    "\n",
    "- 🚩 `monto_final_facturado` se delata sin cruzarla con nada: su\n",
    "primer cuartil vale 0 y el 42,23% de las filas también.\n",
    "\n",
    "- 💸 Borrar los atípicos de `monto` quita el 6,77% de las filas y\n",
    "mucho más del dinero, porque son justo las ventas grandes.\n",
    "\n",
    "- 😌 Las cuatro categóricas están repartidas entre el 15,70% y el 26,70%, sin\n",
    "niveles raros. Aburrido, y aquí eso es una buena noticia.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No existe cuántos atípicos tiene una columna. Existe cuántos encuentra el método que elegiste."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La asimetría, la curtosis y las pruebas de normalidad que salieron aquí tienen su capítulo propio en el [libro de estadística desde cero](https://missyera.com/guias/estadistica-desde-cero/) 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cada columna por separado ya no tiene secretos. Falta la pregunta que de\n",
    "verdad importa: **¿cuál de ellas tiene que ver con lo que queremos\n",
    "predecir?** 🎯\n",
    "\n",
    "El capítulo 7 cruza cada columna con el objetivo, y no a\n",
    "ojo: con la prueba que corresponde a cada tipo y con el tamaño del efecto al\n",
    "lado, que es lo que separa \"hay diferencia\" de \"la diferencia importa\".\n",
    "\n",
    "Lo que verás allí:\n",
    "\n",
    "- 🧮 Qué prueba toca según el tipo de columna, en una tabla que se puede\n",
    "pegar en la pared.\n",
    "\n",
    "- 📐 Por qué el valor p solo no sirve, y qué se pone al lado.\n",
    "\n",
    "- 🎣 Las columnas que parecen decir algo y no dicen nada, con el ajuste por\n",
    "comparaciones múltiples.\n",
    "\n",
    "- 🪤 Y una columna con doce puntos de diferencia que al modelo no le va a\n",
    "aportar nada, que es la trampa del capítulo 20 vista\n",
    "desde el otro lado."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 6 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/eda-univariado/\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
}
