{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cuánto sube por cada uno\n",
    "\n",
    "Mínimos cuadrados escritos a mano, la pendiente leída en soles y el intervalo que decide si significa algo.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 16 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/regresion-lineal/\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": [
    "Última herramienta del libro, y la que más voy a usar yo en un trabajo de\n",
    "verdad 📏\n",
    "\n",
    "El capítulo 15 terminó diciendo que dos cosas se mueven\n",
    "juntas. Está bien y se queda corto, porque en una reunión nadie pregunta si algo\n",
    "se relaciona: preguntan **cuánto**.\n",
    "\n",
    "Y una pregunta para ti antes de empezar: **¿cuántas veces has dicho \"sí\n",
    "influye\" sin poder decir cuánto ni con qué margen?** Este capítulo es para\n",
    "no tener que volver a hacerlo 🎯\n",
    "\n",
    "La regresión contesta eso con un número que se lee en soles. Vamos a\n",
    "escribirla nosotras, que son dos líneas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La recta que menos se equivoca"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La idea es la que ya te imaginas: pasar una recta por la nube de puntos. Lo\n",
    "que hay que decidir es *cuál* recta, porque caben infinitas.\n",
    "\n",
    "El criterio se llama **mínimos cuadrados** y es este: de todas\n",
    "las rectas posibles, quédate con la que hace más chica la suma de los errores al\n",
    "cuadrado. Al cuadrado por dos razones, y las dos importan: así los errores por\n",
    "arriba y por abajo no se cancelan, y así un error grande pesa mucho más que dos\n",
    "chicos."
   ]
  },
  {
   "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",
    "    return v\n",
    "\n",
    "v = carga_limpia(URL)\n",
    "v = v[v['monto'] > 0]\n",
    "x = v['unidades'].to_numpy(float)\n",
    "y = v['monto'].to_numpy(float)\n",
    "print('filas:', len(v))\n",
    "print('correlacion entre unidades y monto:', round(float(np.corrcoef(x, y)[0, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Empezamos por la pregunta más obvia de todas: **¿cuánto sube la venta\n",
    "por cada unidad de más que se lleva el cliente?**\n",
    "\n",
    "Y ya vemos algo: la correlación es 0,0125, o sea prácticamente cero. Guárdalo,\n",
    "que en un momento nos va a servir 🔖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Escrita a mano, en dos líneas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pendiente = ((x - x.mean()) * (y - y.mean())).sum() / ((x - x.mean()) ** 2).sum()\n",
    "intercepto = y.mean() - pendiente * x.mean()\n",
    "print(f'pendiente  : {pendiente:.4f}')\n",
    "print(f'intercepto : {intercepto:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es una regresión lineal entera. En serio, no hay más 😌\n",
    "\n",
    "La primera línea se lee bien si la miras por partes. Arriba, cuánto se mueven\n",
    "`x` e `y` *juntas* respecto de sus promedios. Abajo,\n",
    "cuánto se mueve `x` sola. La división es \"de todo lo que se mueve x,\n",
    "qué parte arrastra a y\".\n",
    "\n",
    "Y el intercepto sale de una condición bonita: **la recta siempre pasa\n",
    "por el punto de los dos promedios**. Sabiendo la pendiente y ese punto, la\n",
    "recta queda fija.\n",
    "\n",
    "Comprobemos contra la librería, que es como se comprueba todo en este\n",
    "libro:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r = stats.linregress(x, y)\n",
    "print(f'scipy dice : pendiente {r.slope:.4f}   intercepto {r.intercept:.4f}')\n",
    "print(f'diferencia : {abs(r.slope - pendiente):.2e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Idénticas hasta la quinceava cifra 🎉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cómo se lee una pendiente"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí está lo que hace que esto valga la pena, y es una sola frase que conviene\n",
    "aprenderse:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pendiente es cuánto cambia y cuando x sube en uno. Y viene con las unidades puestas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nuestra pendiente es 1,6175, y las unidades son soles por unidad. O sea:\n",
    "**por cada unidad más que se lleva el cliente, la venta sube 1,62\n",
    "soles**.\n",
    "\n",
    "Y el intercepto, 793,64, es lo que predice la recta para un cliente que se\n",
    "lleva cero unidades. Que no significa nada aquí, porque nadie compra cero, y es\n",
    "justo el aviso: **el intercepto solo se interpreta si el cero está dentro\n",
    "del rango de tus datos**.\n",
    "\n",
    "Ahora bien. 1,62 soles por unidad suena razonable, se puede poner en una\n",
    "diapositiva y alguien lo va a repetir en una reunión 😬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El número que decide si eso significa algo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pendiente sale siempre. Le das cualquier par de columnas y te devuelve un\n",
    "número, sin quejarse. Lo que hay que preguntarle es **con cuánta\n",
    "precisión**, y para eso está su intervalo, igual que en el capítulo\n",
    "10."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ic = 1.96 * r.stderr\n",
    "print(f'pendiente        : {r.slope:.4f} soles por unidad')\n",
    "print(f'error estandar   : {r.stderr:.4f}')\n",
    "print(f'intervalo del 95%: [{r.slope - ic:.4f}, {r.slope + ic:.4f}]')\n",
    "print(f'valor p          : {r.pvalue:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira el intervalo: **de -3,02 a 6,25** 😳\n",
    "\n",
    "Cruza el cero de lado a lado. O sea que con estos datos no podemos descartar\n",
    "que la pendiente de verdad sea negativa, ni que sea cero, ni que sea 6. El valor\n",
    "p de 0,4941 dice lo mismo con otro formato.\n",
    "\n",
    "Traducido al idioma de la reunión: **llevarse más unidades no está\n",
    "relacionado con gastar más** en esta distribuidora. Y tiene sentido si lo\n",
    "piensas, porque una cosa es cuántas cajas te llevas y otra es de qué producto.\n",
    "\n",
    "Quédate con esto, que es la lección del capítulo entero: **el número\n",
    "siempre sale, y decidir si significa algo es el trabajo** 🧐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Entonces, ¿dónde está la venta?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La regresión sirve para variables de categoría, no solo para números, y ahí\n",
    "es donde estos datos sí tienen algo que contar. Metemos el segmento:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import statsmodels.formula.api as smf\n",
    "\n",
    "modelo = smf.ols('monto ~ C(segmento)', data=v).fit()\n",
    "print(f'R2 = {modelo.rsquared:.4f}')\n",
    "for nombre, coef in modelo.params.items():\n",
    "    bajo, alto = modelo.conf_int().loc[nombre]\n",
    "    print(f'{nombre:26} {coef:9.2f}   IC 95% [{bajo:8.2f}, {alto:8.2f}]')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora sí 💰\n",
    "\n",
    "Se lee así, y es más fácil de lo que parece. El `Intercept` de\n",
    "181,65 es la **categoría que falta**, que es Bodega: una bodega\n",
    "compra 181,65 soles de promedio. Todo lo demás se mide *contra* ella.\n",
    "\n",
    "O sea que un mayorista compra **1.693,75 soles más que una bodega**,\n",
    "y su intervalo va de 1.655,50 a 1.731,99, que no se acerca al cero ni de lejos.\n",
    "Eso sí se puede llevar a una reunión.\n",
    "\n",
    "Y el **R cuadrado** de 0,7530 dice qué parte de la variación del\n",
    "monto queda explicada: el 75,3%. Compáralo con lo de antes, donde con unidades\n",
    "salía 0,0002, y ahí tienes de un vistazo cuál de las dos preguntas era la\n",
    "buena 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué existe la regresión, de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo que hicimos hasta aquí se podía hacer comparando grupos. La razón de\n",
    "ser de la regresión es otra: **mirar varias variables a la vez y decir\n",
    "cuánto aporta cada una con las demás fijas**.\n",
    "\n",
    "Añadamos las unidades al modelo del segmento:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "completo = smf.ols('monto ~ C(segmento) + unidades', data=v).fit()\n",
    "print(f'R2 solo con segmento     : {modelo.rsquared:.4f}')\n",
    "print(f'R2 anadiendo unidades    : {completo.rsquared:.4f}')\n",
    "print(f'coeficiente de unidades  : {completo.params[\"unidades\"]:.4f}')\n",
    "print(f'su valor p               : {completo.pvalues[\"unidades\"]:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El R cuadrado no se movió y el coeficiente de unidades se desplomó de 1,6175\n",
    "a 0,0365, con un p de 0,9752 😅\n",
    "\n",
    "Esa es **la lectura correcta**: sabiendo el segmento, las\n",
    "unidades no aportan absolutamente nada. Y es lo que ninguna comparación de grupos\n",
    "te puede decir, porque para saberlo hay que mirar las dos a la vez.\n",
    "\n",
    "Este es el mecanismo que hay detrás de la frase \"controlando por\". Cuando\n",
    "alguien dice \"controlando por el tamaño de la empresa\", está diciendo\n",
    "exactamente esto: metí esa variable en el modelo y miro qué queda del resto.\n",
    "\n",
    "Y también es el aviso de siempre, el del capítulo\n",
    "17: **un coeficiente no es una causa**.\n",
    "Que ser mayorista pese 1.693,75 no significa que convertir una bodega en\n",
    "mayorista le vaya a subir la venta en 1.693,75. Significa que los mayoristas\n",
    "compran más, que ya lo sabíamos, ahora con número e intervalo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los residuos, o dónde se ve si la recta sirve"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un residuo es lo que le sobra a la predicción: lo real menos lo predicho. Y\n",
    "son la parte del diagnóstico que casi nadie mira 🔍"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pred = modelo.fittedvalues\n",
    "res = modelo.resid\n",
    "print(f'media de los residuos: {res.mean():.6f}')\n",
    "tercio = pd.qcut(pred, 3, labels=['bajo', 'medio', 'alto'])\n",
    "print('desviacion del residuo por tercio de prediccion:')\n",
    "print(res.groupby(tercio, observed=True).std().round(1).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La media es cero, y eso no es una buena noticia: **siempre da cero**,\n",
    "por construcción de los mínimos cuadrados. No comprueba nada.\n",
    "\n",
    "Lo que sí dice algo es la tabla de abajo, y dice bastante: el error típico\n",
    "pasa de 109 soles donde la predicción es baja a **676 donde es alta**.\n",
    "Seis veces más 😳\n",
    "\n",
    "Eso se llama **heterocedasticidad**, que es una palabra horrible\n",
    "para una idea simple: el modelo se equivoca mucho más en unos sitios que en\n",
    "otros. Aquí es evidente y tiene explicación de negocio: una bodega compra entre\n",
    "100 y 300 soles y un mayorista entre 500 y 4.000, así que hay muchísimo más\n",
    "espacio para fallar arriba.\n",
    "\n",
    "La consecuencia práctica es concreta: **los intervalos de arriba están\n",
    "mal calculados**. Suponen que el error es parejo y no lo es. Se arreglan\n",
    "pidiendo errores robustos, que es una palabra de más en la misma línea:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "# los mismos coeficientes, pero con intervalos que aguantan\n",
    "robusto = smf.ols('monto ~ C(segmento)', data=v).fit(cov_type='HC3')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los coeficientes no cambian, solo los intervalos. Y aquí los coeficientes\n",
    "están tan lejos del cero que la conclusión aguanta igual, pero eso hay que\n",
    "comprobarlo y no suponerlo 🧯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los dos errores que sí dan error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El primero no da ninguno, que es peor:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('con la columna descuento, que trae nulos:')\n",
    "print(stats.linregress(v['descuento'], v['monto']).slope)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`nan`. Ni un aviso. Un solo nulo en la columna y la pendiente\n",
    "entera se vuelve nada, y si eso pasa dentro de un informe automático el número\n",
    "sale en blanco y nadie sabe por qué 🫠\n",
    "\n",
    "El segundo sí revienta, y menos mal:"
   ]
  },
  {
   "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",
    "    iguales = [5.0] * 20\n",
    "    stats.linregress(iguales, y[:20])\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: Cannot calculate a linear regression if all x values are identical\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*if all x values are identical*. Si `x` no varía, la\n",
    "división de la primera línea que escribimos a mano tiene un cero abajo.\n",
    "\n",
    "Y esa es la condición de fondo de toda la regresión, dicha por la librería:\n",
    "**sin variación en x no hay nada que medir**. Si todos tus clientes\n",
    "compran lo mismo, ninguna herramienta te va a decir qué pasa cuando compran\n",
    "distinto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo NO usarla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Si pasa esto | Qué hacer |\n",
    "|---|---|\n",
    "| La nube no es una recta, es una curva | Transformar (el logaritmo suele bastar) o usar otra herramienta. La recta va a dar un número igual, y va a estar mal |\n",
    "| Lo que quieres predecir es sí o no | Regresión logística, que está en el libro de machine learning |\n",
    "| Las filas no son independientes | Es el problema del capítulo 17. Varias compras del mismo cliente no son varios clientes |\n",
    "| Hay atípicos fuertes | Mira primero el capítulo 6. Un punto lejano mueve la recta entera, porque el error va al cuadrado |\n",
    "| Quieres afirmar una causa | Ninguna regresión demuestra causalidad. Hace falta un experimento o las herramientas de inferencia causal |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una más, que no cabe en la tabla y es la que más veo: **meter en el\n",
    "modelo una variable que se calcula con la respuesta**. Eso es la trampa de\n",
    "este capítulo 👇"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Alguien quiere explicar el monto de la venta y añade el precio unitario, que parece razonable porque es un dato del pedido. El R cuadrado se dispara y el valor p sale astronómico.\n",
    "\n",
    "```\n",
    "v['precio_unitario'] = v['monto'] / v['unidades']\n",
    "trampa = smf.ols('monto ~ precio_unitario', data=v).fit()\n",
    "print(f'R2 = {trampa.rsquared:.4f}')\n",
    "print(f'valor p = {trampa.pvalues[\"precio_unitario\"]:.3g}')\n",
    "\n",
    "# R2 = 0.1944   valor p = 5.54e-142\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Mira la primera línea: `precio_unitario` se calcula **dividiendo el monto**, que es justo lo que queremos explicar. Estamos usando la respuesta para predecir la respuesta, y por eso el valor p sale en 5,54e-142, que es un número absurdo hasta para una relación de verdad. No es un hallazgo: es una identidad matemática disfrazada de modelo. La regla, y no tiene excepciones: **antes de meter una variable, pregúntate si se conocía ANTES de que pasara lo que estás explicando**. El precio unitario no existe hasta que la venta ocurrió. Esto en el libro de machine learning tiene nombre propio, fuga de información, y es el capítulo entero 12 de allá."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis, y el 5 es el que más enseña. Intenta antes de abrir 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La recta al revés\n",
    "\n",
    "Calcula la regresión de `unidades` sobre\n",
    "`monto`, o sea al revés que arriba, y compara las dos pendientes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "al_derecho = stats.linregress(x, y).slope\n",
    "al_reves = stats.linregress(y, x).slope\n",
    "print(f'monto sobre unidades : {al_derecho:.4f}')\n",
    "print(f'unidades sobre monto : {al_reves:.6f}')\n",
    "print(f'una es la inversa de la otra?: {1 / al_derecho:.6f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No lo son, y esa es la gracia. La regresión no es simétrica: minimizar el\n",
    "error vertical no es lo mismo que minimizar el horizontal. Por eso hay que tener\n",
    "clarísimo cuál variable es la que explicas y cuál la que usas 🔄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Del R cuadrado a la correlación\n",
    "\n",
    "Comprueba que en una regresión de una sola variable el R\n",
    "cuadrado es exactamente la correlación al cuadrado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r = stats.linregress(x, y)\n",
    "print(f'correlacion al cuadrado : {r.rvalue ** 2:.8f}')\n",
    "print(f'R cuadrado              : {r.rvalue ** 2:.8f}')\n",
    "print(f'correlacion             : {r.rvalue:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Salen iguales, y por eso el nombre lleva una R. Ojo con una cosa: esto solo\n",
    "vale con una variable. En cuanto metes dos, el R cuadrado ya no es la\n",
    "correlación de nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Qué le hace un atípico\n",
    "\n",
    "Añade una sola fila inventada con 500 unidades y 50.000\n",
    "soles, y vuelve a calcular la pendiente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x2 = np.append(x, 500.0)\n",
    "y2 = np.append(y, 50000.0)\n",
    "print(f'pendiente con 2.979 filas : {stats.linregress(x, y).slope:.4f}')\n",
    "print(f'pendiente con una mas     : {stats.linregress(x2, y2).slope:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una fila de 2.980 mueve la pendiente entera. Es la consecuencia directa de\n",
    "elevar el error al cuadrado: un punto lejano pesa muchísimo más que cien\n",
    "cercanos. Por eso el capítulo 6 va antes que este."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El segmento, comparado a mano\n",
    "\n",
    "Comprueba que el coeficiente del mayorista es exactamente\n",
    "la diferencia de promedios contra la bodega."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "medias = v.groupby('segmento')['monto'].mean()\n",
    "print(medias.round(2).to_string())\n",
    "print()\n",
    "print(f'mayorista menos bodega: {medias[\"Mayorista\"] - medias[\"Bodega\"]:.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Da 1.693,75, el mismo coeficiente. Con una sola variable de categoría, la\n",
    "regresión *es* comparar promedios, escrito de otra forma. Lo que añade es\n",
    "el intervalo y la posibilidad de meter más variables."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. El logaritmo, que arregla la mitad de los casos\n",
    "\n",
    "Ajusta el modelo del segmento sobre el logaritmo del monto\n",
    "y vuelve a mirar los residuos por tercio."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v2 = v.copy()\n",
    "v2['log_monto'] = np.log(v2['monto'])\n",
    "m = smf.ols('log_monto ~ C(segmento)', data=v2).fit()\n",
    "tercio = pd.qcut(m.fittedvalues, 3, labels=['bajo', 'medio', 'alto'])\n",
    "print(m.resid.groupby(tercio, observed=True).std().round(3).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las tres desviaciones se parecen muchísimo más que las de 109, 262 y 676 de\n",
    "arriba. Eso es lo que hace el logaritmo: convierte un error que crece con el\n",
    "nivel en uno parejo.\n",
    "\n",
    "El precio es que los coeficientes ya no se leen en soles, se leen en\n",
    "porcentaje: un coeficiente de 0,7 significa aproximadamente un 70% más. Es un\n",
    "cambio de unidades y hay que decirlo en voz alta cuando lo presentas 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Predecir fuera del rango\n",
    "\n",
    "Usa la recta de `monto ~ unidades` para predecir\n",
    "la venta de un cliente que se lleva 10.000 unidades."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f'prediccion para 10.000 unidades: {r.intercept + r.slope * 10000:.2f} soles')\n",
    "print(f'unidades que hay en los datos  : de {x.min():.0f} a {x.max():.0f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El número sale, y es basura. Los datos llegan hasta un máximo mucho menor, así\n",
    "que ahí la recta no está apoyada en nada: está inventando. Eso se llama\n",
    "**extrapolar** y es de los errores más caros, porque el resultado\n",
    "parece igual de serio que los otros 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Tres regresiones de una sola pasada\n",
    "\n",
    "Ajusta el monto contra cada una de las tres numéricas, una por una, y pon la pendiente al lado de su p y de su r cuadrado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['unidades', 'descuento', 'satisfaccion']:\n",
    "    t = v[[col, 'monto']].dropna()\n",
    "    r2 = stats.linregress(t[col], t['monto'])\n",
    "    print('monto ~ %-13s pendiente %9.2f   p = %.4f   r2 = %.4f'\n",
    "          % (col, r2.slope, r2.pvalue, r2.rvalue ** 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "monto ~ unidades      pendiente      1.62   p = 0.4941   r2 = 0.0002\n",
    "monto ~ descuento     pendiente   -288.53   p = 0.1748   r2 = 0.0008\n",
    "monto ~ satisfaccion  pendiente     -6.71   p = 0.4996   r2 = 0.0002\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la de descuento: menos 288 soles por cada punto 😱\n",
    "\n",
    "Es la pendiente más grande de las tres y es la más fácil de contar en una reunión. Pero su p es 0,17 y su r cuadrado es 0,0008, que quiere decir que la recta explica menos del uno por mil de lo que pasa.\n",
    "\n",
    "**Una pendiente grande no es una relación fuerte.** La pendiente te dice en qué unidades se mueve, no si se mueve de verdad. Por eso las tres columnas van juntas y nunca sueltas 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Lo que aparece cuando entra el segmento\n",
    "\n",
    "Ajusta el monto contra unidades sola y después contra unidades más segmento, y compara los dos r cuadrado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "solo = smf.ols('monto ~ unidades', data=v).fit()\n",
    "con_seg = smf.ols('monto ~ unidades + segmento', data=v).fit()\n",
    "print('sin el segmento: r2 = %.4f   pendiente de unidades = %8.2f'\n",
    "      % (solo.rsquared, solo.params['unidades']))\n",
    "print('con el segmento: r2 = %.4f   pendiente de unidades = %8.2f'\n",
    "      % (con_seg.rsquared, con_seg.params['unidades']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sin el segmento: r2 = 0.0002   pendiente de unidades =     1.62\n",
    "con el segmento: r2 = 0.7530   pendiente de unidades =     0.04\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 0,0002 a 0,7530 al meter una sola columna 🚀\n",
    "\n",
    "El modelo pasa de no explicar nada a explicar tres cuartas partes del monto, y lo hace sin tocar los datos: solo le dije que mirara el segmento. Y de paso la pendiente de unidades se cae de 1,62 a 0,04.\n",
    "\n",
    "**Que una columna no explique nada sola no significa que el modelo no pueda explicar.** Significa que estabas mirando la columna equivocada, y en estos datos la que manda es la misma de siempre, la que ya separaba diez veces en 1 🧩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Corres una regresión y el coeficiente del descuento sale en -288, con un intervalo del 95% que va de -706 a 129. ¿Qué reportas?\n",
    "\n",
    "a) Que con estos datos no se puede afirmar que el descuento mueva la venta\n",
    "\n",
    "b) Que el descuento baja la venta en 288 soles\n",
    "\n",
    "c) Que el descuento no tiene ningún efecto\n",
    "\n",
    "d) Que hace falta más muestra\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Ese es el valor más probable, cierto. Pero mira dónde termina el intervalo por el lado derecho.\n",
    "\n",
    "*c)* Cuidado, que no es lo mismo. El intervalo también incluye -700, o sea un efecto enorme. No poder afirmarlo no es lo mismo que descartarlo.\n",
    "\n",
    "*d)* Puede ser verdad y no es lo que se reporta. Primero se dice qué se sabe hoy y con qué margen.\n",
    "\n",
    "Y si el que pregunta insiste, el número honesto es el intervalo entero, no su punto medio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📏 La pendiente es cuánto cambia y cuando x sube en uno, con unidades\n",
    "puestas.\n",
    "\n",
    "- 🧐 Sale siempre. Lo que decide si significa algo es su intervalo, y el\n",
    "nuestro iba de -3,02 a 6,25.\n",
    "\n",
    "- 💰 Con variables de categoría, el intercepto es la que falta y el resto se\n",
    "mide contra ella.\n",
    "\n",
    "- 🔗 Meter más variables sirve para saber qué aporta cada una con las demás\n",
    "fijas, que es lo que quiere decir \"controlando por\".\n",
    "\n",
    "- 🔍 Los residuos por tramo son el diagnóstico. Los nuestros pasan de 109 a\n",
    "676 soles.\n",
    "\n",
    "- 🚫 Y ninguna regresión demuestra una causa. Ninguna.\n",
    "\n",
    "Si quieres la versión que predice en vez de la que explica, con validación y\n",
    "métricas, eso es el\n",
    "[libro de machine learning desde\n",
    "cero](https://missyera.com/guias/machine-learning-desde-cero/) 🤖\n",
    "\n",
    "Y si el pandas de este capítulo te costó más que la estadística, eso se\n",
    "arregla en el [libro de Python desde\n",
    "cero](https://missyera.com/guias/python-desde-cero/) 🐍\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es una regresión lineal?La recta que mejor pasa entre tus puntos, y con ella una forma de decir cuánto cambia una cosa cuando cambia la otra.\n",
    "\n",
    "¿Cómo se interpreta la pendiente de una regresión?Es cuánto sube la variable que predices por cada unidad que sube la otra. Si la pendiente es 1,6 soles por unidad, cada unidad más son 1,6 soles más de venta.\n",
    "\n",
    "¿Qué es el método de mínimos cuadrados?La regla que elige la recta: la que hace más chica la suma de los errores al cuadrado. Se elevan al cuadrado para que un error hacia arriba no cancele a uno hacia abajo.\n",
    "\n",
    "¿Qué significa el R cuadrado?Qué parte de la variación logra explicar la recta. Un R cuadrado alto no significa que el modelo sirva para predecir, y esa confusión es de las que más caro salen."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 16 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/regresion-lineal/\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
}
