{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Agrupar clientes sin decirle cómo\n",
    "\n",
    "K-means sobre los clientes de verdad, la silueta que dice que no hay grupos naturales, y detección de anomalías.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 23 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/agrupar-sin-etiqueta/\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": [
    "Veintiún capítulos prediciendo, y todos necesitaban lo mismo: una columna con\n",
    "la respuesta correcta. Este va de cuando no la hay 🧩\n",
    "\n",
    "Y es lo que más me piden después de predecir, con una frase que ya conoces:\n",
    "\"queremos segmentar a los clientes\". Antes de empezar, la pregunta:\n",
    "**¿cómo sabrías si los grupos que encontraste existen de verdad o los\n",
    "inventó el algoritmo?** De eso va casi todo el capítulo 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una fila por cliente, que es donde empieza todo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.cluster import KMeans\n",
    "from sklearn.metrics import silhouette_score\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "v = pd.read_csv(URL).drop_duplicates()\n",
    "v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "v = v[v['monto'] > 0]\n",
    "v['fecha'] = pd.to_datetime(v['fecha'], format='mixed', dayfirst=True, errors='coerce')\n",
    "\n",
    "corte = v['fecha'].max()\n",
    "c = v.groupby('cliente_id').agg(\n",
    "    total=('monto', 'sum'), compras=('monto', 'count'),\n",
    "    ticket=('monto', 'mean'), ultima=('fecha', 'max')).reset_index()\n",
    "c['dias_sin_comprar'] = (corte - c['ultima']).dt.days\n",
    "c = c.dropna(subset=['dias_sin_comprar'])\n",
    "\n",
    "COLS = ['total', 'compras', 'ticket', 'dias_sin_comprar']\n",
    "print('clientes:', len(c))\n",
    "print(c[COLS].describe().round(1).loc[['mean', 'min', 'max']].to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esas cuatro columnas son las de siempre en segmentación: cuánto compra, cuántas\n",
    "veces, de a cuánto, y hace cuánto que no vuelve. Si te suena a las tres letras de\n",
    "RFM, es exactamente eso 💡"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Escalar no es opcional aquí"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El total llega a 19.929 y las compras a 15. Si le das eso a k-means tal cual,\n",
    "la distancia entre dos clientes la decide el total y las otras tres columnas no\n",
    "existen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = c[COLS].to_numpy(float)\n",
    "Xs = StandardScaler().fit_transform(X)\n",
    "\n",
    "sin_escalar = KMeans(n_clusters=4, n_init=10, random_state=42).fit(X)\n",
    "escalado = KMeans(n_clusters=4, n_init=10, random_state=42).fit(Xs)\n",
    "print('tamanos sin escalar:', np.bincount(sin_escalar.labels_))\n",
    "print('tamanos escalado   :', np.bincount(escalado.labels_))\n",
    "print('silueta sin escalar:', round(silhouette_score(Xs, sin_escalar.labels_), 4))\n",
    "print('silueta escalado   :', round(silhouette_score(Xs, escalado.labels_), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin escalar sale un grupo de 48 y otro de 227, y la silueta es casi la mitad.\n",
    "Es el mismo escalado del capítulo 10, y aquí no\n",
    "es una buena práctica: **es la diferencia entre agrupar por cuatro cosas o\n",
    "por una** ⚖️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuántos grupos, y por qué el codo no contesta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora la pregunta de siempre. El método que te van a enseñar se llama el codo:\n",
    "mira cómo baja la inercia (lo apretados que están los grupos) y quédate donde la\n",
    "curva hace una esquina."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for k in range(2, 9):\n",
    "    m = KMeans(n_clusters=k, n_init=10, random_state=42).fit(Xs)\n",
    "    print(f'k={k}  inercia {m.inertia_:9.1f}  silueta {silhouette_score(Xs, m.labels_):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la columna de la inercia: baja, baja y sigue bajando. **No hay\n",
    "codo**, hay una cuesta 🫠\n",
    "\n",
    "Y no es mala suerte: la inercia *siempre* baja al subir k, por\n",
    "construcción. Con un grupo por cliente sería cero. El codo solo aparece cuando\n",
    "los datos de verdad vienen en montoncitos separados, que es lo que pasa en los\n",
    "ejemplos de los tutoriales y casi nunca en una cartera de clientes.\n",
    "\n",
    "La otra columna es más honesta. La **silueta** mide, para cada\n",
    "punto, cuánto más cerca está de su grupo que del grupo vecino, en una escala de\n",
    "-1 a 1. Y aquí dice dos cosas incómodas:\n",
    "\n",
    "- 📉 El máximo está en **k=2** y de ahí para abajo. Cuantos más\n",
    "grupos pides, peor separados están.\n",
    "\n",
    "- 🤏 Y el máximo es **0,2950**, que es bajo. Por encima de 0,5 se\n",
    "habla de grupos razonables. Esto no llega ni a la mitad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estos clientes no vienen en grupos. Segmentarlos es una decisión de negocio, no un descubrimiento del algoritmo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y eso está **bien**, siempre que se diga. Cortar una cartera en\n",
    "cuatro sirve para organizar al equipo comercial aunque la naturaleza no la haya\n",
    "cortado. Lo que no se puede es presentarlo como \"descubrimos cuatro tipos de\n",
    "cliente\", porque no se descubrió nada: se decidió 🧭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los grupos, que es donde está el trabajo de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El algoritmo es una línea. Lo que cuesta es mirarlos y ponerles nombre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c['grupo'] = escalado.labels_\n",
    "print(c.groupby('grupo')[COLS].mean().round(1).to_string())\n",
    "print()\n",
    "print(c['grupo'].value_counts().sort_index().to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora sí se lee, y encima se lee bonito 💰\n",
    "\n",
    "- 👑 **Grupo 0**: compran mucho, seguido y caro, y volvieron hace\n",
    "poco. Son 133 y hay que cuidarlos.\n",
    "\n",
    "- 🥶 **Grupo 1**: poco, poquísimas veces y llevan 367 días sin\n",
    "aparecer. Están perdidos y hay que decidir si se recuperan o se sueltan.\n",
    "\n",
    "- 🌱 **Grupo 2**: el más numeroso, compra seguido pero barato, y\n",
    "está activo. Aquí es donde se sube el ticket.\n",
    "\n",
    "- 💎 **Grupo 3**: ticket de 1.253, el más alto de los cuatro, pero\n",
    "solo 3,6 compras y 301 días sin volver. Compran poco y grande.\n",
    "\n",
    "Fíjate en el 3, que es el que justifica el capítulo entero: **no lo\n",
    "habrías encontrado ordenando por total**, porque en total queda en medio.\n",
    "Solo aparece cuando miras las cuatro columnas a la vez 🔍\n",
    "\n",
    "Y cada uno de esos cuatro párrafos es una acción distinta del equipo\n",
    "comercial. Ese es el entregable, no la tabla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los raros, que es la otra mitad del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Segmentar parte a todos en grupos. Detectar anomalías busca **los que no\n",
    "son de ninguno**, que es una pregunta distinta y muy útil: fraude, errores\n",
    "de carga, el cliente que se comporta raro antes de irse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import IsolationForest\n",
    "\n",
    "iso = IsolationForest(contamination=0.03, random_state=42).fit(Xs)\n",
    "c['raro'] = iso.predict(Xs) == -1\n",
    "print('marcados como raros:', int(c['raro'].sum()))\n",
    "print()\n",
    "print('los raros :', c[c['raro']][COLS].mean().round(1).to_dict())\n",
    "print('el resto  :', c[~c['raro']][COLS].mean().round(1).to_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Diecinueve clientes con casi el doble de ticket que el resto. Y ojo con el\n",
    "`contamination=0.03`, que no es un detalle técnico:\n",
    "**eres tú quien decide qué porcentaje va a salir raro**. El\n",
    "algoritmo no lo descubre, lo obedece.\n",
    "\n",
    "Compáralo con la herramienta más simple de todas, la regla de las tres\n",
    "desviaciones del libro de estadística:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "z = np.abs((c['total'] - c['total'].mean()) / c['total'].std()) > 3\n",
    "print('la regla de la z sobre el total marca:', int(z.sum()))\n",
    "print('de esos, cuantos marca tambien el bosque:', int((z & c['raro']).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los cuatro de la z están dentro de los diecinueve del bosque 🎯\n",
    "\n",
    "Y eso dice exactamente para qué sirve cada uno. La z mira **una\n",
    "columna** y encuentra al que gastó una barbaridad. El bosque mira\n",
    "**las cuatro a la vez** y encuentra además al que tiene una\n",
    "combinación rara sin que ninguna cifra suelta destaque: mucho ticket con pocas\n",
    "compras, por ejemplo.\n",
    "\n",
    "Si te sirve una regla para elegir: empieza siempre por la z, que es una línea\n",
    "y se explica en una reunión. Sube al bosque cuando lo raro sea la combinación y\n",
    "no el número."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## De segmentar a recomendar, que es el mismo truco"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de seguir, una parada corta, porque de acá sale una familia entera de\n",
    "sistemas que seguro usas todos los días.\n",
    "\n",
    "Todo lo de arriba se apoya en una sola idea: **poner cada cliente como\n",
    "una fila de números y medir qué tan cerca están**. Segmentar es agrupar a\n",
    "los que están cerca. Y si en vez de agruparlos preguntas quién está más cerca de\n",
    "uno en concreto, ya tienes un recomendador.\n",
    "\n",
    "Eso es lo que hay detrás de \"clientes como tú también compraron\". No hay\n",
    "magia: se busca a los vecinos y se mira qué compraron ellos que este todavía no.\n",
    "En los libros le dicen **filtrado colaborativo**, y la versión\n",
    "que acabas de aprender a montar es la mitad del trabajo.\n",
    "\n",
    "Las dos advertencias, que son las que hunden estos proyectos:\n",
    "\n",
    "- **El cliente nuevo no tiene vecinos.** Si acaba de llegar y no\n",
    "compró nada, no hay con qué medir su distancia. Es el problema del arranque en\n",
    "frío y se resuelve por fuera: lo más vendido, lo de su zona, preguntarle.\n",
    "\n",
    "- **Recomendar lo parecido encierra.** Si a quien compra arroz\n",
    "siempre le ofreces arroz, nunca descubre el aceite. Un recomendador que solo\n",
    "acierta es un recomendador que no vende nada nuevo, y eso no lo mide el\n",
    "acierto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y el que no te pregunta cuántos grupos quieres"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo anterior lo hizo k-means, y k-means tiene una manía: **le pides\n",
    "tres grupos y te da tres**, haya tres o no haya ninguno. Es obediente, no\n",
    "sincero.\n",
    "\n",
    "DBSCAN funciona al revés. En vez del número de grupos le dices qué tan cerca\n",
    "tienen que estar los clientes para considerarse vecinos, y él decide cuántos\n",
    "grupos salen. Y tiene un premio: **lo que no entra en ninguno lo deja\n",
    "suelto**, o sea que agrupa y detecta raros de una vez."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cluster import DBSCAN\n",
    "\n",
    "d = DBSCAN(eps=0.9, min_samples=8).fit(Xs)\n",
    "c['suelto'] = d.labels_ == -1\n",
    "\n",
    "print('grupos que encuentra:', len(set(d.labels_)) - (1 if -1 in d.labels_ else 0))\n",
    "print('sueltos:', int(c['suelto'].sum()))\n",
    "print('coinciden con los raros del IsolationForest:',\n",
    "      int((c['suelto'] & c['raro']).sum()), 'de', int(c['raro'].sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Un grupo.** Uno solo, con 23 clientes colgando por fuera.\n",
    "\n",
    "Y eso no es que DBSCAN haya fallado: es la respuesta honesta a la pregunta que\n",
    "k-means no contesta. Estos clientes **no forman islas separadas**,\n",
    "forman una nube continua con una cola de gente que gasta mucho más. Cuando le\n",
    "pediste cuatro grupos a k-means, cortó esa nube en cuatro pedazos porque se lo\n",
    "pediste, no porque estuvieran ahí 🔀\n",
    "\n",
    "Lo cual no invalida la segmentación de arriba. Cortar una nube continua en\n",
    "cuatro tramos sirve igual para trabajar: son cuatro cajones útiles para decidir a\n",
    "quién llamas. Lo que cambia es cómo lo cuentas. \"Descubrimos cuatro tipos de\n",
    "cliente\" no es lo que pasó. \"Partimos a los clientes en cuatro tramos según\n",
    "cuánto y cada cuánto compran\" sí, y encima se defiende mejor en una reunión.\n",
    "\n",
    "Y mira la última línea: de los 19 raros que marcó el IsolationForest, DBSCAN\n",
    "coincide en 14. Dos métodos distintos señalando a la misma gente es la mejor\n",
    "señal que vas a tener de que esos clientes son de verdad distintos. Los cinco en\n",
    "los que no coinciden son los que vale la pena mirar a mano 🔍\n",
    "\n",
    "Lo que se paga: el `eps`. Ese 0,9 lo elegiste tú igual que elegiste\n",
    "el número de grupos en k-means, así que la decisión no desapareció, se mudó de\n",
    "sitio. La diferencia es que ahora estás decidiendo qué tan cerca es cerca, que se\n",
    "puede razonar mirando las distancias, en vez de cuántas islas hay, que era\n",
    "adivinar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que sale al pedir un solo grupo"
   ]
  },
  {
   "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",
    "    silhouette_score(Xs, np.zeros(len(Xs), dtype=int))\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: Number of labels is 1. Valid values are 2 to n_samples - 1 (inclusive)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tiene sentido si piensas qué mide la silueta: cuánto más cerca está cada punto\n",
    "de su grupo que *del vecino*. Sin vecino no hay nada que comparar.\n",
    "\n",
    "Y por eso no existe la silueta de k=1, que sería la forma natural de preguntar\n",
    "\"¿de verdad hacen falta grupos?\". Esa pregunta hay que contestarla mirando si el\n",
    "máximo es alto, y aquí no lo era 🧐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Un equipo segmenta la cartera y presenta cuatro perfiles de cliente. Al mes siguiente vuelven a correrlo con los datos nuevos para actualizar los grupos y presentan el resultado igual.\n",
    "\n",
    "```\n",
    "# mes 1\n",
    "grupos_enero = KMeans(n_clusters=4, random_state=42).fit_predict(escalar(enero))\n",
    "# -> grupo 0 = \"los premium\", grupo 1 = \"los perdidos\", ...\n",
    "\n",
    "# mes 2, con los datos nuevos\n",
    "grupos_febrero = KMeans(n_clusters=4, random_state=42).fit_predict(escalar(febrero))\n",
    "\n",
    "# y se comparan los dos meses grupo por grupo\n",
    "crecimiento = grupos_febrero.mean() - grupos_enero.mean()\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "El numero de grupo que devuelve k-means **no significa nada y no es estable**: sale del orden en que arrancaron los centros. El grupo 0 de enero y el grupo 0 de febrero pueden ser perfiles completamente distintos, asi que restarlos no mide crecimiento, mide nada. Y no da error: da un numero que alguien va a poner en una diapositiva. Se arregla de dos maneras y hay que elegir una: o se guarda el modelo de enero y en febrero solo se usa `predict`, sin volver a entrenar, o se vuelve a entrenar y despues se emparejan los grupos comparando sus centros. Lo que nunca se puede hacer es dar por hecho que el numero es el mismo perfil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis, y el 4 es el que separa un análisis de un adorno 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La semilla, y si los grupos aguantan\n",
    "\n",
    "Corre k-means con cinco semillas distintas y mira si salen\n",
    "los mismos grupos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for s in range(5):\n",
    "    m = KMeans(n_clusters=4, n_init=10, random_state=s).fit(Xs)\n",
    "    print(f'semilla {s}: tamanos {np.sort(np.bincount(m.labels_))}  '\n",
    "          f'silueta {silhouette_score(Xs, m.labels_):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si los tamaños ordenados coinciden, la partición es la misma aunque cambien\n",
    "los números de grupo. Si no coinciden, tienes un problema que hay que contar\n",
    "antes de presentar nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Qué columna manda\n",
    "\n",
    "Agrupa usando solo `total` y `compras`,\n",
    "y compara la silueta contra la de las cuatro columnas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Menos columnas casi siempre da silueta más alta, y eso no significa que los\n",
    "grupos sean mejores: significa que en menos dimensiones es más fácil parecer\n",
    "separado. Es el mismo aviso de la maldición de la dimensión, al revés."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El logaritmo antes de agrupar\n",
    "\n",
    "Aplica logaritmo a `total` y `ticket`\n",
    "antes de escalar, y vuelve a mirar la silueta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c2 = c.copy()\n",
    "c2['total'] = np.log1p(c2['total'].clip(lower=0))\n",
    "c2['ticket'] = np.log1p(c2['ticket'].clip(lower=0))\n",
    "Xs2 = StandardScaler().fit_transform(c2[COLS].to_numpy(float))\n",
    "m = KMeans(n_clusters=4, n_init=10, random_state=42).fit(Xs2)\n",
    "print('silueta con logaritmo:', round(silhouette_score(Xs2, m.labels_), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El dinero casi siempre tiene cola larga, y el logaritmo la endereza. Fíjate en\n",
    "el `clip`: hay clientes con total negativo por las notas de crédito, y\n",
    "el logaritmo de un negativo no existe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Ponles nombre y una acción\n",
    "\n",
    "Sin código. Escribe para cada uno de los cuatro grupos una\n",
    "frase con el nombre y una acción concreta del equipo comercial."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es el ejercicio de verdad, y el que casi nadie hace. Una segmentación que\n",
    "no termina en cuatro acciones distintas es una tabla bonita que nadie va a usar.\n",
    "Si a algún grupo no le encuentras acción, esa es la señal de que sobran\n",
    "grupos 🧭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Sube la contaminación\n",
    "\n",
    "Prueba `contamination` en 0,01, 0,05 y 0,10 y\n",
    "cuenta cuántos raros salen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for cont in [0.01, 0.03, 0.05, 0.10]:\n",
    "    iso = IsolationForest(contamination=cont, random_state=42).fit(Xs)\n",
    "    print(f'contamination={cont}: {int((iso.predict(Xs) == -1).sum())} raros')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Salen exactamente los que pediste. Eso es lo que hay que entender: el número\n",
    "de anomalías es un presupuesto que tú fijas, normalmente por cuántos casos puede\n",
    "revisar el equipo a la semana, no por cuántos hay."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Agrupar y después predecir\n",
    "\n",
    "Mete el grupo como columna en el modelo del capítulo\n",
    "11 y mira si el AUC mejora."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A veces sube un poquito y casi nunca compensa. Y hay que tener cuidado con\n",
    "algo: si el grupo se calculó usando datos posteriores a la predicción, acabas de\n",
    "meter fuga de información, que es el capítulo\n",
    "12. Los `dias_sin_comprar` son justo esa\n",
    "clase de columna 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Corres k-means sobre tus clientes, la silueta sale 0,21 y el codo no aparece por ningún lado. ¿Qué haces?\n",
    "\n",
    "a) Segmento igual si sirve al negocio, diciendo que es una decisión y no un hallazgo\n",
    "\n",
    "b) Pruebo más valores de k hasta que la silueta suba\n",
    "\n",
    "c) Concluyo que no se puede segmentar y paro\n",
    "\n",
    "d) Cambio a otro algoritmo hasta que salgan grupos claros\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Vas a encontrar uno más alto por casualidad. Es el buscar hasta encontrar del libro de estadística, con otro disfraz.\n",
    "\n",
    "*c)* Demasiado. Que no haya grupos naturales no quita que partir la cartera sirva para organizar al equipo.\n",
    "\n",
    "*d)* Si los datos no vienen en montoncitos, ningún algoritmo los va a crear. Los va a dibujar, que no es lo mismo.\n",
    "\n",
    "Lo que nunca se hace es presentarlo como"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- ⚖️ Sin escalar, k-means agrupa por la columna más grande y nada más.\n",
    "\n",
    "- 📉 El codo casi nunca aparece, porque la inercia siempre baja. La silueta sí\n",
    "contesta.\n",
    "\n",
    "- 🧭 Aquí la silueta máxima fue 0,2950 en k=2 y baja de ahí. No hay grupos\n",
    "naturales, y decirlo es parte del entregable.\n",
    "\n",
    "- 🔍 El trabajo no es correr el algoritmo: es mirar los grupos, ponerles nombre\n",
    "y sacar una acción de cada uno.\n",
    "\n",
    "- 🚨 En anomalías, tú fijas cuántas salen. La z mira una columna y el bosque\n",
    "mira la combinación.\n",
    "\n",
    "Y si lo que quieres es medir cuánto pesa cada variable en vez de agrupar, eso\n",
    "es el capítulo 22, y con todos sus intervalos está\n",
    "en el [libro de estadística desde\n",
    "cero](https://missyera.com/guias/estadistica-desde-cero/) 📐\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 23 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/agrupar-sin-etiqueta/\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
}
