{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# De la muestra a la población\n",
    "\n",
    "Intervalos de confianza, lo que de verdad significan, y por qué dos intervalos que se solapan pueden ser una diferencia real.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 10 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/intervalos-de-confianza/\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": [
    "Tienes 3.000 ventas y calculas que el monto promedio es 803,76 soles.\n",
    "\n",
    "Pero esas 3.000 no son todas las ventas del mundo: son las que te tocaron. Si\n",
    "el mes que viene sacas otras 3.000, el promedio va a salir distinto. Entonces,\n",
    "¿de cuánto es el promedio *de verdad*? 🤷\n",
    "\n",
    "La respuesta honesta no es un número, es un rango. Eso es un intervalo de\n",
    "confianza, y este capítulo va de calcularlo, entenderlo y no contarlo mal."
   ]
  },
  {
   "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",
    "m = v['monto']\n",
    "n = len(m)\n",
    "\n",
    "error_estandar = m.std() / np.sqrt(n)\n",
    "\n",
    "print('promedio:        %.4f' % m.mean())\n",
    "print('error estandar:  %.4f' % error_estandar)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese error estándar es el del capítulo 8: cuánto se mueve un promedio de\n",
    "muestra en muestra. 13,73 soles."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "EE=sn\n",
    "\n",
    "cuánto se mueve un promedio de muestra en muestra, que baja con la raíz de cuántos datos tengas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una pregunta que vale para toda tu vida laboral: **¿cuántas veces has entregado un número sin decir cuánto se puede mover?** El margen de error no es un adorno académico: es lo que separa un dato de una apuesta 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El intervalo, en una línea"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bajo, alto = stats.t.interval(0.95, n - 1, m.mean(), error_estandar)\n",
    "\n",
    "print('promedio: %.2f' % m.mean())\n",
    "print('intervalo del 95%%: de %.2f a %.2f' % (bajo, alto))\n",
    "print('margen de error: +/- %.2f soles' % ((alto - bajo) / 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí lo tienes: **el monto promedio está entre 776,84 y 830,68\n",
    "soles** 📊\n",
    "\n",
    "Ese 1,96 que multiplica el error estándar (aquí lo pone la t por dentro) es el\n",
    "número mágico del 95%. Si quisieras el 99% sería 2,58, y el intervalo saldría\n",
    "más ancho: más seguridad cuesta más rango.\n",
    "\n",
    "La regla rápida que puedes hacer de cabeza: **margen de error igual a\n",
    "dos errores estándar** 🧮"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "IC95%=x¯±1,96·sn\n",
    "\n",
    "el promedio más menos dos errores estándar, que es de donde sale el margen de error de las encuestas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que significa, y lo que no"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es la parte que casi todo el mundo cuenta mal, incluida yo durante\n",
    "años 🙋\n",
    "\n",
    "**Mal:** \"hay un 95% de probabilidad de que el promedio real esté\n",
    "entre 776,84 y 830,68\".\n",
    "\n",
    "**Bien:** \"si repitiera este cálculo muchas veces con muestras\n",
    "distintas, el 95% de los intervalos que saldrían contendrían el promedio real\".\n",
    "\n",
    "La diferencia parece de abogados y no lo es. El promedio real es un número\n",
    "fijo: o está dentro de tu intervalo o no está. **Lo que tiene el 95% es el\n",
    "método, no el intervalo concreto que te salió**.\n",
    "\n",
    "Y como esto se entiende mejor viéndolo, vamos a hacerlo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generador = np.random.default_rng(7)\n",
    "verdadera = m.mean()\n",
    "tamano = 50\n",
    "contiene = 0\n",
    "\n",
    "for _ in range(1000):\n",
    "    muestra = generador.choice(m.values, tamano)\n",
    "    ee = muestra.std(ddof=1) / np.sqrt(tamano)\n",
    "    lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)\n",
    "    contiene += (lo <= verdadera <= hi)\n",
    "\n",
    "print('de 1000 intervalos del 95%%, contienen el promedio real: %d' % contiene)\n",
    "print('o sea el %.1f%%' % (contiene / 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "92,9% en vez de 95%. Se queda corto 🤔\n",
    "\n",
    "Y ese \"casi\" tiene explicación, que es de las cosas más útiles del capítulo.\n",
    "Vamos a probar con distintos tamaños:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for tamano in [20, 50, 200, 1000]:\n",
    "    contiene = 0\n",
    "    for _ in range(1000):\n",
    "        muestra = generador.choice(m.values, tamano)\n",
    "        ee = muestra.std(ddof=1) / np.sqrt(tamano)\n",
    "        lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)\n",
    "        contiene += (lo <= verdadera <= hi)\n",
    "    print('con muestras de %4d -> cobertura real %.1f%%' % (tamano, contiene / 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Con muestras de 20 o 50, tu intervalo del 95% acierta el 93%** 😬\n",
    "\n",
    "O sea que te está mintiendo un poco: crees que fallas 1 de cada 20 y fallas 1\n",
    "de cada 15. Y no hay ningún error en el cálculo. Solo cuando llegas a 1.000 la\n",
    "cobertura sube al 95,9% y la promesa se cumple.\n",
    "\n",
    "La causa es la cola del capítulo 5. La fórmula supone que los promedios ya se\n",
    "han acampanado, y con 20 ventas de una distribución tan torcida todavía no lo\n",
    "están (capítulo 8: con 30 la asimetría seguía en 0,3223).\n",
    "\n",
    "Compáralo con `unidades`, que sí era acampanada:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "u = v['unidades'].values\n",
    "verdadera_u = u.mean()\n",
    "\n",
    "for tamano in [20, 50]:\n",
    "    contiene = 0\n",
    "    for _ in range(1000):\n",
    "        muestra = generador.choice(u, tamano)\n",
    "        ee = muestra.std(ddof=1) / np.sqrt(tamano)\n",
    "        lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)\n",
    "        contiene += (lo <= verdadera_u <= hi)\n",
    "    print('unidades, muestras de %3d -> cobertura %.1f%%' % (tamano, contiene / 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**95,2% con solo 20 datos.** Justo lo prometido, con la cuarta\n",
    "parte de la muestra que le hacía falta al monto 🔔\n",
    "\n",
    "La forma de los datos importa más que la cantidad. Y date cuenta de lo que eso\n",
    "significa en la práctica: el mismo tamaño de muestra te da un intervalo honesto\n",
    "o uno optimista según qué columna estés midiendo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El intervalo de una proporción"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El caso más frecuente en la vida real: \"el 57,77% de las ventas se cierra,\n",
    "¿con cuánto margen?\".\n",
    "\n",
    "Aquí el error estándar tiene su propia fórmula, la que sacamos en el capítulo\n",
    "4:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "EEp=p(1−p)n\n",
    "\n",
    "el error estándar de un porcentaje, que es máximo cuando el reparto está en la mitad y se encoge cuando se acerca a cero o a uno"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = v['compro'].mean()\n",
    "ee_p = np.sqrt(p * (1 - p) / n)\n",
    "\n",
    "print('proporcion: %.4f' % p)\n",
    "print('error estandar: %.6f' % ee_p)\n",
    "print('intervalo del 95%%: %.4f a %.4f' % (p - 1.96 * ee_p, p + 1.96 * ee_p))\n",
    "print('o sea: %.2f%% +/- %.2f puntos' % (100 * p, 100 * 1.96 * ee_p))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "57,77% con un margen de 1,77 puntos. Ese \"±1,8 puntos\" es exactamente lo que\n",
    "sale en las encuestas electorales 🗳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error del capítulo"
   ]
  },
  {
   "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.t.interval(95, n - 1, m.mean(), error_estandar)\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: alpha must be between 0 and 1 inclusive\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le pedí el 95 en vez del 0,95. Menos mal que avisa 🙏\n",
    "\n",
    "Porque el mismo despiste en la otra dirección **no** avisa:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('pidiendo 0.95:', np.round(stats.t.interval(0.95, n - 1, m.mean(), error_estandar), 2))\n",
    "print('pidiendo 0.05:', np.round(stats.t.interval(0.05, n - 1, m.mean(), error_estandar), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si por error pides 0,05 en vez de 0,95, te devuelve un intervalo\n",
    "**estrechísimo** y perfectamente válido: el intervalo del 5% 😱\n",
    "\n",
    "Y ese sí que se cuela en un informe, porque un margen de error chiquito es\n",
    "justo lo que uno quiere ver. Cuidado con los resultados que te gustan\n",
    "demasiado 🍬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La trampa de los intervalos que se solapan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora lo más importante del capítulo, que es un error que he visto en\n",
    "informes serios.\n",
    "\n",
    "Comparamos dos segmentos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg in ['Horeca', 'Minimarket']:\n",
    "    g = v.loc[v['segmento'] == seg, 'compro']\n",
    "    pp = g.mean()\n",
    "    e = np.sqrt(pp * (1 - pp) / len(g))\n",
    "    print('%-11s n=%3d  p=%.4f  IC [%.4f, %.4f]'\n",
    "          % (seg, len(g), pp, pp - 1.96 * e, pp + 1.96 * e))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los intervalos **se solapan**: Horeca llega hasta 0,5974 por\n",
    "abajo y Minimarket sube hasta 0,6036.\n",
    "\n",
    "La conclusión que casi todo el mundo saca: \"se solapan, así que no hay\n",
    "diferencia\". Vamos a comprobarlo calculando el intervalo *de la\n",
    "diferencia*, que es lo que había que hacer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = v.loc[v['segmento'] == 'Horeca', 'compro']\n",
    "b = v.loc[v['segmento'] == 'Minimarket', 'compro']\n",
    "\n",
    "pa, pb = a.mean(), b.mean()\n",
    "ea = np.sqrt(pa * (1 - pa) / len(a))\n",
    "eb = np.sqrt(pb * (1 - pb) / len(b))\n",
    "ee_dif = np.sqrt(ea ** 2 + eb ** 2)\n",
    "dif = pa - pb\n",
    "\n",
    "print('diferencia: %.4f' % dif)\n",
    "print('intervalo del 95%%: [%.4f, %.4f]'\n",
    "      % (dif - 1.96 * ee_dif, dif + 1.96 * ee_dif))\n",
    "print('contiene el cero:', (dif - 1.96 * ee_dif) <= 0 <= (dif + 1.96 * ee_dif))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**El intervalo de la diferencia no contiene el cero.** O sea que\n",
    "la diferencia es real, aunque los intervalos individuales se solapen 🤯\n",
    "\n",
    "Por qué pasa: el error estándar de la diferencia no es la suma de los dos,\n",
    "es la raíz de la suma de los cuadrados, que sale más chica. Sumar dos incertidumbres\n",
    "\"a lo bruto\" exagera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "EEd=EE12+EE22\n",
    "\n",
    "la incertidumbre de una diferencia no es la suma de las dos, es la raíz de la suma de sus cuadrados, y por eso sale más chica"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Regla:** para comparar dos grupos, calcula el intervalo de la\n",
    "diferencia. Nunca mires si los individuales se tocan 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, y el primero es EL error de este capítulo"
   ]
  },
  {
   "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.t.interval(95, len(m) - 1, m.mean(), error_estandar)\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: alpha must be between 0 and 1 inclusive\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El 95% se escribe **0,95** y no 95. Este lo he visto tantas veces que casi merece un capítulo propio, y menos mal que revienta: si scipy aceptara el 95 sin quejarse, te devolvería un intervalo absurdo y no habría forma de notarlo."
   ]
  },
  {
   "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.bootstrap(m, np.mean)\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",
    "AxisError: `axis` is out of bounds for array of dimension 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y este te presenta al capítulo siguiente. `scipy` trae su propio\n",
    "bootstrap, y se queja porque quiere **una secuencia de muestras** y\n",
    "le diste la muestra: hay que escribir `stats.bootstrap((m,), np.mean)`,\n",
    "con esa coma que convierte a `m` en una tupla de un elemento. Es una\n",
    "coma y es la diferencia entre que funcione y que no 🥾"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. El margen de error de cada segmento\n",
    "\n",
    "Calcula el intervalo de la tasa de compra de los cuatro\n",
    "segmentos. ¿Cuál tiene el margen más ancho y por qué?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for seg, g in v.groupby('segmento'):\n",
    "    pp = g['compro'].mean()\n",
    "    e = np.sqrt(pp * (1 - pp) / len(g))\n",
    "    print('%-11s n=%3d  %.2f%% +/- %.2f puntos'\n",
    "          % (seg, len(g), 100 * pp, 100 * 1.96 * e))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Bodega      n=707  37.48% +/- 3.57 puntos\n",
    "Horeca      n=742  63.21% +/- 3.47 puntos\n",
    "Mayorista   n=750  72.40% +/- 3.20 puntos\n",
    "Minimarket  n=801  56.93% +/- 3.43 puntos\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los cuatro rondan los 3,4 puntos, y el más estrecho es Mayorista 🎯\n",
    "\n",
    "Lo interesante es *por qué*. Mayorista no tiene más filas que\n",
    "Minimarket (750 contra 801) y aun así su margen es más chico.\n",
    "\n",
    "La razón está en la fórmula: p(1-p) es máximo cuando p vale 0,5 y baja\n",
    "cuando te alejas. Mayorista está en 0,724, o sea lejos de la mitad, así que hay\n",
    "menos incertidumbre que estimar.\n",
    "\n",
    "Es lo que decíamos en el capítulo 4: **una votación peleada necesita\n",
    "más gente que una que se ve clara** 🗳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. ¿Cuántos datos necesito para un margen de 1 punto?\n",
    "\n",
    "Despeja n de la fórmula del margen de error de una\n",
    "proporción y calcula cuántas ventas hacen falta para varios márgenes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = v['compro'].mean()\n",
    "\n",
    "for margen in [0.05, 0.03, 0.02, 0.01]:\n",
    "    necesarias = (1.96 ** 2) * p * (1 - p) / (margen ** 2)\n",
    "    print('para +/- %.0f puntos hacen falta %7.0f ventas'\n",
    "          % (100 * margen, necesarias))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "para +/- 5 puntos hacen falta     375 ventas\n",
    "para +/- 3 puntos hacen falta    1041 ventas\n",
    "para +/- 2 puntos hacen falta    2343 ventas\n",
    "para +/- 1 puntos hacen falta    9372 ventas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el famoso \"1.000 encuestados\" de los medios: es justo lo que hace\n",
    "falta para un margen de 3 puntos 📰\n",
    "\n",
    "Y fíjate en el salto final: bajar de 2 a 1 punto pasa de 2.344 a 9.375. Cuatro\n",
    "veces más gente para la mitad de margen, la raíz cuadrada de siempre.\n",
    "\n",
    "Truco de campo: si no sabes qué p esperar, usa 0,5. Es el peor caso, o sea que\n",
    "el número que te salga te va a alcanzar seguro 🛡️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Un intervalo sin fórmulas: bootstrap\n",
    "\n",
    "La mediana no tiene fórmula sencilla de error estándar.\n",
    "Calcula su intervalo remuestreando."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "medianas = [np.median(generador.choice(m.values, n)) for _ in range(2000)]\n",
    "\n",
    "print('mediana observada: %.2f' % m.median())\n",
    "print('intervalo del 95%%: [%.2f, %.2f]'\n",
    "      % (np.percentile(medianas, 2.5), np.percentile(medianas, 97.5)))\n",
    "print('error estandar de la mediana: %.2f' % np.std(medianas))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "mediana observada: 533.63\n",
    "intervalo del 95%: [509.82, 552.86]\n",
    "error estandar de la mediana: 11.36\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El bootstrap es de las ideas más bonitas que hay, y en tres líneas 🥾\n",
    "\n",
    "Lo que hace es tratar tu muestra como si fuera la población: saca muestras de\n",
    "ella con reposición, calcula el estadístico en cada una, y mira cómo varía. Sin\n",
    "fórmulas, sin suponer normalidad, y funciona con cualquier cosa que sepas\n",
    "calcular.\n",
    "\n",
    "Fíjate en un detalle: el error estándar de la mediana sale 11,36 y el de la\n",
    "media era 13,73. **La mediana es más precisa que la media aquí**, y\n",
    "tiene sentido: es la medida robusta, la cola no la zarandea."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El intervalo de una diferencia de medias\n",
    "\n",
    "¿Cuánto más vende un mayorista que una bodega? Da la\n",
    "diferencia con su intervalo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "may = v.loc[v['segmento'] == 'Mayorista', 'monto']\n",
    "bod = v.loc[v['segmento'] == 'Bodega', 'monto']\n",
    "\n",
    "dif = may.mean() - bod.mean()\n",
    "ee = np.sqrt(may.var() / len(may) + bod.var() / len(bod))\n",
    "\n",
    "print('mayorista: %.2f | bodega: %.2f' % (may.mean(), bod.mean()))\n",
    "print('diferencia: %.2f soles' % dif)\n",
    "print('intervalo del 95%%: [%.2f, %.2f]' % (dif - 1.96 * ee, dif + 1.96 * ee))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "mayorista: 1856.34 | bodega: 178.70\n",
    "diferencia: 1677.64 soles\n",
    "intervalo del 95%: [1625.89, 1729.39]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un mayorista compra **entre 1.625 y 1.729 soles más** que una\n",
    "bodega, por venta 💰\n",
    "\n",
    "Y esa forma de decirlo es la buena. Compara:\n",
    "\n",
    "- 😐 \"Hay diferencia significativa entre segmentos\".\n",
    "\n",
    "- 😍 \"Un mayorista compra entre 1.625 y 1.729 soles más por venta que una\n",
    "bodega\".\n",
    "\n",
    "La segunda tiene el tamaño, la unidad y la incertidumbre. Con eso alguien\n",
    "puede decidir algo. Con la primera, no 🤝\n",
    "\n",
    "Esa es la diferencia entre reportar significancia y reportar un intervalo, y\n",
    "es el motivo por el que muchos estadísticos prefieren los intervalos a las\n",
    "pruebas de hipótesis del capítulo siguiente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Fabrica el gráfico de cobertura a mano\n",
    "\n",
    "Genera 20 intervalos de muestras de 100 ventas y dibuja\n",
    "cuáles contienen el promedio real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "verdadera = m.mean()\n",
    "fallos = 0\n",
    "\n",
    "for i in range(20):\n",
    "    muestra = generador.choice(m.values, 100)\n",
    "    ee = muestra.std(ddof=1) / np.sqrt(100)\n",
    "    lo, hi = stats.t.interval(0.95, 99, muestra.mean(), ee)\n",
    "    dentro = lo <= verdadera <= hi\n",
    "    fallos += not dentro\n",
    "    print('%2d: [%7.2f, %7.2f]  %s' % (i + 1, lo, hi, 'ok' if dentro else 'FALLA'))\n",
    "\n",
    "print()\n",
    "print('fallaron %d de 20' % fallos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    " 1: [ 616.29,  926.35]  ok\n",
    " 2: [ 741.65, 1053.20]  ok\n",
    " 3: [ 698.04, 1016.73]  ok\n",
    " 4: [ 825.21, 1140.85]  FALLA\n",
    " 5: [ 588.81,  855.16]  ok\n",
    " 6: [ 677.77,  983.14]  ok\n",
    " 7: [ 657.62,  932.26]  ok\n",
    " 8: [ 655.14,  932.56]  ok\n",
    " 9: [ 729.09, 1025.84]  ok\n",
    "10: [ 708.92,  981.22]  ok\n",
    "11: [ 554.88,  829.98]  ok\n",
    "12: [ 695.73,  997.46]  ok\n",
    "13: [ 720.68, 1049.35]  ok\n",
    "14: [ 698.92,  964.96]  ok\n",
    "15: [ 607.22,  902.92]  ok\n",
    "16: [ 639.63,  905.61]  ok\n",
    "17: [ 622.90,  921.65]  ok\n",
    "18: [ 710.77, 1015.38]  ok\n",
    "19: [ 672.77,  981.01]  ok\n",
    "20: [ 654.24,  940.69]  ok\n",
    "\n",
    "fallaron 1 de 20\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Uno de veinte. Justo el 5% que promete el método 🎯\n",
    "\n",
    "Mira el número 4: va de 825,21 a 1.140,85, y el promedio real es 803,76. Se\n",
    "quedó por debajo del intervalo entero.\n",
    "\n",
    "Y aquí está la gracia: **ese intervalo se ve exactamente igual de bien\n",
    "que los otros diecinueve**. Es igual de ancho, está igual de centrado en\n",
    "su propia muestra, y no tiene ninguna marca que lo delate.\n",
    "\n",
    "Por eso la interpretación correcta habla del método y no de tu intervalo\n",
    "concreto. Tú tienes uno solo y no sabes si es de los 19 o es el 1 😅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El resumen que sí se puede llevar a una reunión\n",
    "\n",
    "Escribe una función que dé una proporción con su margen en\n",
    "lenguaje de persona."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def con_margen(serie, nombre):\n",
    "    s = serie.dropna()\n",
    "    pp = s.mean()\n",
    "    e = np.sqrt(pp * (1 - pp) / len(s))\n",
    "    return ('%s: %.1f%% (entre %.1f%% y %.1f%%, con %d casos)'\n",
    "            % (nombre, 100 * pp, 100 * (pp - 1.96 * e),\n",
    "               100 * (pp + 1.96 * e), len(s)))\n",
    "\n",
    "\n",
    "print(con_margen(v['compro'], 'tasa de cierre'))\n",
    "print(con_margen(v.loc[v['canal'] == 'WhatsApp', 'compro'], 'cierre por WhatsApp'))\n",
    "print(con_margen(v.loc[v['ciudad'] == 'lima', 'compro'], 'cierre en Lima'))\n",
    "print(con_margen(v.loc[(v['ciudad'] == 'lima') & (v['canal'] == 'WhatsApp'), 'compro'],\n",
    "                 'cierre en Lima por WhatsApp'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "tasa de cierre: 57.8% (entre 56.0% y 59.5%, con 3000 casos)\n",
    "cierre por WhatsApp: 65.5% (entre 62.0% y 69.0%, con 719 casos)\n",
    "cierre en Lima: 58.4% (entre 53.9% y 62.8%, con 471 casos)\n",
    "cierre en Lima por WhatsApp: 62.9% (entre 54.4% y 71.4%, con 124 casos)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira cómo se abre el intervalo según vas filtrando 📈\n",
    "\n",
    "Con 3.000 casos el margen es de menos de 2 puntos. Al llegar a \"Lima por\n",
    "WhatsApp\" quedan 124 filas y el intervalo va de 54,4% a 71,4%, o sea\n",
    "**17 puntos de ancho**.\n",
    "\n",
    "Eso quiere decir que con ese trozo de datos no puedes distinguir \"cierra\n",
    "igual que la media\" de \"cierra muchísimo mejor\". No es que no haya efecto: es\n",
    "que no tienes datos para verlo.\n",
    "\n",
    "Y aquí está la costumbre que quiero dejarte del capítulo entero:\n",
    "**cada vez que cortes los datos en trocitos, pon el número de casos al\n",
    "lado**. Un porcentaje sin su n no significa nada 🙏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Cuánto encoge el margen al crecer la muestra\n",
    "\n",
    "Calcula el intervalo del promedio con 30, 100, 500 y todos los pedidos, y mira cómo cambia el margen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in [30, 100, 500, len(m)]:\n",
    "    sub = m.sample(n, random_state=3)\n",
    "    lo2, hi2 = stats.t.interval(0.95, n - 1, sub.mean(), sub.std() / np.sqrt(n))\n",
    "    print('con %5d pedidos: de %7.2f a %7.2f, margen +/- %6.2f'\n",
    "          % (n, lo2, hi2, (hi2 - lo2) / 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "con    30 pedidos: de  680.58 a 1289.62, margen +/- 304.52\n",
    "con   100 pedidos: de  673.54 a  994.88, margen +/- 160.67\n",
    "con   500 pedidos: de  785.72 a  927.84, margen +/-  71.06\n",
    "con  3000 pedidos: de  776.84 a  830.68, margen +/-  26.92\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 304 soles de margen a 27 😮\n",
    "\n",
    "Y fíjate en el ritmo, que es lo que hay que interiorizar: pasar de 30 a 500 pedidos, o sea multiplicar por 16, divide el margen entre cuatro. **La raíz cuadrada otra vez.** Para la mitad de margen hace falta cuatro veces más gente, y para la décima parte, cien veces más.\n",
    "\n",
    "Esa es la tabla que hay que tener a mano cuando alguien pregunte \"¿y si encuestamos a más?\" 💰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. El precio de estar más seguro\n",
    "\n",
    "Saca el intervalo al 80%, 90%, 95% y 99% y compara los anchos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for conf in [0.80, 0.90, 0.95, 0.99]:\n",
    "    lo2, hi2 = stats.t.interval(conf, len(m) - 1, m.mean(), error_estandar)\n",
    "    print('al %2.0f%%: de %7.2f a %7.2f, ancho %6.2f'\n",
    "          % (conf * 100, lo2, hi2, hi2 - lo2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "al 80%: de  786.16 a  821.36, ancho  35.20\n",
    "al 90%: de  781.17 a  826.35, ancho  45.18\n",
    "al 95%: de  776.84 a  830.68, ancho  53.84\n",
    "al 99%: de  768.37 a  839.15, ancho  70.77\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estar más seguro cuesta ancho, y no sale gratis 📏\n",
    "\n",
    "Del 95% al 99% el intervalo crece un 31%. Y ahí está el intercambio que nadie te explica: **un intervalo más ancho es más probable que contenga la verdad y dice menos**. Si tu intervalo va de 0 a infinito, aciertas siempre y no has dicho nada.\n",
    "\n",
    "El 95% no tiene nada de sagrado, igual que el 0,05 del capítulo 12. Es una convención, y las convenciones se pueden discutir cuando hay una razón 🤝"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los intervalos se tocan y la diferencia es real"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de cerrar el capítulo, el atajo más repetido sobre intervalos, y por qué es falso 😬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Tienes los intervalos de los dos canales y quieres el de la diferencia. Restas los extremos, que es lo que parece razonable.\n",
    "\n",
    "```\n",
    "# WhatsApp 0.655  IC [0.620, 0.690]\n",
    "# Tienda   0.620  IC [0.585, 0.655]\n",
    "\n",
    "# restando extremos: [0.620 - 0.655, 0.690 - 0.585]\n",
    "#                  = [-0.035, 0.105]\n",
    "\n",
    "# el de la diferencia, bien calculado:\n",
    "#                  = [-0.014, 0.085]\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "El intervalo bien hecho es un 41% más estrecho 📐\n",
    "\n",
    "Restar los extremos supone lo peor de los dos mundos a la vez: que un canal se equivoca todo hacia arriba *y* el otro todo hacia abajo. Eso pasa poquísimas veces, así que el intervalo que sale es mucho más ancho que la incertidumbre de verdad.\n",
    "\n",
    "La cuenta correcta suma las varianzas y saca la raíz, que es lo mismo que hace la prueba: `1.96 * sqrt(pa*(1-pa)/na + pb*(1-pb)/nb)`.\n",
    "\n",
    "Y el precio de la versión ancha es real: hace que diferencias que sí se podían afirmar se queden en \"no sabemos\". Aquí el resultado no cambia, porque el cero está dentro de los dos. Pero con WhatsApp contra Marketplace la diferencia es de 19,4 puntos y el intervalo bien hecho va de 14,4 a 24,3, que es lo que se puede llevar a una reunión.\n",
    "\n",
    "Si la pregunta es sobre la diferencia, **calcula el intervalo de la diferencia**. Un solo intervalo y una sola lectura: si no incluye el cero, hay diferencia."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Dos segmentos tienen intervalos de confianza que se solapan. ¿Hay diferencia entre ellos?\n",
    "\n",
    "a) No se sabe: hay que calcular el intervalo de la diferencia\n",
    "\n",
    "b) No, porque si se solapan la diferencia no es real\n",
    "\n",
    "c) Sí, porque los centros son distintos\n",
    "\n",
    "d) Depende de cuánto se solapen\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Es lo que dice todo el mundo, y aquí Horeca y Minimarket se solapan y su diferencia sí es real.\n",
    "\n",
    "*c)* Los centros siempre son distintos. La pregunta es si esa diferencia aguanta el margen de error.\n",
    "\n",
    "*d)* No hay un porcentaje de solape que decida. Hay que calcular otra cosa.\n",
    "\n",
    "El error estándar de una diferencia es la raíz de la suma de cuadrados, que sale más chica que la suma."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📏 Margen de error = dos errores estándar. Aquí el monto promedio es 803,76\n",
    "con un margen de ±26,92.\n",
    "\n",
    "- 🎯 El 95% es del método, no de tu intervalo. Tú tienes uno y no sabes si es\n",
    "de los buenos.\n",
    "\n",
    "- ⚠️ Con muestras chicas de una columna con cola, el intervalo del 95% acierta\n",
    "el 93%. Con `unidades`, que es acampanada, acierta el 95,2% ya con\n",
    "20 datos.\n",
    "\n",
    "- 🔗 Intervalos que se solapan NO significan que no haya diferencia. Horeca y\n",
    "Minimarket se solapan y su diferencia va de 0,0140 a 0,1116, sin tocar el\n",
    "cero.\n",
    "\n",
    "- 🥾 El bootstrap da intervalos de cualquier cosa sin fórmulas. La mediana\n",
    "salió más precisa que la media: 11,36 contra 13,73.\n",
    "\n",
    "- 🧮 Para un margen de 3 puntos hacen falta 1.042 casos; para 1 punto, 9.375.\n",
    "\n",
    "- 🔢 Un porcentaje sin su número de casos no significa nada.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un número sin intervalo es una opinión con decimales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esta idea de dar un rango en vez de un número es la misma que hay detrás de\n",
    "la validación cruzada: un modelo tampoco tiene una nota, tiene un rango. Está en\n",
    "el [libro de machine learning](https://missyera.com/guias/machine-learning-desde-cero/) 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con el intervalo de la diferencia ya casi hemos hecho una prueba de\n",
    "hipótesis sin darnos cuenta: mirábamos si el cero estaba dentro. En el capítulo 12 le ponemos nombre, y sobre todo desmontamos el valor p, que es el número peor\n",
    "entendido de toda la estadística 🎓"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es un intervalo de confianza?Un rango donde es razonable que esté el valor de verdad, dado lo que viste en la muestra. Se reporta en vez de un número solo porque el número solo esconde que hubo muestra.\n",
    "\n",
    "¿Cómo se interpreta un intervalo de confianza del 95%?No es que haya 95% de probabilidad de que el valor esté ahí. Es que si repitieras el muestreo muchas veces, 95 de cada 100 intervalos calculados así contendrían el valor real.\n",
    "\n",
    "¿Qué es el margen de error?La mitad del ancho del intervalo. Cuando una encuesta dice \"más o menos 3 puntos\", eso es el margen de error.\n",
    "\n",
    "¿Cómo hago el intervalo más angosto?Con más datos, y crece con la raíz: para que el intervalo mida la mitad hace falta cuatro veces la muestra. Por eso pasar de mil a dos mil encuestados sirve menos de lo que la gente espera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 10 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/intervalos-de-confianza/\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
}
