{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dos negocios con el mismo promedio\n",
    "\n",
    "Desviación estándar, rango intercuartílico y coeficiente de variación. Y el ddof que cambia el número sin que lo pidas.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 4 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/dispersion/\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": [
    "Dos vendedores. Los dos venden 500 soles de promedio 💰\n",
    "\n",
    "Uno vende 500 todos los días como un reloj. El otro vende 50 un día y 950 al\n",
    "siguiente. El promedio no los distingue, y sin embargo son dos personas\n",
    "completamente distintas: al segundo no puedes prometerle a nadie que mañana\n",
    "factura 500.\n",
    "\n",
    "Eso es la dispersión, y por eso **un promedio sin su dispersión al lado\n",
    "es medio dato**."
   ]
  },
  {
   "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",
    "\n",
    "reloj = pd.Series([500, 510, 495, 505, 490, 500, 505, 495])\n",
    "montania = pd.Series([100, 900, 50, 950, 200, 800, 150, 850])\n",
    "\n",
    "print('el reloj:    media %.1f | desviacion %.2f' % (reloj.mean(), reloj.std()))\n",
    "print('la montaña:  media %.1f | desviacion %.2f' % (montania.mean(), montania.std()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mismo promedio, desviaciones de 6,55 y 405,32. Ahí sí se ven dos negocios\n",
    "distintos 👀\n",
    "\n",
    "Y una pregunta para que la lleves puesta: **¿tu negocio es más reloj o más montaña rusa?** Los dos pueden tener el mismo promedio, y se planifican de forma completamente distinta 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las cuatro medidas, de la peor a la mejor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**El rango** es lo primero que se le ocurre a cualquiera: el\n",
    "máximo menos el mínimo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('rango: %.2f  (de %.2f a %.2f)'\n",
    "      % (v['monto'].max() - v['monto'].min(), v['monto'].min(), v['monto'].max()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ya se ve el problema: ese número lo deciden **dos filas** de\n",
    "tres mil. Las otras 2.998 no opinan. Además una de las dos es una venta\n",
    "negativa, que ya sabemos que es rara y que miramos en el capítulo 6.\n",
    "\n",
    "**La varianza** es la media de las distancias al cuadrado. Se\n",
    "elevan al cuadrado para que las que están por debajo no cancelen a las que están\n",
    "por encima:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "s2=1n−1∑i=1n(xi−x¯)2\n",
    "\n",
    "promedias las distancias al cuadrado de cada dato al centro, dividiendo entre n menos uno"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('varianza:  %.2f' % v['monto'].var())\n",
    "print('desviacion: %.2f' % v['monto'].std())\n",
    "print('la desviacion es la raiz de la varianza:',\n",
    "      round(np.sqrt(v['monto'].var()), 2) == round(v['monto'].std(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí está por qué la varianza casi nunca se reporta: **sus unidades\n",
    "son soles al cuadrado**. 565.487 soles cuadrados no significa nada para\n",
    "nadie 🤷 La desviación estándar deshace el cuadrado con la raíz y vuelve a\n",
    "soles, que sí se entienden: *\"las ventas se separan del promedio unos 752\n",
    "soles\"*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El ddof, o por qué numpy y pandas no coinciden"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto pilla a todo el mundo una vez, y luego ya no se olvida:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('pandas: %.6f' % v['monto'].std())\n",
    "print('numpy:  %.6f' % np.std(v['monto']))\n",
    "print('diferencia: %.6f' % (v['monto'].std() - np.std(v['monto'])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos calculan \"la desviación estándar\" de la misma columna y dan números\n",
    "distintos 😑\n",
    "\n",
    "La razón es el **ddof**, que decide si divides entre n o entre\n",
    "n-1. pandas divide entre n-1 por defecto y numpy entre n.\n",
    "\n",
    "Y no es un capricho. Se divide entre n-1 cuando tus datos son una\n",
    "**muestra** y quieres estimar la dispersión de la población entera.\n",
    "Una muestra siempre parece un poquito menos dispersa que la población de la que\n",
    "salió, así que se corrige haciendo el divisor más chico. Se divide entre n\n",
    "cuando tus datos **son** toda la población y no estimas nada.\n",
    "\n",
    "Con 3.000 filas la diferencia es de 12 céntimos y da igual. Pero mira lo que\n",
    "pasa con pocos datos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tres = pd.Series([9, 10, 11])\n",
    "print('con 3 datos -> pandas %.4f | numpy %.4f' % (tres.std(), np.std(tres)))\n",
    "print('diferencia relativa: %.1f%%'\n",
    "      % (100 * (tres.std() - np.std(tres)) / np.std(tres)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**22,5% de diferencia** por un parámetro que no pusiste 😳\n",
    "\n",
    "Si trabajas con grupos chicos, encuestas de 10 personas o resultados de 5\n",
    "sucursales, esto sí te cambia el número que llevas a la reunión. La regla\n",
    "práctica: en análisis de datos casi siempre tienes una muestra, así que\n",
    "**casi siempre quieres n-1**, o sea el de pandas. Si usas numpy,\n",
    "pídelo a mano con `np.std(x, ddof=1)`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El rango intercuartílico, que es el hermano robusto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Igual que la mediana era la versión robusta de la media, el\n",
    "**IQR** es la versión robusta de la desviación. Se queda con el 50%\n",
    "central y descarta las dos puntas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q1 = v['monto'].quantile(0.25)\n",
    "q3 = v['monto'].quantile(0.75)\n",
    "\n",
    "print('Q1 (25%%): %.2f' % q1)\n",
    "print('Q3 (75%%): %.2f' % q3)\n",
    "print('IQR:      %.2f' % (q3 - q1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se lee así: **el 50% de las ventas está entre 252,69 y 1.068,68\n",
    "soles**. Y eso, para contárselo a alguien que no sabe estadística, es\n",
    "muchísimo más claro que \"la desviación estándar es 752\" 🗣️\n",
    "\n",
    "Ojo con la escala de los cuantiles, que se equivoca todo el mundo:"
   ]
  },
  {
   "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.5)\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, no de 0 a 100. El 75% es\n",
    "`0.75`. En numpy, en cambio, `np.percentile` va de 0 a 100.\n",
    "Es de las incoherencias más molestas que hay entre las dos librerías 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El coeficiente de variación, y lo que encontró"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Última medida, y es la que más uso cuando comparo cosas distintas.\n",
    "\n",
    "Problema: ¿varían más las ventas de Bodega o las de Mayorista? La desviación\n",
    "no te sirve para contestar, porque una vende a 178 soles y la otra a 1.856. La\n",
    "grande va a tener una desviación más grande siempre, solo por ser grande.\n",
    "\n",
    "El **coeficiente de variación** arregla eso dividiendo la\n",
    "desviación entre la media. Queda un número sin unidades, o sea comparable:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "CV=sx¯\n",
    "\n",
    "la dispersión medida en veces la media, que sale sin unidades y por eso se puede comparar entre cosas distintas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_segmento = v.groupby('segmento')['monto'].agg(['mean', 'std'])\n",
    "por_segmento['cv'] = por_segmento['std'] / por_segmento['mean']\n",
    "\n",
    "print(por_segmento.round(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto me pareció precioso cuando salió 😍\n",
    "\n",
    "Las desviaciones van de **69,59 a 719,55**, o sea que una es diez\n",
    "veces la otra. Y los coeficientes de variación van de\n",
    "**0,358 a 0,389**: prácticamente el mismo número en los cuatro.\n",
    "\n",
    "Lo que eso dice del negocio es concreto y bonito: **los cuatro\n",
    "segmentos se comportan igual, solo que a escalas distintas**. Todos\n",
    "tienen ventas que se desvían alrededor de un 37% de su propio promedio. Un\n",
    "mayorista es una bodega multiplicada por diez, no un animal diferente.\n",
    "\n",
    "Y encaja con lo del capítulo 3, donde vimos que dentro de cada segmento la\n",
    "media y la mediana coincidían. Cuatro poblaciones con la misma forma y distinto\n",
    "tamaño 🪆"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La regla del 68, 95 y 99 (y cuándo falla)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seguro la has oído: en una distribución normal, el 68% de los datos cae a una\n",
    "desviación del promedio, el 95% a dos y el 99,7% a tres.\n",
    "\n",
    "Vamos a comprobarlo en tres columnas de este archivo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['monto', 'unidades', 'satisfaccion']:\n",
    "    s = v[col].dropna()\n",
    "    dentro = 100 * (abs(s - s.mean()) < s.std()).mean()\n",
    "    print('%-13s dentro de 1 desviacion: %.2f%%' % (col, dentro))\n",
    "\n",
    "print('lo que dice la teoria para una normal:  68.27%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres columnas del mismo archivo y tres respuestas: 81,80%, 68,70% y 59,44%.\n",
    "La teoría dice 68,27% 🎯\n",
    "\n",
    "- 📦 `unidades` da 68,70%, que es clavado. Esa columna sí tiene\n",
    "forma de campana.\n",
    "\n",
    "- 💰 `monto` da 81,80%, muy por encima. Se concentra más de lo que\n",
    "debería cerca del centro y tiene cola.\n",
    "\n",
    "- ⭐ `satisfaccion` da 59,44%, muy por debajo. Y tiene sentido:\n",
    "son cinco valores repartidos casi por igual, no una campana.\n",
    "\n",
    "**La regla del 68-95-99 solo vale si tus datos son normales**, y\n",
    "la mayoría no lo son. Aplicarla sin comprobar es de los errores más comunes que\n",
    "existen, y por eso el capítulo 5 va entero de eso: cómo saber si tienes una\n",
    "campana antes de usar las herramientas de la campana.\n",
    "\n",
    "Mientras tanto hay una regla que vale *siempre*, pase lo que pase con\n",
    "la forma. Se llama desigualdad de Chebyshev y dice que al menos el 75% de los\n",
    "datos está a menos de 2 desviaciones, y al menos el 88,9% a menos de 3:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m, s = v['monto'].mean(), v['monto'].std()\n",
    "\n",
    "for k, cota in [(2, 75.0), (3, 88.89)]:\n",
    "    dentro = 100 * (abs(v['monto'] - m) < k * s).mean()\n",
    "    print('a menos de %d desviaciones: %.2f%%  (Chebyshev garantiza %.2f%%)'\n",
    "          % (k, dentro, cota))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se cumple con holgura, como tenía que ser. Chebyshev es una garantía mínima,\n",
    "floja pero universal: no hay ninguna distribución en el mundo que la rompa 🛡️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, que salen justo con el rango intercuartílico"
   ]
  },
  {
   "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",
    "    np.percentile(m, [25, 110])\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 must be in the range [0, 100]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un percentil va de 0 a 100 y no hay un 110. Suena obvio hasta que mezclas las dos formas de escribirlo: `np.percentile` quiere 25 y `pandas.quantile` quiere 0,25. Las dos calculan lo mismo y se piden distinto."
   ]
  },
  {
   "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",
    "    np.std(v['ciudad'])\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",
    "TypeError: Cannot perform reduction 'std' with string dtype\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La desviación estándar mide cuánto se alejan los valores del promedio, y para eso hay que poder restar. A \"lima\" menos \"cusco\" no se le puede pedir eso. Es la misma frontera del capítulo 2: el tipo decide qué cálculo tiene sentido 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. El rango es de cristal\n",
    "\n",
    "Quita la venta más grande y la más chica y vuelve a\n",
    "calcular el rango. Compara también con el rango entre el percentil 1 y el 99."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "orden = v['monto'].sort_values()\n",
    "\n",
    "print('rango completo:        %.2f' % (orden.iloc[-1] - orden.iloc[0]))\n",
    "print('sin la mayor y la menor: %.2f' % (orden.iloc[-2] - orden.iloc[1]))\n",
    "print('entre el 1%% y el 99%%:    %.2f'\n",
    "      % (v['monto'].quantile(0.99) - v['monto'].quantile(0.01)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "rango completo:        6734.43\n",
    "sin la mayor y la menor: 6575.48\n",
    "entre el 1% y el 99%:    3022.95\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quitar dos filas se lleva 159 soles. Pero quitar el 1% de cada punta, o sea\n",
    "30 filas de cada lado, **se lleva más de la mitad del rango**:\n",
    "de 6.734 baja a 3.023.\n",
    "\n",
    "Eso te dice dónde está la acción: en las puntas hay unas pocas ventas muy\n",
    "raras que estiran el rango una barbaridad. Las cazamos en el capítulo 6.\n",
    "\n",
    "El rango entre percentiles es una medida decente y muy fácil de explicar. La\n",
    "uso mucho cuando alguien me pide \"el rango\" y sé que el máximo es un error de\n",
    "captura 😅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. ¿Varían igual todas las ciudades?\n",
    "\n",
    "Saca media, desviación y coeficiente de variación por\n",
    "ciudad. ¿Se parece al resultado de los segmentos?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_ciudad = v.groupby('ciudad')['monto'].agg(['mean', 'std'])\n",
    "por_ciudad['cv'] = por_ciudad['std'] / por_ciudad['mean']\n",
    "print(por_ciudad.round(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "             mean      std     cv\n",
    "ciudad\n",
    "arequipa  801.917  752.519  0.938\n",
    "chiclayo  813.908  760.953  0.935\n",
    "cusco     780.696  705.237  0.903\n",
    "lima      792.737  752.954  0.950\n",
    "piura     792.161  725.125  0.915\n",
    "trujillo  842.688  816.312  0.969\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos cosas, y la segunda es la interesante 🤓\n",
    "\n",
    "La primera: las seis ciudades se parecen muchísimo entre ellas, tanto en\n",
    "media como en dispersión. Otra vez ciudad no separa nada, igual que en el\n",
    "capítulo 1.\n",
    "\n",
    "La segunda: **el CV por ciudad es 0,9 y pico, y el CV por segmento era\n",
    "0,37**. ¡Dos veces y media más!\n",
    "\n",
    "¿Por qué? Porque dentro de una ciudad hay bodegas y mayoristas mezclados, y\n",
    "eso mete una variabilidad enorme. Dentro de un segmento no: todos venden a la\n",
    "misma escala.\n",
    "\n",
    "O sea que la dispersión no es una propiedad de los datos, es una propiedad de\n",
    "**cómo los agrupas**. Agrupar bien reduce el ruido, y eso es la\n",
    "mitad de un buen análisis 🧠"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. La desviación de un solo dato\n",
    "\n",
    "Calcula la desviación estándar de una serie con un solo\n",
    "valor. Antes de correrlo, piensa qué debería salir."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('con un dato:  ', pd.Series([500.0]).std())\n",
    "print('con dos datos:', pd.Series([500.0, 500.0]).std())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "con un dato:   nan\n",
    "con dos datos: 0.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con un dato sale `nan`, sin error y sin aviso 🫥\n",
    "\n",
    "Y es lo correcto, aunque asuste: con ddof=1 estás dividiendo entre n-1, o sea\n",
    "entre cero. Un solo dato no tiene dispersión que estimar, porque no hay nada de\n",
    "qué separarse.\n",
    "\n",
    "Con dos datos iguales sale 0,0, que también es correcto.\n",
    "\n",
    "Esto importa de verdad cuando agrupas: si tienes una categoría con una sola\n",
    "fila, su desviación va a ser `nan` y va a contaminar cualquier\n",
    "promedio que hagas después. Por eso, siempre que agrupes, pide el\n",
    "`count` al lado 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Compara la variabilidad de cosas incomparables\n",
    "\n",
    "¿Qué varía más en este archivo: el monto, las unidades, el\n",
    "descuento o la satisfacción? Están en unidades distintas, así que la desviación\n",
    "no sirve."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['monto', 'unidades', 'descuento', 'satisfaccion']:\n",
    "    s = v[col].dropna()\n",
    "    print('%-13s media %8.3f | desviacion %8.3f | CV %.4f'\n",
    "          % (col, s.mean(), s.std(), s.std() / s.mean()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "monto         media  803.761 | desviacion  751.989 | CV 0.9356\n",
    "unidades      media   11.601 | desviacion    5.775 | CV 0.4978\n",
    "descuento     media    0.125 | desviacion    0.072 | CV 0.5731\n",
    "satisfaccion  media    3.010 | desviacion    1.421 | CV 0.4720\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El monto es el que más varía con diferencia: CV de 0,9356, casi el doble que\n",
    "cualquier otro.\n",
    "\n",
    "Y fíjate en lo que habría pasado mirando las desviaciones a secas: monto\n",
    "752, unidades 5,78. Habrías dicho \"el monto varía 130 veces más\", que es\n",
    "comparar soles con cajas y no significa nada 🍎🍐\n",
    "\n",
    "Con el CV la respuesta es \"el monto varía el doble\", que sí se puede\n",
    "defender.\n",
    "\n",
    "Un aviso: el CV solo tiene sentido si la variable tiene un cero natural y no\n",
    "toma valores negativos. Con temperaturas en grados Celsius, por ejemplo, no\n",
    "sirve, porque el cero es arbitrario y la media puede quedar cerca de cero y\n",
    "disparar el cociente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La desviación de una proporción\n",
    "\n",
    "Calcula la desviación estándar de `compro`, que\n",
    "es 0/1. Luego comprueba que coincide con la fórmula de la proporción."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = v['compro'].mean()\n",
    "n = len(v)\n",
    "\n",
    "print('desviacion calculada:  %.6f' % v['compro'].std())\n",
    "print('formula sqrt(p*(1-p)): %.6f' % np.sqrt(p * (1 - p)))\n",
    "print('con la correccion n-1: %.6f' % np.sqrt(p * (1 - p) * n / (n - 1)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "desviacion calculada:  0.494013\n",
    "formula sqrt(p*(1-p)): 0.493931\n",
    "con la correccion n-1: 0.494013\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Coincide hasta el último decimal cuando le pones la corrección 🎯\n",
    "\n",
    "Lo interesante es que **en una variable 0/1 la dispersión no es libre:\n",
    "la decide la proporción**. Si el 57,77% compra, la desviación tiene que\n",
    "ser 0,494013. No hay otra opción.\n",
    "\n",
    "Eso tiene una consecuencia que se usa muchísimo: la dispersión es máxima\n",
    "cuando p vale 0,5 (mitad y mitad, máxima incertidumbre) y se va a cero cuando p\n",
    "se acerca a 0 o a 1. Por eso una encuesta de \"sí o no\" necesita más gente cuando\n",
    "el resultado está peleado, y menos cuando está claro 🗳️\n",
    "\n",
    "Esta fórmula es la que usaremos en el capítulo 10 para poner el margen de\n",
    "error de una proporción."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Un resumen honesto en una línea\n",
    "\n",
    "Escribe una función que resuma una columna con centro y\n",
    "dispersión, eligiendo las medidas según si hay cola o no."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resumen(serie, nombre):\n",
    "    s = serie.dropna()\n",
    "    media, mediana = s.mean(), s.median()\n",
    "    if abs(media - mediana) / mediana < 0.10:\n",
    "        return ('%s: %.2f de media, +/- %.2f'\n",
    "                % (nombre, media, s.std()))\n",
    "    q1, q3 = s.quantile(0.25), s.quantile(0.75)\n",
    "    return ('%s: mediana %.2f, y la mitad central entre %.2f y %.2f'\n",
    "            % (nombre, mediana, q1, q3))\n",
    "\n",
    "\n",
    "print(resumen(v['monto'], 'monto'))\n",
    "print(resumen(v['unidades'], 'unidades'))\n",
    "print(resumen(v.loc[v['segmento'] == 'Bodega', 'monto'], 'monto en Bodega'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "monto: mediana 533.63, y la mitad central entre 252.69 y 1068.68\n",
    "unidades: 11.60 de media, +/- 5.77\n",
    "monto en Bodega: 178.70 de media, +/- 69.59\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres columnas, tres frases, cada una con las medidas que le tocan 🙌\n",
    "\n",
    "Esta función la puedes pegar en cualquier cuaderno y usarla como primer\n",
    "vistazo. Lo que hace, en el fondo, es tomar por ti la decisión del capítulo 3\n",
    "(media o mediana) y arrastrar la dispersión que le corresponde a cada una.\n",
    "\n",
    "Porque eso es lo que no se puede mezclar: media con desviación estándar,\n",
    "mediana con cuartiles. Ver una media con un rango intercuartílico al lado es\n",
    "señal de que alguien copió y pegó sin pensar 😬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Las tres medidas sobre tres columnas\n",
    "\n",
    "Saca desviación, rango intercuartílico y desviación absoluta mediana de tres columnas y compáralas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['monto', 'unidades', 'satisfaccion']:\n",
    "    s = v[col].dropna()\n",
    "    print('%-13s desviacion %8.2f   IQR %8.2f   MAD %8.2f'\n",
    "          % (col, s.std(), s.quantile(0.75) - s.quantile(0.25),\n",
    "             (s - s.median()).abs().median()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "monto         desviacion   751.99   IQR   815.99   MAD   325.67\n",
    "unidades      desviacion     5.77   IQR     8.00   MAD     4.00\n",
    "satisfaccion  desviacion     1.42   IQR     2.00   MAD     1.00\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las tres miden lo mismo y ninguna da el mismo número 🤓\n",
    "\n",
    "Y no es que una esté bien y las otras mal: **miden cosas distintas del mismo concepto**. En el monto, el MAD (325) es menos de la mitad de la desviación (752), y esa distancia es la huella de la cola: la desviación eleva al cuadrado, así que los pedidos enormes le pesan muchísimo, y al MAD casi no.\n",
    "\n",
    "La regla que uso yo: si desviación y MAD se parecen, cualquiera sirve. Si se separan como aquí, reporta el IQR, que es el que se puede explicar en una frase 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Cuánto pesa el uno por ciento de arriba\n",
    "\n",
    "Quita el 1% de montos más altos y mira cuánta dispersión se va con ellos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sin_topes = v[v['monto'] < v['monto'].quantile(0.99)]['monto']\n",
    "print('con todo:        desviacion %.2f' % v['monto'].std())\n",
    "print('sin el 1%% de arriba: desviacion %.2f' % sin_topes.std())\n",
    "print('se fue el %.1f%% de la dispersion quitando el %.1f%% de las filas'\n",
    "      % ((1 - sin_topes.std() / v['monto'].std()) * 100,\n",
    "         (1 - len(sin_topes) / len(v)) * 100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "con todo:        desviacion 751.99\n",
    "sin el 1% de arriba: desviacion 707.47\n",
    "se fue el 5.9% de la dispersion quitando el 1.0% de las filas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Treinta filas se llevan el 5,9% de la dispersión 🎯\n",
    "\n",
    "O sea que cada una de esas treinta pesa casi seis veces más que una fila normal. Ese es el argumento entero a favor de las medidas robustas, y ahora lo tienes en un número en vez de en una recomendación.\n",
    "\n",
    "Ojo con la conclusión fácil, que es la trampa: **esto no dice que haya que borrarlas**. Son pedidos de verdad de clientes de verdad. Lo que dice es que la desviación estándar no es la medida adecuada para describir esta columna, que es otra cosa. Los atípicos tienen su capítulo entero justo después 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La columna más estable, que no lo era"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora una comparación que parece de sentido común y que no lo es. Mírala despacio, que es de las que se cuelan solas 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Miras qué columna es más estable para decidir cuál vigilar. Comparas las desviaciones y una es enorme y la otra chiquita.\n",
    "\n",
    "```\n",
    "for c in ['monto', 'unidades']:\n",
    "    print(c, round(v[c].std(), 2))\n",
    "\n",
    "# monto     751.99\n",
    "# unidades    5.78\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "La conclusión sale sola: las unidades son estabilísimas y el monto está descontrolado. Y no 📏\n",
    "\n",
    "Una está en soles y la otra en cajas. Comparar 752 soles con 5,78 cajas es como decir que 3 kilos es más que 2 metros. La desviación tiene las mismas unidades que el dato, así que **solo se compara entre cosas medidas en lo mismo**.\n",
    "\n",
    "Para comparar entre columnas se divide la desviación entre la media, y eso es el coeficiente de variación. Aquí sale 0,936 para el monto y 0,498 para las unidades: el monto sí es más variable, pero por el doble y no por ciento treinta veces.\n",
    "\n",
    "Y de paso, el descuento sale 0,573 y la satisfacción 0,472. Puestas en la misma escala, las cuatro columnas se parecen muchísimo más de lo que decían las desviaciones sueltas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Los cuatro segmentos tienen desviaciones de 69,59 a 719,55 y todos un coeficiente de variación de 0,37. ¿Qué dice eso del negocio?\n",
    "\n",
    "a) Que son el mismo negocio a distinta escala\n",
    "\n",
    "b) Que Mayorista es mucho más impredecible que Bodega\n",
    "\n",
    "c) Que hay que analizarlos por separado siempre\n",
    "\n",
    "d) Que las desviaciones están mal calculadas\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* En soles sí varía más. Pero el CV compara la variación con el tamaño de cada uno.\n",
    "\n",
    "*c)* Puede que convenga, pero eso no es lo que dice este número en concreto.\n",
    "\n",
    "*d)* Están bien. Fíjate en qué tienen en común los cuatro coeficientes.\n",
    "\n",
    "Un mayorista es una bodega multiplicada por diez, no un animal distinto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📏 Un promedio sin dispersión es medio dato. Dos vendedores de 500 soles con\n",
    "desviaciones de 6,55 y 405,32 son dos negocios distintos.\n",
    "\n",
    "- 🔢 `ddof`: pandas divide entre n-1 y numpy entre n. Con 3.000\n",
    "filas da igual; con 3 datos son 22,5% de diferencia.\n",
    "\n",
    "- 📦 El IQR se explica solo: el 50% de las ventas está entre 252,69 y\n",
    "1.068,68.\n",
    "\n",
    "- ⚖️ El coeficiente de variación compara escalas distintas. Los cuatro\n",
    "segmentos tienen desviaciones de 69 a 719 y todos el mismo CV de 0,37: son el\n",
    "mismo negocio a distinto tamaño.\n",
    "\n",
    "- 🧺 El CV por ciudad es 0,9 y por segmento 0,37. La dispersión depende de\n",
    "cómo agrupas, no solo de los datos.\n",
    "\n",
    "- 🔔 La regla del 68% solo vale si hay campana. Aquí dio 68,70% en unidades,\n",
    "81,80% en monto y 59,44% en satisfacción.\n",
    "\n",
    "- 🛡️ Chebyshev sí vale siempre: al menos el 75% a dos desviaciones, pase lo\n",
    "que pase con la forma.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un promedio sin su dispersión al lado no describe nada. Describe la mitad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si vienes de Excel, esto es la `DESVEST` que quizá ya usaste sin\n",
    "saber qué contaba. La [guía de Excel](https://missyera.com/guias/excel-desde-cero/) lo\n",
    "cuenta desde ese lado 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Llevamos dos capítulos usando la palabra \"campana\" sin comprobar si la hay.\n",
    "En el capítulo 5 la miramos de frente: qué es la distribución normal, cómo se\n",
    "comprueba si tus datos la siguen, y qué se rompe cuando no 🔔"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Cuál es la fórmula de la desviación estándar?Se resta la media a cada dato, se eleva al cuadrado cada resta, se promedian esos cuadrados y se saca la raíz. Ese promedio de cuadrados es la varianza, y la desviación estándar es su raíz.\n",
    "\n",
    "¿Qué es la desviación estándar, en una frase?Cuánto se aleja un dato típico de la media, medido en las mismas unidades que los datos. Si tus ventas están en soles, la desviación está en soles.\n",
    "\n",
    "¿Cómo se calcula la desviación estándar paso a paso?Calcula la media, resta la media a cada dato, eleva cada resta al cuadrado, súmalos, divide entre el número de datos y saca la raíz. En Python es una línea, y en este capítulo está corriendo.\n",
    "\n",
    "¿Qué diferencia hay entre varianza y desviación estándar?La varianza está en unidades al cuadrado, así que no se puede leer: soles al cuadrado no significa nada. La desviación estándar es su raíz y vuelve a las unidades originales, por eso es la que se reporta.\n",
    "\n",
    "¿Es lo mismo desviación estándar que desviación típica?Sí, son dos nombres para lo mismo. \"Típica\" se usa más en España y \"estándar\" en América Latina.\n",
    "\n",
    "¿Qué es el coeficiente de variación?La desviación estándar dividida entre la media, en porcentaje. Sirve para comparar la dispersión de dos cosas que no están en la misma escala, como el ticket de Lima contra el de Cusco.\n",
    "\n",
    "¿Qué es la varianza?El promedio de los cuadrados de las distancias a la media. Es el paso de en medio para llegar a la desviación estándar, y sola casi no se reporta porque sus unidades no se pueden leer.\n",
    "\n",
    "¿Cuál es la fórmula de la varianza?La suma de las diferencias al cuadrado respecto de la media, dividida entre el número de datos. Si es una muestra y no la población entera, se divide entre uno menos.\n",
    "\n",
    "¿Qué diferencia hay entre varianza poblacional y muestral?El divisor. La poblacional divide entre n y la muestral entre n menos uno, y esa resta existe para corregir que una muestra siempre se ve un poco menos dispersa de lo que es la población.\n",
    "\n",
    "¿Qué símbolo tiene la varianza?Sigma al cuadrado para la población y s al cuadrado para la muestra. La desviación estándar es la misma letra sin el cuadrado.\n",
    "\n",
    "¿Cómo se calcula la desviación estándar en Excel?Con DESVEST.M si tus datos son una muestra y DESVEST.P si son la población entera. Elegir mal cambia poco con muchos datos y cambia bastante con pocos.\n",
    "\n",
    "¿Cuáles son las medidas de dispersión?El rango, el rango intercuartílico, la varianza y la desviación estándar. Todas contestan lo mismo con distinta prudencia frente a los datos raros."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 4 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/dispersion/\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
}
