{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tres distribuciones y el teorema que lo explica todo\n",
    "\n",
    "Binomial, Poisson y normal, con datos de verdad. Y por qué la campana aparece aunque tus datos no la tengan.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 8 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/distribuciones/\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": [
    "Una distribución es **una forma que se repite**. Y lo bonito es\n",
    "que en la práctica casi todo se parece a una de tres 🎴\n",
    "\n",
    "No te voy a hacer memorizar fórmulas. Vamos a ver cuál encaja con qué parte\n",
    "de estas ventas, y por qué."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from scipy import stats\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",
    "print('filas:', len(v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de arrancar, una pregunta: **¿alguien te ha dicho alguna vez que uses la normal porque \"todo tiende a la normal\"?** Esa frase está a medias, y la mitad que falta es la que importa 🔔"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La binomial: contar éxitos en intentos fijos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pregunta que contesta: *\"de 20 visitas comerciales, ¿cuántas van a\n",
    "cerrar?\"*. Hay un número fijo de intentos y cada uno sale bien o mal."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "P(X=k)=(nk)pk(1−p)n−k\n",
    "\n",
    "la probabilidad de k éxitos en n intentos, contando de cuántas formas pueden repartirse esos k entre los n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = v['compro'].mean()\n",
    "n = 20\n",
    "\n",
    "print('probabilidad de cierre: %.4f' % p)\n",
    "print()\n",
    "for k in [8, 10, 12, 14, 16]:\n",
    "    print('P(cerrar exactamente %2d de 20) = %.4f' % (k, stats.binom.pmf(k, n, p)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo más probable es cerrar 12, y aun así solo pasa el 17,61% de las veces.\n",
    "Fíjate en eso, que es lo que la gente no espera: **ni siquiera el\n",
    "resultado más probable es probable** 🎲\n",
    "\n",
    "Y esto ya sirve para algo práctico. Un comercial cierra 16 de 20 y todo el\n",
    "mundo aplaude. ¿Es mérito o es suerte?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('media esperada:  %.2f cierres' % (n * p))\n",
    "print('desviacion:      %.4f' % np.sqrt(n * p * (1 - p)))\n",
    "print()\n",
    "print('P(cerrar 16 o mas de 20) = %.6f' % (1 - stats.binom.cdf(15, n, p)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un 3,33%. O sea que cerrar 16 de 20 pasa una de cada treinta veces\n",
    "**solo por azar**, sin que el comercial haga nada especial 😬\n",
    "\n",
    "Si tienes treinta comerciales, cada mes va a haber uno que lo consiga. Y si\n",
    "lo premias, estás premiando una moneda.\n",
    "\n",
    "Ese cálculo, \"qué probabilidad hay de ver algo así de bueno por pura suerte\",\n",
    "es exactamente un valor p. Lo formalizamos en el capítulo 12, pero ya lo acabas\n",
    "de hacer 😎"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Poisson: contar sucesos en un periodo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pregunta cambia: ya no hay 20 intentos. *\"¿Cuántas ventas voy a tener\n",
    "mañana?\"*. Puede ser cualquier número."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "P(X=k)=λke−λk!\n",
    "\n",
    "la probabilidad de ver k sucesos en un periodo donde en promedio se ven lambda"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_dia = v.groupby(v['fecha'].dt.date).size()\n",
    "\n",
    "print('dias con actividad: %d' % len(por_dia))\n",
    "print('media:    %.4f ventas por dia' % por_dia.mean())\n",
    "print('varianza: %.4f' % por_dia.var())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Media 5,5866 y varianza 5,4146. Prácticamente iguales 👀\n",
    "\n",
    "Eso es **la firma de una Poisson**, y es su rareza más útil: es\n",
    "la única distribución común donde la media y la varianza son el mismo número. Si\n",
    "las mides y coinciden, tienes una Poisson delante casi seguro.\n",
    "\n",
    "Vamos a comprobar si de verdad encaja, contando cuántos días hubo con cada\n",
    "cantidad de ventas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lam = por_dia.mean()\n",
    "\n",
    "print(' ventas | observados | esperados por Poisson')\n",
    "for k in range(0, 12):\n",
    "    print('   %2d   |    %4d    |   %7.2f'\n",
    "          % (k, (por_dia == k).sum(), len(por_dia) * stats.poisson.pmf(k, lam)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Encaja bastante bien 🎯 Observados y esperados se siguen la pista en todo el\n",
    "rango.\n",
    "\n",
    "Y hay una discrepancia que merece la pena mirar: la Poisson espera unos 2\n",
    "días con **cero** ventas y en los datos hay **cero días con\n",
    "cero ventas**.\n",
    "\n",
    "Eso no es un fallo de la Poisson, es cómo construí la cuenta: agrupé por las\n",
    "fechas *que aparecen en el archivo*, así que un día sin ninguna venta\n",
    "sencillamente no existe en esa lista. Es un sesgo de selección puesto por mí sin\n",
    "darme cuenta, y de esos hay muchos en el capítulo 17 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La normal y por qué está en todas partes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 5 vimos que el monto **no** es normal: asimetría\n",
    "1,3405, cola larga por la derecha.\n",
    "\n",
    "Y aun así la normal va a aparecer. Mira lo que pasa si en vez de mirar ventas\n",
    "sueltas miro *promedios* de ventas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generador = np.random.default_rng(7)\n",
    "montos = v['monto'].values\n",
    "\n",
    "print('las ventas sueltas: asimetria %.4f' % v['monto'].skew())\n",
    "print()\n",
    "for n in [2, 5, 30, 100]:\n",
    "    medias = pd.Series([generador.choice(montos, n).mean() for _ in range(2000)])\n",
    "    print('promedios de %3d ventas -> asimetria %+.4f | desviacion %6.2f'\n",
    "          % (n, medias.skew(), medias.std()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la columna de la asimetría bajando: **1,3405, luego 1,0091, 0,6173,\n",
    "0,3223 y 0,0518** 😍\n",
    "\n",
    "Eso es el **teorema central del límite**, y es probablemente el\n",
    "resultado más importante de toda la estadística. Dice que:\n",
    "\n",
    "**los promedios se vuelven acampanados aunque los datos de los que\n",
    "salen no lo sean.**\n",
    "\n",
    "Por eso la normal está en todas partes: casi nunca trabajamos con datos\n",
    "sueltos, trabajamos con promedios, proporciones y totales. Y todos esos se\n",
    "comportan como una campana aunque el material de origen sea un desastre.\n",
    "\n",
    "Fíjate también en la otra columna, la de la desviación, que baja de 531 a 76.\n",
    "No baja de cualquier manera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in [2, 5, 30, 100]:\n",
    "    print('n=%3d -> formula: %.2f' % (n, v['monto'].std() / np.sqrt(n)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "531,74 contra 531,09. 137,29 contra 138,92. Clavado 🎯\n",
    "\n",
    "Esa fórmula, **desviación dividida entre la raíz de n**, se\n",
    "llama **error estándar** y es la pieza central de todo lo que\n",
    "viene. Es literalmente la respuesta a \"cuánto se equivoca un promedio\".\n",
    "\n",
    "Y mira lo que dice esa raíz cuadrada: para que tu promedio sea el doble de\n",
    "preciso necesitas **cuatro veces** más datos. Para diez veces más\n",
    "preciso, cien veces más datos. Es la razón por la que las encuestas serias\n",
    "tienen 1.000 personas y no 100.000: la mejora deja de compensar 💸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Y el famoso \"con 30 basta\"?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Habrás oído que con 30 datos ya vale. Míralo otra vez en la tabla: con 30\n",
    "ventas la asimetría todavía es **0,3223**. No es cero.\n",
    "\n",
    "El \"30\" es una regla de dedo que funciona cuando los datos de partida no\n",
    "están muy torcidos. Con una cola como la del monto hacen falta más. Con 100 ya\n",
    "estamos en 0,0518, que sí es despreciable.\n",
    "\n",
    "La versión honesta de la regla: **cuanto más torcidos los datos, más\n",
    "promedio hace falta**. Y se comprueba en tres líneas, como acabamos de\n",
    "hacer 🙌"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error del capítulo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quiero una muestra de 5.000 ventas distintas para hacer el experimento más\n",
    "grande:"
   ]
  },
  {
   "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",
    "    generador.choice(montos, 5000, replace=False)\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 take a larger sample than population when replace is False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Solo hay 3.000 ventas, no puedo sacar 5.000 sin repetir. Justo 👍\n",
    "\n",
    "Lo interesante es que el experimento de arriba **sí** repetía\n",
    "(`replace=True` es lo que hace `choice` por defecto), y eso\n",
    "no es un descuido: es la técnica. Se llama remuestreo, y consiste en tratar tus\n",
    "datos como si fueran la población y sacar muestras con reposición.\n",
    "\n",
    "Y ojo con el pariente silencioso de este error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('P(cerrar 25 de 20 intentos) =', stats.binom.pmf(25, 20, p))\n",
    "print('media de una binomial con p=1.5:', stats.binom(20, 1.5).mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pedirle 25 éxitos de 20 intentos devuelve 0.0, que es correcto pero se parece\n",
    "mucho a \"es muy improbable\" cuando en realidad es \"eso no puede pasar\". Y una\n",
    "probabilidad de 1,5 devuelve `nan` sin quejarse 😑"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuál usar, en una tabla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Si cuentas | Distribución | Señal para reconocerla |\n",
    "|---|---|---|\n",
    "| Éxitos en n intentos fijos | Binomial | Hay un máximo posible |\n",
    "| Sucesos en un periodo | Poisson | Media y varianza parecidas |\n",
    "| Promedios, totales, proporciones | Normal | Casi siempre, por el TLC |\n",
    "| Tiempo hasta que pase algo | Exponencial | Muchos cortos, pocos larguísimos |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, de pedirle a una distribución algo imposible"
   ]
  },
  {
   "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",
    "    generador.poisson(-3)\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: lam < 0 or lam is NaN\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La Poisson cuenta cosas que pasan, y su parámetro es **cuántas pasan en promedio**. Menos tres pedidos por día no existe. Cuando una distribución se queja del parámetro, casi siempre te está diciendo que le pediste algo que el mundo no puede hacer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    stats.binom.rvs(10, 1.4, size=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: Domain error in arguments. The `scale` parameter must be positive for all distributions, and many distributions have restrictions on shape parameters. Please see the `scipy.stats.binom` documentation for details.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo mismo con la binomial: su segundo parámetro es una probabilidad y 1,4 no lo es. El mensaje es largo y genérico porque scipy usa el mismo para todas sus distribuciones, así que lo útil está al final, en el nombre de la que se quejó 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. ¿Es raro cerrar 16 de 20 dos meses seguidos?\n",
    "\n",
    "Ya sabemos que cerrar 16 de 20 pasa el 3,33% de las veces.\n",
    "Calcula la probabilidad de que le pase al mismo comercial dos meses seguidos, y\n",
    "la de que le pase a alguno de 30 comerciales en un mes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "una_vez = 1 - stats.binom.cdf(15, 20, p)\n",
    "\n",
    "print('un comercial, un mes:       %.6f' % una_vez)\n",
    "print('el mismo, dos meses:        %.6f' % (una_vez ** 2))\n",
    "print('alguno de 30, en un mes:    %.6f' % (1 - (1 - una_vez) ** 30))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "un comercial, un mes:       0.033334\n",
    "el mismo, dos meses:        0.001111\n",
    "alguno de 30, en un mes:    0.638347\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los tres números cuentan historias distintas 📖\n",
    "\n",
    "Que el mismo lo repita dos meses seguidos tiene una probabilidad de 0,11%. Eso\n",
    "ya es difícil de explicar con suerte, y ahí sí empezaría a mirarle la técnica.\n",
    "\n",
    "Pero mira el tercero: **en un equipo de 30, la probabilidad de que\n",
    "alguien lo consiga es del 63,83%**. O sea que es lo más normal del mundo.\n",
    "\n",
    "Y esa es la trampa de las comparaciones múltiples, que tiene capítulo propio\n",
    "(el 14). Un resultado raro deja de ser raro en cuanto le das muchas\n",
    "oportunidades de ocurrir 🎰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. ¿Encaja Poisson también por segmento?\n",
    "\n",
    "Comprueba la firma de la Poisson (media parecida a\n",
    "varianza) en las ventas diarias de cada segmento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg, g in v.groupby('segmento'):\n",
    "    dia = g.groupby(g['fecha'].dt.date).size()\n",
    "    print('%-11s media %.4f | varianza %.4f | razon %.3f'\n",
    "          % (seg, dia.mean(), dia.var(), dia.var() / dia.mean()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Bodega      media 1.8556 | varianza 1.0607 | razon 0.572\n",
    "Horeca      media 1.7966 | varianza 0.8517 | razon 0.474\n",
    "Mayorista   media 1.8337 | varianza 0.9282 | razon 0.506\n",
    "Minimarket  media 1.9632 | varianza 1.2296 | razon 0.626\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí la razón está entre 0,47 y 0,63, no en 1. Ya no es Poisson 🤔\n",
    "\n",
    "Una varianza *menor* que la media significa que los días se parecen\n",
    "entre ellos más de lo que el azar puro permitiría. A eso se le llama\n",
    "subdispersión.\n",
    "\n",
    "Y la explicación es la misma trampa de antes: al agrupar por segmento, los\n",
    "días sin ninguna venta de ese segmento desaparecen de la cuenta. Estoy\n",
    "recortando justo los días flojos, así que los que quedan se parecen demasiado.\n",
    "\n",
    "Buena lección: **cuando una distribución deja de encajar, sospecha\n",
    "antes de tus datos que de la teoría** 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El TLC con la peor columna posible\n",
    "\n",
    "Aplica el mismo experimento a algo mucho más torcido que el\n",
    "monto: la columna `compro`, que solo tiene ceros y unos. ¿Cuántos\n",
    "hacen falta?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ceros_y_unos = v['compro'].values\n",
    "\n",
    "for n in [5, 30, 100, 500]:\n",
    "    medias = pd.Series([generador.choice(ceros_y_unos, n).mean()\n",
    "                        for _ in range(2000)])\n",
    "    print('promedios de %3d -> asimetria %+.4f | valores distintos %d'\n",
    "          % (n, medias.skew(), medias.nunique()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "promedios de   5 -> asimetria -0.1134 | valores distintos 6\n",
    "promedios de  30 -> asimetria -0.0899 | valores distintos 18\n",
    "promedios de 100 -> asimetria -0.0079 | valores distintos 34\n",
    "promedios de 500 -> asimetria -0.0074 | valores distintos 70\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto me sorprendió: **converge más rápido que el monto**, aunque\n",
    "los datos de partida son mucho más feos 😲\n",
    "\n",
    "Con solo 5 la asimetría ya está en -0,11, que al monto le costó llegar con\n",
    "30 y pico. Y con 100 estamos en -0,0079, diez veces más cerca de cero que el\n",
    "monto con las mismas 100.\n",
    "\n",
    "El motivo es que lo que frena al TLC no es que los datos sean raros, sino que\n",
    "tengan **cola**. Una columna 0/1 no tiene cola: no hay ningún valor\n",
    "extremo que pueda arrastrar un promedio. El monto sí, y por eso tarda.\n",
    "\n",
    "Mira también la última columna, la de valores distintos: con n=5 solo hay 6\n",
    "promedios posibles (0, 0.2, 0.4...). La campana está ahí pero dibujada a\n",
    "brochazos 🖌️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El error estándar, y cuánto cuesta mejorarlo\n",
    "\n",
    "Calcula cuántas ventas necesitas para que el error estándar\n",
    "del promedio baje a 50, a 25 y a 10 soles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = v['monto'].std()\n",
    "\n",
    "for objetivo in [100, 50, 25, 10]:\n",
    "    print('para un error estandar de %3d soles hacen falta %8.0f ventas'\n",
    "          % (objetivo, (s / objetivo) ** 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "para un error estandar de 100 soles hacen falta       57 ventas\n",
    "para un error estandar de  50 soles hacen falta      226 ventas\n",
    "para un error estandar de  25 soles hacen falta      905 ventas\n",
    "para un error estandar de  10 soles hacen falta     5655 ventas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 100 a 50 soles de precisión: pasas de 57 a 226 ventas. De 50 a 25: de 226\n",
    "a 905. Cada vez que quieres el doble de precisión, cuadruplicas la muestra 💸\n",
    "\n",
    "Ese es el cálculo que hay detrás de \"¿cuántos clientes encuesto?\", y es de lo\n",
    "más útil que te llevas del libro. Lo normal es que alguien pida una precisión\n",
    "absurda sin saber que cuesta cinco mil respuestas.\n",
    "\n",
    "Fíjate en que la fórmula solo necesita dos cosas: la desviación que esperas y\n",
    "la precisión que quieres. Puedes hacerlo antes de recoger un solo dato 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Simula un mes de ventas\n",
    "\n",
    "Con la Poisson de las ventas diarias y la distribución de\n",
    "montos, simula 30 días y mira cuánto varía el total mensual."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lam = por_dia.mean()\n",
    "totales = []\n",
    "\n",
    "for _ in range(2000):\n",
    "    ventas_del_mes = generador.poisson(lam, 30).sum()\n",
    "    totales.append(generador.choice(montos, ventas_del_mes).sum())\n",
    "\n",
    "totales = pd.Series(totales)\n",
    "print('total mensual simulado: media %.0f | desviacion %.0f' % (totales.mean(), totales.std()))\n",
    "print('el 90%% de los meses cae entre %.0f y %.0f'\n",
    "      % (totales.quantile(0.05), totales.quantile(0.95)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "total mensual simulado: media 134673 | desviacion 14433\n",
    "el 90% de los meses cae entre 111598 y 158882\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora compáralo con lo que medimos en el capítulo 1: los meses reales iban de\n",
    "103.474 a 164.313 soles 🤯\n",
    "\n",
    "La simulación dice que el 90% de los meses debería caer entre 111.598 y\n",
    "158.882, con una media de 134.673.\n",
    "\n",
    "La simulación, que **no sabe nada de meses** y solo conoce la\n",
    "tasa diaria y la forma de los montos, cae casi encima del rango real. Se queda\n",
    "un poco corta en las dos puntas, y tiene sentido: los meses de verdad tienen\n",
    "distinto número de días laborables y campañas, cosas que la simulación no\n",
    "sabe.\n",
    "\n",
    "Y eso cierra el círculo del capítulo 1. Aquella variación mensual del 14,41%\n",
    "que parecía tan dramática es **exactamente lo que produce el azar**\n",
    "con esta tasa de ventas y estos montos. No hacía falta ninguna explicación de\n",
    "negocio, y ahora tenemos la prueba en vez de la sospecha 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. ¿Cuántas ventas negativas esperabas?\n",
    "\n",
    "Sabemos que hay 21 ventas negativas en 3.000. Si fueran\n",
    "cosa del azar con esa tasa, ¿cuánto variarían de un archivo a otro?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tasa = (v['monto'] < 0).mean()\n",
    "esperadas = tasa * len(v)\n",
    "\n",
    "print('tasa: %.5f -> esperadas %.1f' % (tasa, esperadas))\n",
    "print('desviacion de una Poisson: %.2f' % np.sqrt(esperadas))\n",
    "print()\n",
    "print('rango tipico: de %.0f a %.0f'\n",
    "      % (esperadas - 2 * np.sqrt(esperadas), esperadas + 2 * np.sqrt(esperadas)))\n",
    "print('P(ver 40 o mas) = %.6f' % (1 - stats.poisson.cdf(39, esperadas)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "tasa: 0.00700 -> esperadas 21.0\n",
    "desviacion de una Poisson: 4.58\n",
    "\n",
    "rango tipico: de 12 a 30\n",
    "P(ver 40 o mas) = 0.000144\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La Poisson tiene una propiedad muy cómoda: **su desviación es la raíz\n",
    "de su media**. Con 21 esperadas, la desviación es 4,58.\n",
    "\n",
    "O sea que si el mes que viene aparecen 28 ventas negativas, tranqui: entra\n",
    "dentro de lo normal. Si aparecen 40, ahí sí ha pasado algo, porque eso tiene una\n",
    "probabilidad de 0,014% 🚨\n",
    "\n",
    "Este cálculo es la forma más barata que conozco de montar una alerta. No hace\n",
    "falta ningún modelo: cuentas lo que suele pasar, sacas la raíz, y avisas cuando\n",
    "te separas más de dos o tres de esas 📟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El teorema central del límite, a mano\n",
    "\n",
    "Promedia 1, 2, 5 y 30 pedidos al azar, dos mil veces cada uno, y mira cuánto varían esos promedios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g2 = np.random.default_rng(11)\n",
    "for n in [1, 2, 5, 30]:\n",
    "    medias = [g2.choice(v['monto'].values, n).mean() for _ in range(2000)]\n",
    "    print('promediando %2d pedidos: desviacion de las medias %7.2f' % (n, np.std(medias)))\n",
    "print()\n",
    "print('la desviacion de un pedido suelto es %.2f' % v['monto'].std())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "promediando  1 pedidos: desviacion de las medias  733.73\n",
    "promediando  2 pedidos: desviacion de las medias  525.72\n",
    "promediando  5 pedidos: desviacion de las medias  339.30\n",
    "promediando 30 pedidos: desviacion de las medias  135.72\n",
    "\n",
    "la desviacion de un pedido suelto es 751.99\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la raíz cuadrada, escondida a plena vista 🤓\n",
    "\n",
    "Con 30 pedidos la desviación baja a 135,72. Y 751,99 dividido entre la raíz de 30 da 137,3, que es lo mismo salvo por el azar de las dos mil repeticiones. **Para que el promedio sea el doble de estable hace falta cuatro veces más gente**, y por eso las encuestas caras son tan caras.\n",
    "\n",
    "Este número reaparece en el capítulo 10 con el nombre de error estándar, y ahora ya sabes de dónde sale 📐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. ¿Los pedidos por mes son Poisson?\n",
    "\n",
    "Cuenta los pedidos de cada mes y compara su media con su varianza."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_mes = v.groupby(v['fecha'].dt.month).size()\n",
    "print('pedidos por mes:')\n",
    "print(por_mes.to_string())\n",
    "print()\n",
    "print('media %.1f, varianza %.1f' % (por_mes.mean(), por_mes.var()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "pedidos por mes:\n",
    "fecha\n",
    "1     339\n",
    "2     306\n",
    "3     359\n",
    "4     323\n",
    "5     365\n",
    "6     292\n",
    "7     164\n",
    "8     156\n",
    "9     179\n",
    "10    181\n",
    "11    177\n",
    "12    159\n",
    "\n",
    "media 250.0, varianza 7536.4\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Media 250 y varianza 7.536. Ni de cerca 🙅‍♀️\n",
    "\n",
    "La Poisson tiene una propiedad que la delata: **su media y su varianza son el mismo número**. Aquí la varianza es treinta veces la media, así que estos conteos no son Poisson y no hay que forzarlos.\n",
    "\n",
    "Y mira la lista, que se ve el porqué sin ninguna cuenta: de enero a junio ronda los 330 y de julio a diciembre los 170. No es azar, es que **el segundo semestre vende la mitad**. La Poisson supone una tasa constante, y aquí hay dos tasas 📆"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El teorema que casi todo el mundo cita al revés"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora una que se oye mucho en reuniones y que casi nunca se dice bien. Las dos mitades son verdad y no significan lo que parece 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Necesitas cuántas ventas grandes esperar para preparar el stock. Tienes la media y la desviación, así que usas la normal, que es lo que se hace.\n",
    "\n",
    "```\n",
    "from scipy import stats\n",
    "\n",
    "mu, sd = v['monto'].mean(), v['monto'].std()\n",
    "segun_campana = 1 - stats.norm.cdf(3000, mu, sd)\n",
    "real = (v['monto'] > 3000).mean()\n",
    "\n",
    "print(round(segun_campana * 100, 2), round(real * 100, 2))\n",
    "# 0.17 1.23\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Siete veces más ventas grandes de las que la campana predice 📦\n",
    "\n",
    "La normal se define entera con la media y la desviación, así que en cuanto le das esos dos números te contesta lo que sea, aunque tus datos no tengan nada que ver con una campana. Y el monto tiene cola a la derecha, o sea justo la zona donde estás preguntando.\n",
    "\n",
    "Y fíjate en lo que sale mal en la vida real: te quedas corta de stock en las ventas grandes, que son las que más facturan. El error no se reparte, se concentra en lo que más importa.\n",
    "\n",
    "Para preguntas sobre las colas, la campana es la peor herramienta posible. Lo que uso es el dato: `(v['monto'] > 3000).mean()`, que no supone ninguna forma, o los percentiles altos si necesito un umbral. La normal sirve para el centro, no para los bordes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Las ventas diarias tienen media 5,5866 y varianza 5,4146. ¿Qué te dice eso?\n",
    "\n",
    "a) Que se comportan como una Poisson, que es la única común donde media y varianza coinciden\n",
    "\n",
    "b) Que la varianza está mal calculada\n",
    "\n",
    "c) Que los datos son normales\n",
    "\n",
    "d) Que hay poca variación día a día\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Está bien. Que se parezcan tanto es la pista, no el error.\n",
    "\n",
    "*c)* En una normal la media y la varianza son independientes: pueden ser cualquier par de números.\n",
    "\n",
    "*d)* Una varianza de 5,41 sobre una media de 5,59 no es poca. Fíjate en que son casi el mismo número.\n",
    "\n",
    "Media parecida a varianza es la firma de una Poisson."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 Binomial para éxitos en intentos fijos. Cerrar 16 de 20 pasa el 3,33% de\n",
    "las veces por puro azar, y en un equipo de 30 le pasa a alguien el 63,83% de los\n",
    "meses.\n",
    "\n",
    "- 📅 Poisson para sucesos en un periodo. Su firma es media parecida a\n",
    "varianza: aquí 5,5866 y 5,4146.\n",
    "\n",
    "- 🔔 El teorema central del límite: los promedios se acampanan aunque los\n",
    "datos no. La asimetría bajó de 1,3405 a 0,0518 promediando de a 100.\n",
    "\n",
    "- 📐 Error estándar = desviación entre raíz de n. Para el doble de precisión,\n",
    "cuatro veces más datos.\n",
    "\n",
    "- ⚠️ El \"con 30 basta\" es una regla de dedo. Con esta cola, a los 30 la\n",
    "asimetría todavía era 0,3223.\n",
    "\n",
    "- 🎰 Simulando solo con la tasa diaria y la forma de los montos salió un rango\n",
    "mensual de 111.598 a 158.882, y los meses reales fueron de 103.474 a 164.313.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El teorema central del límite habla de los promedios, no de tus datos.\n",
    "No son lo mismo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo de este capítulo se calcula con `scipy.stats` y numpy, y si\n",
    "esas dos te suenan a chino, empiezan desde cero en el\n",
    "[libro de Python](https://missyera.com/guias/python-desde-cero/) 🐍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya tenemos el error estándar, que es la pieza que faltaba. En el capítulo 10\n",
    "la usamos para lo que todo el mundo quiere: poner un margen de error a un\n",
    "número y decir \"está entre esto y esto\" 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es la distribución binomial?La que cuenta cuántos éxitos salen en un número fijo de intentos, cuando cada intento sale bien con la misma probabilidad. Cuántos de cien visitantes compran es exactamente eso.\n",
    "\n",
    "¿Qué es la distribución de Poisson?La que cuenta cuántas veces pasa algo en un rato, cuando pasa poco y al azar. Cuántos reclamos llegan en una hora es el caso de manual, y el de verdad también.\n",
    "\n",
    "¿Cuándo uso binomial y cuándo Poisson?Binomial cuando sabes cuántos intentos hubo. Poisson cuando no hay intentos que contar, solo un periodo de tiempo y cosas que van llegando.\n",
    "\n",
    "¿Qué es la distribución t de Student?La hermana de la normal para cuando tienes pocos datos. Es más ancha, o sea más prudente, y se va pareciendo a la normal a medida que la muestra crece.\n",
    "\n",
    "¿Qué es el teorema central del límite?Que si promedias muestras de cualquier cosa, esos promedios se reparten como una campana aunque los datos originales no lo hagan. Es la razón de que la normal aparezca en todas partes.\n",
    "\n",
    "¿Qué es el error estándar?La desviación estándar de los promedios de muestra, no de los datos. Dice cuánto bailaría tu promedio si repitieras el muestreo, y es lo que hay detrás del margen de error."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 8 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/distribuciones/\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
}
