{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# De dónde salen los datos que estás mirando\n",
    "\n",
    "Muestreo aleatorio, de conveniencia y estratificado, medidos sobre la misma población. Y por qué más datos no arreglan una muestra torcida.\n",
    "\n",
    "Cuaderno de práctica del capítulo 9 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/de-donde-salen-los-datos/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los ocho capítulos anteriores dan por hecho una cosa que nadie comprueba: que\n",
    "los datos que tienes delante representan a lo que quieres saber 🎯\n",
    "\n",
    "Y ese supuesto es el que más veces se cae. Un promedio bien calculado sobre\n",
    "una muestra torcida es un número perfecto de algo que no te interesa.\n",
    "\n",
    "Aquí tenemos una ventaja que en la vida real no vas a tener nunca:\n",
    "**conocemos la población entera**. Son 3.000 pedidos y podemos\n",
    "mirarlos todos. Así que podemos sacar muestras de tres maneras distintas y\n",
    "comparar cada una contra la verdad."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\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).dropna(subset=['monto'])\n",
    "POBLACION = v['monto'].mean()\n",
    "\n",
    "print('pedidos en total: %d' % len(v))\n",
    "print('ticket promedio de TODOS: %.2f' % POBLACION)\n",
    "print()\n",
    "print(v.groupby('segmento')['monto'].agg(['count', 'mean']).round(2)\n",
    "      .sort_values('mean').to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Guárdate esa tabla, que es el motor de todo lo que viene 🔑\n",
    "\n",
    "Un mayorista compra **diez veces** lo que una bodega: 1.856,34\n",
    "contra 178,70. Cuando dentro de tu población hay grupos así de distintos, a quién\n",
    "metes en la muestra deja de ser un detalle administrativo.\n",
    "\n",
    "Y antes de seguir, contéstate esto: **¿hacia dónde se te iría el número\n",
    "si preguntaras solo a los clientes que tienes más a mano?** 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que pasa cuando se saca bien"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Muestreo aleatorio simple: cada pedido tiene exactamente la misma probabilidad\n",
    "de entrar. Lo sacamos 500 veces para no juzgar por una sola:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def promedios(sacar, veces=500):\n",
    "    \"\"\"El ticket promedio de `veces` muestras sacadas con `sacar`.\"\"\"\n",
    "    return np.array([sacar(s)['monto'].mean() for s in range(veces)])\n",
    "\n",
    "\n",
    "al_azar = promedios(lambda s: v.sample(200, random_state=s))\n",
    "print('poblacion:              %.2f' % POBLACION)\n",
    "print('promedio de 500 muestras: %.2f' % al_azar.mean())\n",
    "print('sesgo:                    %+.2f' % (al_azar.mean() - POBLACION))\n",
    "print('cuanto varian entre si:   %.2f' % al_azar.std())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuarenta y nueve céntimos de sesgo sobre 803 soles. Eso es cero 💚\n",
    "\n",
    "Y fíjate en que hay **dos números distintos** y no uno, porque\n",
    "son dos problemas distintos:\n",
    "\n",
    "- 📍 El **sesgo** es si le apuntas al sitio correcto. Aquí, sí.\n",
    "\n",
    "- 📏 La **variación** es lo que se mueve una muestra de otra.\n",
    "Cincuenta soles, que no es poco.\n",
    "\n",
    "La variación se arregla con más datos, y eso ya lo sabías por el capítulo\n",
    "10. El sesgo no. Y esa diferencia es el capítulo\n",
    "entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora una muestra como se saca de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nadie encuesta al azar. Se encuesta a quien contesta, a quien pasa por la\n",
    "tienda, a quien el vendedor visita. Y el vendedor no visita a todos igual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "VISIBILIDAD = {'Bodega': 1, 'Minimarket': 2, 'Horeca': 4, 'Mayorista': 8}\n",
    "peso = v['segmento'].map(VISIBILIDAD)\n",
    "\n",
    "conveniencia = promedios(\n",
    "    lambda s: v.sample(200, weights=peso, replace=True, random_state=s))\n",
    "\n",
    "print('lo que el vendedor visita, por cada bodega:')\n",
    "for seg, cuantas in VISIBILIDAD.items():\n",
    "    print('   %-11s %d' % (seg, cuantas))\n",
    "print()\n",
    "print('poblacion:                 %.2f' % POBLACION)\n",
    "print('lo que mide la encuesta:   %.2f' % conveniencia.mean())\n",
    "print('sesgo:                     %+.2f soles' % (conveniencia.mean() - POBLACION))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatrocientos cincuenta y cinco soles 😱\n",
    "\n",
    "El ticket real es 803,76 y la encuesta dice 1.258,98. Un **57% de\n",
    "más**, y ni una sola línea del cálculo está mal: el promedio está bien\n",
    "hecho, la muestra tiene 200 pedidos de verdad, todo corre. Lo que está mal es\n",
    "quién entró.\n",
    "\n",
    "Y no hace falta que nadie mienta ni haga trampa. Basta con que el vendedor\n",
    "pase más por donde le compran más, que es lo que hace cualquier vendedor\n",
    "sensato."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La tabla que hay que enseñarle a quien pide \"más datos\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando un número no cuadra, lo primero que dice todo el mundo es \"hay que\n",
    "recoger más\". Vamos a ver si eso arregla algo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('%8s %12s %10s %12s' % ('n', 'promedio', 'sesgo', 'variacion'))\n",
    "for n in [100, 400, 1600, 6400]:\n",
    "    m = promedios(lambda s: v.sample(n, weights=peso, replace=True, random_state=s), 200)\n",
    "    print('%8d %12.2f %+10.2f %12.2f' % (n, m.mean(), m.mean() - POBLACION, m.std()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léela de arriba abajo dos veces 😬\n",
    "\n",
    "La columna de la derecha baja bien: de 86 a 11. Con 6.400 encuestas tu número\n",
    "es **ocho veces más estable** que con 100.\n",
    "\n",
    "Y la del medio no se mueve. 453, 451, 450, 452. Sesenta y cuatro veces más\n",
    "datos y el error sigue ahí entero.\n",
    "\n",
    "O sea que recoger más datos de la misma manera torcida te deja\n",
    "**más seguro de un número equivocado**. Que es peor que estar\n",
    "inseguro, porque ahora lo defiendes en la reunión con un intervalo estrecho al\n",
    "lado."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Más datos arreglan la variación y no arreglan el sesgo. Una muestra torcida con muchas filas es una muestra torcida con un intervalo estrecho."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cómo se arregla, que es más fácil de lo que parece"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si el problema es que unos grupos entran de más y otros de menos, la solución\n",
    "es sacar de cada grupo lo que le toca. Eso se llama **muestreo\n",
    "estratificado**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "reparto = v['segmento'].value_counts(normalize=True)\n",
    "print('como es el negocio de verdad:')\n",
    "print((reparto * 100).round(1).to_string())\n",
    "\n",
    "\n",
    "def estratificada(s):\n",
    "    trozos = [v[v['segmento'] == seg].sample(int(round(200 * w)), random_state=s)\n",
    "              for seg, w in reparto.items()]\n",
    "    return pd.concat(trozos)\n",
    "\n",
    "\n",
    "estrato = promedios(estratificada)\n",
    "print()\n",
    "print('%-22s %10s %10s %10s' % ('como se saca', 'promedio', 'sesgo', 'variacion'))\n",
    "for nombre, m in [('al azar', al_azar), ('por conveniencia', conveniencia),\n",
    "                  ('estratificada', estrato)]:\n",
    "    print('%-22s %10.2f %+10.2f %10.2f' % (nombre, m.mean(), m.mean() - POBLACION, m.std()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la última fila entera, que tiene dos buenas noticias 💜\n",
    "\n",
    "El sesgo se fue, como era de esperar. Pero además **la variación baja de\n",
    "50,10 a 28,62**, un 43% menos, con exactamente el mismo número de\n",
    "encuestas.\n",
    "\n",
    "Eso no es magia: al forzar que cada segmento aparezca en su proporción, quitas\n",
    "de en medio la lotería de cuántos mayoristas te tocaron esta vez. Estratificar no\n",
    "solo corrige, también **afina gratis**, y por eso es lo que hacen\n",
    "las encuestas serias.\n",
    "\n",
    "La condición para poder hacerlo es una y hay que decirla: tienes que\n",
    "**conocer el reparto real de la población**. Aquí lo conocemos\n",
    "porque tenemos la base entera. En una encuesta de calle se saca del censo, del\n",
    "padrón de clientes o de lo que haya, y si no lo tienes, no puedes estratificar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que sale al inventarse los pesos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y este sale el primer día que alguien decide pesar por algo:"
   ]
  },
  {
   "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.sample(200, weights=v['monto'] - POBLACION, random_state=0)\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: weight vector many not include negative values\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pesar por \"cuánto se aleja del promedio\" suena razonable hasta que lo escribes:\n",
    "la mitad de los pedidos están por debajo, así que la mitad de los pesos salen\n",
    "negativos. Y un peso negativo no significa nada, porque no puedes meter a alguien\n",
    "*menos* de cero veces 🚩\n",
    "\n",
    "Lo de *many* en vez de *may* es una errata de pandas, no mía. La\n",
    "dejo tal cual porque es lo que vas a ver en tu pantalla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los cuatro muestreos que se usan, en una tabla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Cómo se saca | Cuándo | Qué le pasa |\n",
    "|---|---|---|\n",
    "| **Aleatorio simple** | Cuando tienes la lista completa de clientes | Sin sesgo. Es el patrón contra el que se compara todo |\n",
    "| **Estratificado** | Cuando hay grupos muy distintos y sabes su peso real | Sin sesgo y con menos variación. El mejor si puedes |\n",
    "| **Por conglomerados** | Cuando visitar cuesta: eliges 3 ciudades y encuestas todo dentro | Barato, y con más variación. Vale si eliges los conglomerados al azar |\n",
    "| **Por conveniencia** | Cuando preguntas a quien está a mano, que es lo que pasa casi siempre | Sesgo desconocido. Lo medido aquí: +455 soles |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La cuarta fila es la que de verdad usas, y no pasa nada por usarla: lo que no\n",
    "puedes es olvidarte de que la usaste. **Una encuesta de conveniencia se\n",
    "reporta diciendo a quién se preguntó**, y entonces quien lee decide cuánto\n",
    "se lo cree 🔍\n",
    "\n",
    "Y hay un quinto caso que no es un método sino una avería: la **no\n",
    "respuesta**. Mandas la encuesta al azar, bien sacada, y contesta el 12%.\n",
    "Ese 12% ya no es una muestra aleatoria, es una muestra de conveniencia de la\n",
    "gente a la que le apetece contestar. El sesgo entra por ahí aunque el diseño\n",
    "fuera perfecto.\n",
    "\n",
    "Si quieres el detalle formal, el\n",
    "[INEI](https://www.inei.gob.pe/) publica las fichas técnicas de sus\n",
    "encuestas con el diseño muestral entero, y son de lo mejor que hay para ver esto\n",
    "escrito en serio y sobre el Perú 📄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Un equipo quiere una encuesta estratificada y escribe los pesos a mano. La muestra sale, el código no se queja, y el ticket promedio les da 547,81 cuando el real es 803,76.\n",
    "\n",
    "```\n",
    "PESOS = {'Bodega': 1, 'Minimarket': 2, 'Horeca': 4}\n",
    "peso = v['segmento'].map(PESOS)\n",
    "\n",
    "muestra = v.sample(200, weights=peso, replace=True, random_state=0)\n",
    "print(muestra['monto'].mean())    # 547.81\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis. El 1 es la trampa de arriba ejecutada, y el 4 es el que te va a servir\n",
    "en el trabajo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Olvídate de un segmento a propósito\n",
    "\n",
    "Quita `Mayorista` del diccionario de pesos y mira\n",
    "qué sale."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cambia lo que el vendedor visita\n",
    "\n",
    "Prueba con `VISIBILIDAD` al revés, o sea que\n",
    "visite ocho veces más a las bodegas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Estratifica por ciudad en vez de por segmento\n",
    "\n",
    "Repite el estratificado usando `ciudad` y compara\n",
    "la variación con la del estratificado por segmento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La comprobación que se hace en el trabajo\n",
    "\n",
    "Escríbela como función y déjala puesta: comparar el reparto\n",
    "de tu muestra contra el de la población."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La no respuesta, simulada\n",
    "\n",
    "Saca una muestra aleatoria bien hecha de 500 y después quita\n",
    "al azar el 88%, pero con más probabilidad de quedarse los mayoristas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Cuánto cuesta el sesgo en soles\n",
    "\n",
    "Si tomaras una decisión de compra de inventario con el\n",
    "promedio de la encuesta de conveniencia en vez del real, ¿de cuánto sería el\n",
    "error para 10.000 pedidos previstos?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Cuánto se aleja una muestra según su tamaño\n",
    "\n",
    "Saca trescientas muestras de cada tamaño y mide, en soles, a qué distancia queda su promedio del promedio real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Al azar y estratificada, una al lado de la otra\n",
    "\n",
    "Saca 120 filas de las dos maneras y compara el reparto de segmentos de cada una contra el reparto real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu encuesta a 6.000 clientes dice que el ticket promedio es 1.250 soles, y la contabilidad dice 800. ¿Qué miras primero?\n",
    "\n",
    "a) A quién se le preguntó, y si ese reparto se parece al de tus clientes\n",
    "\n",
    "b) Si 6.000 son suficientes\n",
    "\n",
    "c) Si hay atípicos que estén subiendo el promedio\n",
    "\n",
    "d) Si la encuesta se hizo en un mes raro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 Sesgo y variación son dos cosas distintas. Sesgo es apuntar mal, variación\n",
    "es que te tiemble el pulso.\n",
    "\n",
    "- 📈 Más datos arreglan la variación (de 86 a 11) y no tocan el sesgo (453 y\n",
    "sigue en 452 con 64 veces más filas).\n",
    "\n",
    "- 🚪 Una muestra de conveniencia da 1.258,98 donde la verdad es 803,76, y todo\n",
    "el cálculo está bien hecho.\n",
    "\n",
    "- 🧱 Estratificar quita el sesgo y además baja la variación un 43% con el mismo\n",
    "número de encuestas, si conoces el reparto real.\n",
    "\n",
    "- 🔍 Después de muestrear, cuenta siempre qué hay dentro de la muestra. Ahí se\n",
    "ve lo que falta.\n",
    "\n",
    "El capítulo 10 le pone un intervalo a lo que\n",
    "mediste, y ahora ya sabes que ese intervalo solo vale si la muestra estaba bien\n",
    "sacada. El sesgo no entra en ningún intervalo.\n",
    "\n",
    "Y si quieres el vocabulario suelto de todo esto, está definido en dos líneas\n",
    "por término en el [glosario de IA](https://missyera.com/glosario-ia/) 📖\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 9 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/de-donde-salen-los-datos/\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
}
