{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Qué puedes calcular con cada columna\n",
    "\n",
    "Nominal, ordinal, discreta y continua. Y las cuentas que salen sin dar error y no significan nada.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 2 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/tipos-de-variable/\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": [
    "En el capítulo 1 vimos que las ventas se mueven 14,41% al mes, y que había\n",
    "nueve ciudades donde tenía que haber seis 🙃\n",
    "\n",
    "Este capítulo arregla eso y contesta una pregunta que parece de manual y es\n",
    "de las más prácticas del libro: **¿qué puedo calcular con cada\n",
    "columna?**\n",
    "\n",
    "Porque tu computadora no lo sabe. Le pides la media de lo que sea y te la\n",
    "da. El problema es que la mitad de esas medias no significan nada, y no hay\n",
    "ningún mensaje de error que te avise 😶\n",
    "\n",
    "Y dime si te suena: **¿alguna vez reportaste un promedio sin preguntarte si tenía sentido promediarlo?** Todas lo hicimos. Este capítulo va de que no vuelva a pasar 😶"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Primero la limpieza, que si no todo lo demás miente"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres cosas por orden.\n",
    "\n",
    "**Uno: las filas repetidas.** Se van primero, porque una fila\n",
    "duplicada cuenta dos veces en todas las medidas que vengan después."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "v = pd.read_csv(URL)\n",
    "print('filas:', len(v), '| duplicadas enteras:', v.duplicated().sum())\n",
    "\n",
    "v = v.drop_duplicates()\n",
    "print('quedan:', len(v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "37 filas idénticas. No es que se parezcan: son la misma venta escrita dos\n",
    "veces, con el mismo identificador y todo.\n",
    "\n",
    "**Dos: las cuatro Limas.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v['ciudad'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la trampa entera: `lima`, `Líma`,\n",
    "`Lima` y `LIMA`. Para ti son la misma ciudad. Para pandas\n",
    "son cuatro textos distintos, y ninguno se parece más al otro que a\n",
    "`Piura`.\n",
    "\n",
    "Se arregla en una línea encadenada:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v['ciudad'] = (v['ciudad'].str.strip().str.lower()\n",
    "               .str.normalize('NFKD')\n",
    "               .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "\n",
    "print(v['ciudad'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis ciudades 🎉 Y Lima pasa de aparentar 123 ventas a tener 471, que la\n",
    "pone empatada con Trujillo.\n",
    "\n",
    "Lo que hace cada trozo, por si te lo preguntas: `strip` quita\n",
    "espacios de los bordes, `lower` pasa todo a minúscula, y ese\n",
    "`normalize('NFKD')` con el `encode/decode` es el truco que\n",
    "convierte `í` en `i`. Descompone la letra acentuada en\n",
    "letra más tilde y luego tira lo que no sea ASCII.\n",
    "\n",
    "**Tres: los números que llegaron como texto.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v.dtypes.head(9))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`monto` es `str`, o sea texto. Y ya sabemos por qué:\n",
    "trae comas decimales al estilo peruano, y pandas ante la duda no toca nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "print(v['monto'].dtype, '| media: %.2f' % v['monto'].mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los cuatro tipos, con lo que puedes hacer con cada uno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora la parte que te va a servir para el resto del libro. Cada columna es\n",
    "de uno de estos cuatro tipos 👇"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Tipo | Qué es | En este archivo | Qué puedes calcular |\n",
    "|---|---|---|---|\n",
    "| **Nominal** | Categorías sin orden | ciudad, segmento, canal, categoria | Frecuencias y moda. Nada más. |\n",
    "| **Ordinal** | Categorías con orden, pero sin distancia fija | satisfaccion (1 a 5) | Mediana, percentiles, moda |\n",
    "| **Discreta** | Números que se cuentan, sin partes | unidades | Todo, y la media puede salir con decimales |\n",
    "| **Continua** | Cualquier valor dentro de un rango | monto, descuento | Todo: media, desviación, correlación |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La frontera que más se discute es la de ordinal. Satisfacción va de 1 a 5 y\n",
    "tiene orden clarísimo: 5 es mejor que 4. Pero **la distancia no es\n",
    "fija**. Pasar de 1 a 2 no es lo mismo, para el cliente, que pasar de 4 a\n",
    "5. Y como no lo es, sumar y dividir es discutible.\n",
    "\n",
    "Yo la uso así, y te lo digo como criterio práctico y no como ley: la mediana\n",
    "de satisfacción es la medida honesta; la media se puede reportar al lado, pero\n",
    "no se pelea por dos décimas 🤷"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ahora el error de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pídele a pandas la media de una columna de texto:"
   ]
  },
  {
   "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['ciudad'].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",
    "TypeError: Cannot perform reduction 'mean' with string dtype\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Perfecto, ¿no? Te para en seco. Ese error es tu amigo.\n",
    "\n",
    "El problema es el caso de al lado, que es el mismo disparate y **no\n",
    "para nada**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v[['id_venta', 'unidades', 'monto', 'satisfaccion', 'compro']]\n",
    "      .describe().round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la primera columna. **La media de `id_venta` es\n",
    "11499.50.**\n",
    "\n",
    "Eso es el número de factura promedio. No existe. No significa nada. No se\n",
    "puede hacer nada con él. Y ahí está, con dos decimales, tan formal como los\n",
    "demás 😅\n",
    "\n",
    "`id_venta` es un identificador: son números por comodidad, pero\n",
    "funcionan como nombres. Es tan nominal como `ciudad`. La diferencia\n",
    "es que ciudad se escribe con letras y por eso pandas te frena, y este se escribe\n",
    "con dígitos y por eso pandas calcula.\n",
    "\n",
    "**La lección: que una cuenta salga no quiere decir que signifique\n",
    "algo.** El único que sabe qué es cada columna eres tú.\n",
    "\n",
    "De paso, en esa misma tabla hay dos cosas más que vale la pena que veas:\n",
    "\n",
    "- 💸 El mínimo de `monto` es **-2497.72**. Hay ventas\n",
    "negativas. Las miramos en el capítulo 6.\n",
    "\n",
    "- 🕳️ El `count` de `satisfaccion` es **2769**\n",
    "y no 3000. Faltan 231 valores, y la media se calculó ignorándolos sin decir ni\n",
    "pío."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cómo se detecta el tipo cuando no conoces los datos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Truco de campo, y de los que más uso: cuenta cuántos valores distintos tiene\n",
    "cada columna 🔎"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v.nunique().sort_values())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo de arriba abajo y el archivo se te ordena solo:\n",
    "\n",
    "- 🔢 Lo de arriba, con 2 a 6 valores distintos, es categórico. Da igual que\n",
    "`compro` y `satisfaccion` estén escritos con números.\n",
    "\n",
    "- 📈 Lo de abajo, con cientos o miles de valores distintos, es continuo o\n",
    "identificador.\n",
    "\n",
    "- 🚩 Y hay una bandera roja evidente: `id_venta` tiene 3000\n",
    "valores distintos en 3000 filas. Una columna con tantos valores distintos como\n",
    "filas es un identificador, siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una media que sí significa algo, y mucho"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para que no te quedes con que las medias de columnas 0/1 son sospechosas,\n",
    "mira este caso, que es justo al revés:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('media de compro:  %.4f' % v['compro'].mean())\n",
    "print('cuantos compraron: %d de %d' % (v['compro'].sum(), len(v)))\n",
    "print('proporcion:       %.4f' % (v['compro'].sum() / len(v)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La media de una columna que solo tiene ceros y unos **es la\n",
    "proporción**. Exactamente el mismo número.\n",
    "\n",
    "Y eso es utilísimo: cada vez que quieras un porcentaje de \"cuántos\n",
    "cumplen X\", conviertes a 0/1 y pides la media. Lo vamos a usar en todo el libro,\n",
    "y en el capítulo 13 esa proporción es la que se compara entre grupos.\n",
    "\n",
    "O sea que `compro` es nominal, su media técnicamente no debería\n",
    "significar nada, y significa muchísimo. La regla no es mecánica: es que\n",
    "**tienes que saber qué representa el número** 🧠"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y dos más, que son este capítulo en estado puro"
   ]
  },
  {
   "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['ciudad'].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",
    "TypeError: Cannot perform reduction 'mean' with string dtype\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el capítulo entero en un mensaje: **a una nominal no se le calcula la media**. No es que quede feo, es que la operación no existe. Y fíjate que pandas te lo dice mirando el tipo, no el significado: lo que le molesta es que sea texto."
   ]
  },
  {
   "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['satisfaccion'].astype(int)\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",
    "IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer.Replace or remove non-finite values or cast to an integer typethat supports these values (e.g. 'Int64')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La satisfacción va de 1 a 5 y parece entera, pero le faltan valores. Y un entero no puede estar vacío: no existe el \"número entero ausente\". Por eso pandas la guarda como decimal en cuanto hay un solo hueco, y por eso pasarla a entero te obliga primero a decidir qué haces con los que faltan 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practica 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. ¿Cuántos clientes hay de verdad?\n",
    "\n",
    "El archivo tiene 3.000 ventas. ¿De cuántos clientes\n",
    "distintos? ¿Y cuántas ventas hace un cliente típico?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('clientes distintos:', v['cliente_id'].nunique())\n",
    "print('ventas por cliente: %.2f' % (len(v) / v['cliente_id'].nunique()))\n",
    "print(v['cliente_id'].value_counts().describe().round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "clientes distintos: 617\n",
    "ventas por cliente: 4.86\n",
    "count    617.00\n",
    "mean       4.86\n",
    "std        2.19\n",
    "min        1.00\n",
    "25%        3.00\n",
    "50%        5.00\n",
    "75%        6.00\n",
    "max       14.00\n",
    "Name: count, dtype: float64\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "617 clientes y 3.000 ventas: **4,86 ventas por cliente**.\n",
    "\n",
    "Esto cambia cómo hay que leer el archivo entero, y es de las cosas que más\n",
    "se pasan por alto. Las 3.000 filas **no son 3.000 clientes\n",
    "independientes**: hay uno que aparece 14 veces.\n",
    "\n",
    "Cuando el mismo cliente sale muchas veces, sus filas se parecen entre ellas\n",
    "más de lo normal, y eso hace que cualquier prueba estadística crea que tiene más\n",
    "información de la que tiene. Es una de las cosas que invalidan análisis y sale\n",
    "en el capítulo 17."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Caza las categóricas disfrazadas de números\n",
    "\n",
    "Escribe algo que recorra las columnas numéricas y te avise\n",
    "de cuáles son sospechosas de ser categorías."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in v.select_dtypes('number').columns:\n",
    "    distintos = v[col].nunique()\n",
    "    aviso = '  <-- categorica disfrazada' if distintos <= 10 else ''\n",
    "    print('%-24s %5d distintos%s' % (col, distintos, aviso))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "id_venta                  3000 distintos\n",
    "unidades                    30 distintos\n",
    "monto                     2964 distintos\n",
    "descuento                  251 distintos\n",
    "satisfaccion                 5 distintos  <-- categorica disfrazada\n",
    "compro                       2 distintos  <-- categorica disfrazada\n",
    "monto_final_facturado     1723 distintos\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Caza las dos que buscábamos. Y fíjate que `unidades`, con 30\n",
    "valores, se queda fuera: es discreta de verdad, no una categoría.\n",
    "\n",
    "El umbral de 10 es mío y es discutible, que quede claro. Sirve como primer\n",
    "filtro para mirar, no como sentencia. Lo que este código hace es ahorrarte\n",
    "abrir 14 columnas a mano 🙌"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. La media que ignora los huecos\n",
    "\n",
    "La columna `descuento` tiene vacíos. Calcula su\n",
    "media, y luego calcúlala tratando los vacíos como cero. ¿Cuál está bien?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('filas totales:      ', len(v))\n",
    "print('descuentos con dato:', v['descuento'].count())\n",
    "print('media ignorando huecos: %.4f' % v['descuento'].mean())\n",
    "print('media con huecos = 0:   %.4f' % v['descuento'].fillna(0).mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "filas totales:       3000\n",
    "descuentos con dato: 2405\n",
    "media ignorando huecos: 0.1252\n",
    "media con huecos = 0:   0.1004\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "12,52% contra 10,04%. Una diferencia de dos puntos y medio, decidida\n",
    "enteramente por lo que supongas de 595 celdas vacías.\n",
    "\n",
    "¿Cuál está bien? **Depende de qué signifique el vacío, y eso no está en\n",
    "los datos.** Si el sistema deja el campo en blanco cuando no hubo\n",
    "descuento, el vacío es un cero y la buena es 10,04%. Si el campo se llena\n",
    "siempre y esos 595 se perdieron al migrar, el vacío es un desconocido y la buena\n",
    "es 12,52%.\n",
    "\n",
    "Lo que no puedes hacer es no decidir. Porque `mean()` decide por\n",
    "ti: ignora los nulos en silencio y te devuelve 12,52% sin avisarte de que se\n",
    "saltó el 20% de las filas.\n",
    "\n",
    "Esto se pregunta a quien mantiene el sistema. Es la clase de pregunta que\n",
    "distingue a alguien que analiza datos de alguien que ejecuta celdas 😌"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Fabrica una variable continua: la recencia\n",
    "\n",
    "Con `fecha` y `fecha_ultima_compra`,\n",
    "calcula cuántos días hacía que ese cliente no compraba. Mira bien el resultado\n",
    "antes de darlo por bueno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "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",
    "\n",
    "dias = (v['fecha'] - v['fecha_ultima_compra']).dt.days\n",
    "print(dias.describe().round(2))\n",
    "print('negativos:', (dias < 0).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "count    2456.00\n",
    "mean       72.82\n",
    "std       190.33\n",
    "min      -385.00\n",
    "25%       -65.00\n",
    "50%        69.00\n",
    "75%       213.25\n",
    "max       522.00\n",
    "dtype: float64\n",
    "negativos: 904\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí hay que parar 🛑\n",
    "\n",
    "**904 valores negativos.** O sea 904 ventas donde la \"última\n",
    "compra\" del cliente ocurrió *después* de la venta que estamos mirando.\n",
    "\n",
    "Eso no es un dato raro, es un dato imposible tal como lo estamos leyendo. La\n",
    "explicación más probable es que la columna no sea \"la compra anterior a esta\"\n",
    "sino \"la última compra registrada del cliente hasta hoy\", que es un dato del\n",
    "cliente y no de la venta. Con ese significado los negativos son normales: la\n",
    "venta que miramos es vieja y el cliente volvió después.\n",
    "\n",
    "La conclusión práctica es dura y es la correcta: **esta columna no\n",
    "sirve como recencia y hay que preguntar antes de usarla**. Si la metes en\n",
    "un análisis tal cual, la mitad de tus filas dice que el cliente compró hace -65\n",
    "días.\n",
    "\n",
    "Lo que hicimos aquí, mirar el `describe()` de una variable recién\n",
    "creada, es lo que evitó publicarlo. Cuesta diez segundos y salva informes 🙏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Ordinal: ¿media o mediana?\n",
    "\n",
    "Compara la satisfacción entre segmentos con las dos\n",
    "medidas. ¿Cuál cuenta mejor lo que pasa?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(v.groupby('segmento')['satisfaccion']\n",
    "      .agg(['mean', 'median', 'count']).round(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "              mean  median  count\n",
    "segmento\n",
    "Bodega      2.9603     3.0    655\n",
    "Horeca      3.0332     3.0    692\n",
    "Mayorista   2.9927     3.0    684\n",
    "Minimarket  3.0474     3.0    738\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las medias van de 2,96 a 3,05. Las medianas son todas 3.\n",
    "\n",
    "Y las dos están contando lo mismo: **aquí no pasa nada**. La\n",
    "satisfacción no distingue segmentos.\n",
    "\n",
    "Fíjate en lo fácil que sería hacer el ridículo con esto. Con las medias en la\n",
    "mano, alguien escribe \"Minimarket es nuestro segmento más satisfecho, con 3,05\n",
    "frente al 2,96 de Bodega\". Suena a hallazgo. Son nueve centésimas en una escala\n",
    "de 1 a 5, con setecientas filas por grupo.\n",
    "\n",
    "La mediana, al ser toda 3, te lo deja más difícil de contar mal. Por eso me\n",
    "gusta para ordinales: **no te deja fingir precisión que no tienes** 🎯\n",
    "\n",
    "En el capítulo 14 le ponemos número a esto y sale que la diferencia es\n",
    "minúscula incluso comparada con lo que varía la satisfacción dentro de un mismo\n",
    "segmento."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. La columna que solo existe si la venta se cerró\n",
    "\n",
    "Saca la media de `monto_final_facturado` sobre\n",
    "todas las filas y luego solo sobre las que compraron. Explica la diferencia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('media sobre las 3000 filas:      %.2f' % v['monto_final_facturado'].mean())\n",
    "print('media solo sobre los que compraron: %.2f'\n",
    "      % v.loc[v['compro'] == 1, 'monto_final_facturado'].mean())\n",
    "print('cuantos ceros:', (v['monto_final_facturado'] == 0).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "media sobre las 3000 filas:      481.26\n",
    "media solo sobre los que compraron: 833.10\n",
    "cuantos ceros: 1267\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "481 contra 833. Casi el doble, y las dos son \"la media de la misma\n",
    "columna\" 🫠\n",
    "\n",
    "Lo que pasa es que 1.267 filas tienen un cero, y esos ceros no son ventas de\n",
    "cero soles: son ventas que no ocurrieron. La columna solo se llena cuando\n",
    "`compro` vale 1.\n",
    "\n",
    "El número que un negocio quiere oír, el ticket promedio, es el de 833,10, y\n",
    "se calcula solo sobre las ventas cerradas. El 481,26 no es el ticket de nada: es\n",
    "una mezcla de tickets y de no-ventas.\n",
    "\n",
    "Y ojo con esta columna, porque tiene otra trampa peor que la usamos en el\n",
    "libro de machine learning: como solo existe si la venta se cerró, meterla en un\n",
    "modelo que predice si la venta se va a cerrar es hacer trampa. Se llama fuga de\n",
    "información y está contada en [el\n",
    "capítulo de fuga del libro de ML](https://missyera.com/guias/machine-learning-desde-cero/fuga-de-informacion/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. La limpieza completa, en una función\n",
    "\n",
    "Junta todo lo del capítulo en algo que puedas pegar al\n",
    "inicio de cualquier cuaderno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def carga_limpia(url):\n",
    "    \"\"\"Lee el archivo y deja cada columna en su tipo.\"\"\"\n",
    "    v = pd.read_csv(url).drop_duplicates()\n",
    "\n",
    "    v['ciudad'] = (v['ciudad'].str.strip().str.lower()\n",
    "                   .str.normalize('NFKD')\n",
    "                   .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "\n",
    "    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "\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",
    "\n",
    "    return v\n",
    "\n",
    "limpio = carga_limpia(URL)\n",
    "print(limpio.shape)\n",
    "print(limpio['ciudad'].nunique(), 'ciudades |',\n",
    "      limpio['monto'].dtype, '| fechas:', limpio['fecha'].dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "(3000, 14)\n",
    "6 ciudades | float64 | fechas: datetime64[us]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta función es la que abre todos los capítulos que vienen. Cópiala 📌\n",
    "\n",
    "Fíjate en ese `falta = f.isna() & v[col].notna()`: solo\n",
    "reintenta las fechas que *tenían texto* y no se pudieron convertir. Sin\n",
    "la segunda condición intentaría rellenar también los huecos de verdad, y esos\n",
    "tienen que seguir siendo huecos 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. La ficha de tipos de toda la tabla\n",
    "\n",
    "Saca de un tirón el tipo que pandas le puso a cada columna y cuántos valores distintos tiene."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in v.columns:\n",
    "    print('%-22s %-16s %5d distintos' % (col, v[col].dtype, v[col].nunique()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "id_venta               int64             3000 distintos\n",
    "fecha                  datetime64[us]     537 distintos\n",
    "cliente_id             str                617 distintos\n",
    "ciudad                 str                  6 distintos\n",
    "segmento               str                  4 distintos\n",
    "canal                  str                  4 distintos\n",
    "categoria              str                  5 distintos\n",
    "unidades               int64               30 distintos\n",
    "monto                  float64           2964 distintos\n",
    "descuento              float64            251 distintos\n",
    "fecha_ultima_compra    datetime64[us]     399 distintos\n",
    "satisfaccion           float64              5 distintos\n",
    "compro                 int64                2 distintos\n",
    "monto_final_facturado  float64           1723 distintos\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta tabla es lo primero que yo miro de un archivo que no conozco, y tarda un segundo 📋\n",
    "\n",
    "Fíjate en las dos columnas juntas, que es donde está la gracia. `satisfaccion` es `float64` y tiene cinco valores distintos: pandas la guarda como decimal y en realidad es **ordinal**. Y `compro` es `int64` con dos valores, o sea binaria disfrazada de número.\n",
    "\n",
    "El tipo que pone pandas dice cómo está guardada. El tipo que importa lo decides tú mirando cuántos valores distintos hay y qué significan 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Convertir una ordinal en ordinal de verdad\n",
    "\n",
    "Dile a pandas que el segmento tiene orden, y comprueba que después se puede comparar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "orden = ['Bodega', 'Minimarket', 'Horeca', 'Mayorista']\n",
    "v['segmento_orden'] = pd.Categorical(v['segmento'], categories=orden, ordered=True)\n",
    "print(v.groupby('segmento_orden', observed=True)['monto'].median().round(2).to_string())\n",
    "print()\n",
    "print('ahora si se puede preguntar por el orden:')\n",
    "print((v['segmento_orden'] > 'Minimarket').sum(), 'pedidos son de Horeca o Mayorista')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "segmento_orden\n",
    "Bodega         183.26\n",
    "Minimarket     413.74\n",
    "Horeca         743.00\n",
    "Mayorista     1869.26\n",
    "\n",
    "ahora si se puede preguntar por el orden:\n",
    "1492 pedidos son de Horeca o Mayorista\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos cosas cambian y las dos importan 💜\n",
    "\n",
    "La primera: la tabla sale **en el orden del negocio** y no en alfabético. Con eso solo, ya se lee que la mediana sube de 183 a 1.869 según el escalón, que es una historia entera.\n",
    "\n",
    "La segunda: ahora `> 'Minimarket'` significa algo. Sobre texto suelto esa comparación te habría dado el orden del diccionario, que aquí no quiere decir nada. Convertir una ordinal en ordinal de verdad es una línea y te ahorra errores todo el resto del análisis 🔑"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La satisfacción media que no describe a nadie"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora el número más publicado y menos defendible que existe en las empresas. Está en la mitad de los reportes que he visto 😳"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Reportas la satisfacción media del mes. Sale 3,01 sobre 5, o sea justo en el medio, así que escribes que los clientes están neutrales.\n",
    "\n",
    "```\n",
    "print(round(v['satisfaccion'].mean(), 2))\n",
    "# 3.01\n",
    "\n",
    "print(v['satisfaccion'].value_counts().sort_index())\n",
    "# 1.0    560\n",
    "# 2.0    537\n",
    "# 3.0    551\n",
    "# 4.0    558\n",
    "# 5.0    563\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Nadie está neutral 😳 Hay 1.097 clientes molestos (1 y 2) y 1.121 contentos (4 y 5). El promedio da 3 porque los dos bandos se cancelan, no porque haya un bando en el medio.\n",
    "\n",
    "Y hay algo peor de fondo: una escala de 1 a 5 es **ordinal**. Sabemos que 4 es mejor que 3, pero no que la distancia entre 3 y 4 sea la misma que entre 1 y 2. Y si las distancias no significan nada, sumarlas y dividirlas tampoco.\n",
    "\n",
    "La media de una escala de satisfacción es de los números más publicados y menos defendibles que hay. Se ve técnica, cabe en una diapositiva y esconde exactamente lo que había que contar.\n",
    "\n",
    "Lo que sí se reporta: **el reparto entero**, o el porcentaje de cada extremo. \"El 37% puntúa 1 o 2\" es un número con el que se puede hacer algo. \"La media es 3,01\" no."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "describe() te devuelve que la media de id_venta es 11499,50. ¿Qué haces?\n",
    "\n",
    "a) Nada, es un identificador y esa media no significa nada\n",
    "\n",
    "b) La reporto como el número de factura promedio\n",
    "\n",
    "c) Reviso si hay un error en los datos\n",
    "\n",
    "d) La uso para detectar facturas fuera de rango\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Ese número no existe en ninguna factura. Piensa qué representa la columna, no cómo está escrita.\n",
    "\n",
    "*c)* Los datos están bien. El problema no es el dato, es la operación.\n",
    "\n",
    "*d)* Las facturas van correlativas, así que ese rango no dice nada de si una venta es rara.\n",
    "\n",
    "Un identificador es tan nominal como una ciudad: lo único que cambia es que se escribe con dígitos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🧹 Duplicados primero, textos normalizados después, tipos al final. En ese\n",
    "orden, porque cada paso ensucia al siguiente si se hace al revés.\n",
    "\n",
    "- 🏙️ Cuatro formas de escribir Lima son cuatro ciudades para el software.\n",
    "strip, lower y normalize lo arreglan en una línea.\n",
    "\n",
    "- 📊 Nominal solo admite frecuencias y moda; ordinal, mediana; discreta y\n",
    "continua, todo.\n",
    "\n",
    "- 🤖 El software no conoce esa diferencia. `describe()` te dará la\n",
    "media de un número de factura y se quedará tan tranquilo.\n",
    "\n",
    "- 🔎 `nunique()` es el mejor detector de tipos que hay. Pocos\n",
    "valores distintos, categoría; tantos valores como filas, identificador.\n",
    "\n",
    "- 0️⃣ La media de una columna de ceros y unos es la proporción, y esa sí\n",
    "significa muchísimo.\n",
    "\n",
    "- 🕳️ `mean()` ignora los nulos sin avisar. Aquí eran 595 celdas y\n",
    "movían el descuento medio dos puntos y medio.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tu computadora calcula la media de lo que sea. Decidir si eso significa\n",
    "algo es tuyo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué viene ahora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 3 nos metemos con la medida más usada y peor usada del mundo:\n",
    "la media. Con un caso donde la media dice 803 soles, la mediana dice 533 y la\n",
    "diferencia entre las dos es una decisión de negocio 💰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 2 de **Estadística desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/estadistica-desde-cero/tipos-de-variable/\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
}
