{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Gráficos que se entienden\n",
    "\n",
    "matplotlib sin adornos, y la regla que vale más que todas las opciones de color juntas.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 15 de **Python 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/python-desde-cero/graficos/\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": [
    "## Antes de empezar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# Este capitulo abre el archivo por su nombre, sin URL, que es lo que se hace\n",
    "# cuando lo tienes al lado. Aqui se baja primero para que exista.\n",
    "for nombre in ['ventas-miss-yera.csv']:\n",
    "    urllib.request.urlretrieve(f\"https://missyera.com/static/datasets/{nombre}\", nombre)\n",
    "    print(nombre, \"lista\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Vamos a hacer que se vea"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya sabes cargar, limpiar y agrupar. Ahora falta la parte que decide si a\n",
    "alguien le importa: que se entienda de un vistazo 📊\n",
    "\n",
    "Y te lo digo de entrada, porque es la lección del capítulo:\n",
    "**lo que hace que un gráfico funcione no son los colores**. Son tres\n",
    "decisiones que se toman antes de escribir una línea."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres reglas, antes que el código"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 **El tipo sale de la pregunta**, no del gusto. Comparar\n",
    "categorías es barras. Ver evolución en el tiempo es líneas. Ver cómo se reparte\n",
    "una variable es histograma. Ver si dos cosas se mueven juntas es dispersión. Ya\n",
    "está, con esas cuatro haces el 95%.\n",
    "\n",
    "- 📐 **Las barras van ordenadas por valor**, no alfabéticas. Nadie\n",
    "va a leer seis nombres para descubrir cuál es la más alta.\n",
    "\n",
    "- ✍️ **El título dice la conclusión**, no el nombre de las\n",
    "columnas. \"Ventas por ciudad\" no dice nada. \"Arequipa vende 17% más que Lima\" sí.\n",
    "\n",
    "Si haces esas tres, ya vas mejor que la mayoría de los informes que he visto.\n",
    "Y ninguna necesita saber matplotlib 💜\n",
    "\n",
    "Y llévate esta pregunta a tu próxima reunión: **¿el último gráfico que presentaste decía la conclusión en el título?** Si decía \"Ventas por ciudad\", ya sabes por dónde empezar 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El primer gráfico"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib\n",
    "matplotlib.use('Agg')          # sin ventana, para guardar en archivo\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "from pathlib import Path\n",
    "import tempfile\n",
    "\n",
    "df = pd.read_csv('ventas-miss-yera.csv').drop_duplicates(subset='id_venta')\n",
    "df['ciudad'] = (df['ciudad'].str.strip().str.lower()\n",
    "                .str.normalize('NFKD')\n",
    "                .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "df['monto'] = pd.to_numeric(df['monto'].str.replace(',', '.'), errors='coerce')\n",
    "\n",
    "por_ciudad = df.groupby('ciudad')['monto'].sum().sort_values()\n",
    "print(por_ciudad.round(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en el `sort_values()` del final: ordené **antes**\n",
    "de graficar. Esa es la regla 2 y se aplica aquí, no en el gráfico."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "carpeta = Path(tempfile.mkdtemp())\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(8, 4.5))\n",
    "ax.barh(por_ciudad.index, por_ciudad.values, color='#F092C7')\n",
    "\n",
    "ax.set_title('Arequipa vende 17% más que Lima', fontsize=13, loc='left')\n",
    "ax.set_xlabel('Ventas del periodo (S/)')\n",
    "ax.spines['top'].set_visible(False)\n",
    "ax.spines['right'].set_visible(False)\n",
    "\n",
    "fig.tight_layout()\n",
    "ruta = carpeta / 'ventas-por-ciudad.png'\n",
    "fig.savefig(ruta, dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print(ruta.name, 'creado:', ruta.exists())\n",
    "print('pesa', round(ruta.stat().st_size / 1024), 'KB')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos por partes, que ahí hay varias cosas 🌸\n",
    "\n",
    "- 🖼️ `fig, ax = plt.subplots()` es la forma seria de empezar.\n",
    "Verás mucho `plt.bar()` suelto por internet; funciona para una prueba\n",
    "rápida y se vuelve un lío en cuanto hay dos gráficos.\n",
    "\n",
    "- 📏 `barh` y no `bar`: barras horizontales, porque los\n",
    "nombres de ciudad se leen sin torcer la cabeza.\n",
    "\n",
    "- ✂️ Esas dos líneas de `spines` quitan el marco de arriba y de la\n",
    "derecha. Es el cambio más barato que existe para que un gráfico se vea\n",
    "profesional.\n",
    "\n",
    "- 💾 `tight_layout()` antes de guardar, o los textos salen\n",
    "cortados.\n",
    "\n",
    "Y ese `matplotlib.use('Agg')` de la primera línea es para guardar\n",
    "sin abrir ventana. En Colab no hace falta, ahí los gráficos salen solos debajo de\n",
    "la celda.\n",
    "\n",
    "### Y el 17% de dónde salió\n",
    "\n",
    "De calcularlo, no de inventarlo. Si vas a poner un número en un título,\n",
    "compruébalo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "diferencia = (por_ciudad['arequipa'] / por_ciudad['lima'] - 1) * 100\n",
    "print(f'{diferencia:.1f}%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parece obvio y no lo es: los títulos con números inventados son la forma más\n",
    "rápida de perder la confianza de una reunión 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los cuatro tipos que necesitas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Barras: comparar categorías"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_canal = df.groupby('canal')['monto'].sum().sort_values()\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(7, 3.5))\n",
    "ax.barh(por_canal.index, por_canal.values, color='#4DB8E8')\n",
    "ax.set_title('Los cuatro canales venden casi lo mismo', loc='left')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'canales.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print(por_canal.round(0))\n",
    "print('diferencia entre el mejor y el peor:',\n",
    "      round((por_canal.max() / por_canal.min() - 1) * 100, 1), '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí una decisión de honestidad: once por ciento de diferencia entre el\n",
    "mejor y el peor canal es poco. El título lo dice y no lo esconde.\n",
    "\n",
    "La tentación sería cortar el eje para que la diferencia se vea enorme. Eso se\n",
    "hace todo el tiempo y es engañar con un gráfico 😖 En barras,\n",
    "**el eje empieza en cero, siempre**.\n",
    "\n",
    "### El error que da todo el mundo la primera vez\n",
    "\n",
    "Y este te va a pasar, así que mejor aquí:"
   ]
  },
  {
   "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",
    "    fig, ax = plt.subplots()\n",
    "    ax.barh(por_canal.index, por_canal.values, colour='#F092C7')\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",
    "AttributeError: Rectangle.set() got an unexpected keyword argument 'colour'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Escribí `colour` a la inglesa en vez de `color`. Y\n",
    "matplotlib no te dice \"no conozco ese parámetro\" a secas: te dice qué objeto lo\n",
    "rechazó, que es `Rectangle`, o sea la barra.\n",
    "\n",
    "Ese patrón se repite con casi todos los errores de matplotlib:\n",
    "**el mensaje nombra el objeto interno y no la función que llamaste**.\n",
    "Cuando veas nombres raros tipo `Rectangle`, `Line2D` o\n",
    "`Text`, no te asustes: son las piezas del gráfico, y el problema está\n",
    "en el parámetro que les pasaste 🙂"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.close(fig)\n",
    "print('cerrada')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Líneas: evolución en el tiempo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fecha = pd.to_datetime(df['fecha'], format='%Y-%m-%d', errors='coerce')\n",
    "faltan = fecha.isna()\n",
    "fecha[faltan] = pd.to_datetime(df.loc[faltan, 'fecha'],\n",
    "                               format='%d/%m/%Y', errors='coerce')\n",
    "df['fecha'] = fecha\n",
    "\n",
    "por_mes = df.groupby(df['fecha'].dt.to_period('M'))['monto'].sum()\n",
    "print(por_mes.head(4).round(0))\n",
    "print('meses:', len(por_mes))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(9, 3.5))\n",
    "ax.plot(por_mes.index.to_timestamp(), por_mes.values,\n",
    "        marker='o', color='#F092C7')\n",
    "ax.set_title('Ventas mensuales, enero 2025 a junio 2026', loc='left')\n",
    "ax.set_ylim(0, None)\n",
    "ax.grid(axis='y', alpha=0.3)\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'mensual.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('grafico de', len(por_mes), 'meses guardado')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `set_ylim(0, None)` hace lo mismo que en barras: obliga a que el\n",
    "eje empiece en cero. Sin él, matplotlib ajusta el eje al rango de los datos y una\n",
    "variación del 3% parece un terremoto.\n",
    "\n",
    "### Histograma: cómo se reparte una variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(8, 3.5))\n",
    "ax.hist(df['monto'].dropna(), bins=40, color='#F08A8A')\n",
    "ax.axvline(df['monto'].median(), color='#4A4A4A', linestyle='--')\n",
    "ax.set_title('La mayoría de ventas está por debajo de S/1.000', loc='left')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'histograma.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('mediana  S/', round(df['monto'].median(), 2))\n",
    "print('promedio S/', round(df['monto'].mean(), 2))\n",
    "print('por debajo de 1000:',\n",
    "      round((df['monto'] < 1000).mean() * 100, 1), '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El histograma es el gráfico que más me gusta y el que menos se usa. Con una\n",
    "tabla de promedios nunca ves la forma; aquí ves que el 73% de las ventas está\n",
    "bajo los mil soles y que hay una cola larga de ventas grandes que estira el\n",
    "promedio 🌟\n",
    "\n",
    "Esa línea vertical de la mediana no es adorno: es lo que hace que se vea la\n",
    "distancia entre la mediana y el promedio sin explicarla.\n",
    "\n",
    "Y ojo con `bins`. Con pocas cajas escondes la forma, con demasiadas\n",
    "todo parece ruido. Entre 20 y 50 suele funcionar, y vale la pena probar dos o\n",
    "tres.\n",
    "\n",
    "### Dispersión: si dos cosas se mueven juntas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(6, 4))\n",
    "ax.scatter(df['unidades'], df['monto'], s=8, alpha=0.25, color='#4DB8E8')\n",
    "ax.set_xlabel('Unidades')\n",
    "ax.set_ylabel('Monto (S/)')\n",
    "ax.set_title('Más unidades no significa más monto', loc='left')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'dispersion.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('correlación:', round(df['unidades'].corr(df['monto']), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Correlación de 0,018, o sea prácticamente nada. Y ahí está el valor del\n",
    "gráfico: **cuando la respuesta es \"no hay relación\", eso también es un\n",
    "hallazgo** y hay que reportarlo.\n",
    "\n",
    "Ese `alpha=0.25` hace los puntos semitransparentes. Con tres mil\n",
    "puntos encimados es la diferencia entre ver una mancha negra y ver dónde se\n",
    "concentran.\n",
    "\n",
    "Y el aviso de siempre, que desarrollo en el\n",
    "[libro de estadística](https://missyera.com/guias/estadistica-desde-cero/): aunque\n",
    "la correlación hubiera salido alta, **correlación no es causa**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Varios gráficos juntos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(11, 3.5))\n",
    "\n",
    "por_segmento = df.groupby('segmento')['monto'].sum().sort_values()\n",
    "axes[0].barh(por_segmento.index, por_segmento.values, color='#F092C7')\n",
    "axes[0].set_title('Por segmento', loc='left')\n",
    "\n",
    "axes[1].hist(df['satisfaccion'].dropna(), bins=5, color='#4DB8E8')\n",
    "axes[1].set_title('Satisfacción', loc='left')\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'panel.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('archivos generados:', len(list(carpeta.glob('*.png'))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `subplots(1, 2)` te da una fila y dos columnas, y\n",
    "`axes` es una lista. Con `subplots(2, 2)` serían cuatro y\n",
    "`axes` sería una matriz, así que se accede con\n",
    "`axes[0][1]`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Guardar para lo que sea"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Formato | Cuándo |\n",
    "|---|---|\n",
    "| `.png` con `dpi=150` | Presentaciones, correo, informes |\n",
    "| `.svg` | Web y documentos que se van a imprimir: no pierde nitidez |\n",
    "| `.pdf` | Cuando va dentro de un documento formal |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(6, 3))\n",
    "ax.barh(por_canal.index, por_canal.values, color='#F092C7')\n",
    "fig.tight_layout()\n",
    "\n",
    "for extension in ['png', 'svg', 'pdf']:\n",
    "    fig.savefig(carpeta / f'canales.{extension}')\n",
    "plt.close(fig)\n",
    "\n",
    "for archivo in sorted(carpeta.glob('canales.*')):\n",
    "    print(archivo.name, round(archivo.stat().st_size / 1024), 'KB')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y siempre `plt.close(fig)` después de guardar. Si generas cincuenta\n",
    "gráficos en un bucle sin cerrarlos, se te van acumulando en memoria y matplotlib\n",
    "te avisa con una advertencia que nadie entiende 😄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El número encima de la barra"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una cosa que cambia un gráfico de \"está bien\" a \"se entiende solo\": poner el\n",
    "valor sobre cada barra y borrar el eje 🔢"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(8, 4))\n",
    "barras = ax.barh(por_ciudad.index, por_ciudad.values, color='#F092C7')\n",
    "\n",
    "ax.bar_label(barras, labels=[f'{v/1000:,.0f}k' for v in por_ciudad.values],\n",
    "             padding=4, fontsize=9)\n",
    "ax.set_xlim(0, por_ciudad.max() * 1.12)\n",
    "ax.get_xaxis().set_visible(False)\n",
    "for lado in ('top', 'right', 'bottom'):\n",
    "    ax.spines[lado].set_visible(False)\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'con-numeros.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('etiquetas puestas:', [f'{v/1000:,.0f}k' for v in por_ciudad.values])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Piénsalo un segundo: si el número está escrito, **el eje de abajo ya no\n",
    "hace falta**. Estaba ahí solo para que el ojo estimara el valor, y ahora\n",
    "el valor está puesto 🙂\n",
    "\n",
    "Por eso ese `get_xaxis().set_visible(False)` no es dejadez, es\n",
    "quitar lo que sobra. Y el `set_xlim` con ese 1.12 es para que la\n",
    "etiqueta de la barra más larga no se salga de la figura.\n",
    "\n",
    "El `padding=4` son los píxeles entre la punta de la barra y el\n",
    "texto. Sin él quedan pegados y se lee peor.\n",
    "\n",
    "Ese `labels=` es opcional. Si te vale el número tal cual, con\n",
    "`ax.bar_label(barras, fmt='S/ %.0f')` ya lo tienes. Lo uso cuando\n",
    "quiero acortar a miles, que es lo que se hace cuando las cifras son largas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La torta, y por qué casi nunca"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te la van a pedir. Y yo te voy a dar el argumento para decir que no, con un\n",
    "número, que es como se ganan estas discusiones 🥧\n",
    "\n",
    "Los datos de arriba, repartidos en porcentaje:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "reparto = (por_ciudad / por_ciudad.sum() * 100).sort_values(ascending=False)\n",
    "grados = reparto / 100 * 360\n",
    "\n",
    "print(grados.round(1))\n",
    "print('la mayor y la menor se llevan',\n",
    "      round(grados.iloc[0] - grados.iloc[-1], 1), 'grados de diferencia')\n",
    "print('en barras, la diferencia es de',\n",
    "      round(reparto.iloc[0] / reparto.iloc[-1] * 100 - 100, 1), '% de largo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está todo 👀\n",
    "\n",
    "Arequipa vende un 17% más que Lima. En un gráfico de barras esa diferencia se\n",
    "ve: una barra es visiblemente más larga que la otra. En una torta, esa misma\n",
    "diferencia son **nueve grados y medio** de abertura, y nueve grados\n",
    "en una circunferencia no los distingue nadie.\n",
    "\n",
    "El ojo compara largos bien y compara ángulos mal. No es opinión mía, es lo que\n",
    "hace que la misma diferencia se vea en un gráfico y desaparezca en el otro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
    "\n",
    "axes[0].pie(reparto.values, labels=reparto.index, autopct='%1.0f%%')\n",
    "axes[0].set_title('En torta')\n",
    "\n",
    "axes[1].barh(reparto.index[::-1], reparto.values[::-1], color='#F092C7')\n",
    "axes[1].set_title('En barras')\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'torta-vs-barras.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('las dos guardadas en la misma figura')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Genera ese archivo y míralo. En la torta tienes que leer los porcentajes para\n",
    "saber cuál gana; en las barras lo ves antes de leer nada. Y si tienes que leer\n",
    "los números, entonces el gráfico no está trabajando: mejor una tabla 📋\n",
    "\n",
    "Cuándo sí uso torta: cuando hay **dos o tres trozos** y uno es\n",
    "claramente más grande, tipo \"el 70% de las ventas viene de un solo canal\". Ahí el\n",
    "mensaje es \"uno se lleva casi todo\" y la torta lo cuenta bien. Con seis trozos\n",
    "parecidos, nunca."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El estilo se decide una vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Habrás notado que en cada gráfico repetí las dos líneas de\n",
    "`spines`, el `loc='left'` del título y el\n",
    "`figsize`. Eso en un cuaderno de verdad se hace una sola vez, arriba,\n",
    "y se olvida 🎨"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.rcParams.update({\n",
    "    'axes.spines.top': False,\n",
    "    'axes.spines.right': False,\n",
    "    'axes.titlelocation': 'left',\n",
    "    'axes.titlesize': 12,\n",
    "    'figure.figsize': (8, 4),\n",
    "    'axes.grid': True,\n",
    "    'grid.alpha': 0.25,\n",
    "})\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.barh(por_canal.index, por_canal.values, color='#F092C7')\n",
    "ax.set_title('Sin repetir una sola linea de estilo')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'con-estilo.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('marco de arriba visible:', plt.rcParams['axes.spines.top'])\n",
    "print('tamano por defecto     :', plt.rcParams['figure.figsize'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro líneas menos en cada gráfico, y sobre todo: **todos los gráficos\n",
    "del informe salen iguales**. Que es la mitad de lo que hace que un\n",
    "documento se vea hecho por alguien que sabe 💜\n",
    "\n",
    "`rcParams` es el diccionario de ajustes por defecto de matplotlib.\n",
    "Lo que pongas ahí vale para todo lo que dibujes después, en ese cuaderno. Al\n",
    "reiniciar el kernel vuelve a lo de fábrica, así que va arriba del todo, con los\n",
    "`import`.\n",
    "\n",
    "Si prefieres no elegir nada, `plt.style.use('seaborn-v0_8-whitegrid')`\n",
    "te deja algo decente de una línea. Yo prefiero las siete de arriba porque sé\n",
    "exactamente qué cambia cada una."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que hace que se entienda, resumido"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🎯 El tipo sale de la pregunta: barras para comparar, líneas para el tiempo,\n",
    "histograma para la forma, dispersión para la relación.\n",
    "\n",
    "- 📊 Barras ordenadas por valor y horizontales si los nombres son largos.\n",
    "\n",
    "- 0️⃣ El eje empieza en cero. Cortarlo es engañar.\n",
    "\n",
    "- ✍️ El título dice la conclusión, con el número comprobado.\n",
    "\n",
    "- ✂️ Fuera el marco de arriba y de la derecha, fuera la leyenda si hay una sola\n",
    "serie, fuera todo lo que no aporte."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Barras ordenadas\n",
    "\n",
    "Grafica el total por categoría, ordenado, y guárdalo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_cat = df.groupby('categoria')['monto'].sum().sort_values()\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(7, 3))\n",
    "ax.barh(por_cat.index, por_cat.values, color='#F092C7')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'categorias.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print(por_cat.round(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "categoria\n",
    "Cuidado personal    453910.0\n",
    "Bebidas             456361.0\n",
    "Limpieza            471763.0\n",
    "Snacks              493895.0\n",
    "Abarrotes           535355.0\n",
    "Name: monto, dtype: float64\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El título con su número\n",
    "\n",
    "Calcula cuánto más vende la categoría líder que la última y\n",
    "escribe el título con ese número."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lider, ultima = por_cat.index[-1], por_cat.index[0]\n",
    "cuanto = (por_cat.iloc[-1] / por_cat.iloc[0] - 1) * 100\n",
    "\n",
    "print(f'{lider} vende {cuanto:.0f}% más que {ultima}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Abarrotes vende 18% más que Cuidado personal\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Histograma de unidades\n",
    "\n",
    "Grafica cómo se reparten las unidades por venta y di cuál es\n",
    "la más frecuente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(7, 3))\n",
    "ax.hist(df['unidades'], bins=25, color='#F08A8A')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'unidades.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('mediana:', df['unidades'].median())\n",
    "print('máximo :', df['unidades'].max())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "mediana: 12.0\n",
    "máximo : 30\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El eje que engaña\n",
    "\n",
    "Grafica los canales dos veces, una con el eje desde cero y\n",
    "otra desde el mínimo, y compara los archivos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(11, 3))\n",
    "\n",
    "axes[0].bar(por_canal.index, por_canal.values, color='#4DB8E8')\n",
    "axes[0].set_title('Eje desde cero (honesto)', loc='left')\n",
    "\n",
    "axes[1].bar(por_canal.index, por_canal.values, color='#F08A8A')\n",
    "axes[1].set_ylim(por_canal.min() * 0.98, por_canal.max() * 1.01)\n",
    "axes[1].set_title('Eje cortado (engañoso)', loc='left')\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'comparacion-ejes.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('los mismos datos, dos historias distintas')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "los mismos datos, dos historias distintas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ábrelos y míralos al lado. En el segundo, once por ciento de diferencia parece\n",
    "que un canal triplica al otro. Es el truco más usado para mentir con datos, y\n",
    "ahora lo vas a reconocer 👀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Dispersión con transparencia\n",
    "\n",
    "Grafica satisfacción contra monto y calcula la\n",
    "correlación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sub = df.dropna(subset=['satisfaccion', 'monto'])\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(6, 3.5))\n",
    "ax.scatter(sub['satisfaccion'], sub['monto'], s=8, alpha=0.2, color='#4DB8E8')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'satisfaccion.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('correlación:', round(sub['satisfaccion'].corr(sub['monto']), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "correlación: -0.017\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prácticamente cero otra vez. Reportar que no hay relación es tan válido como\n",
    "reportar que sí la hay, y a veces más útil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Un panel de cuatro\n",
    "\n",
    "Arma una figura con cuatro gráficos en dos filas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(2, 2, figsize=(10, 6))\n",
    "\n",
    "axes[0][0].barh(por_ciudad.index, por_ciudad.values, color='#F092C7')\n",
    "axes[0][1].barh(por_canal.index, por_canal.values, color='#4DB8E8')\n",
    "axes[1][0].hist(df['monto'].dropna(), bins=30, color='#F08A8A')\n",
    "axes[1][1].hist(df['unidades'], bins=25, color='#4A4A4A')\n",
    "\n",
    "for fila in axes:\n",
    "    for a in fila:\n",
    "        a.spines['top'].set_visible(False)\n",
    "        a.spines['right'].set_visible(False)\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'panel4.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print('panel de', axes.size, 'gráficos')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "panel de 4 gráficos\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Guardar en SVG\n",
    "\n",
    "Guarda un gráfico en SVG y comprueba que el archivo es\n",
    "texto, no una imagen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(5, 2.5))\n",
    "ax.barh(por_canal.index, por_canal.values, color='#F092C7')\n",
    "fig.tight_layout()\n",
    "ruta_svg = carpeta / 'prueba.svg'\n",
    "fig.savefig(ruta_svg)\n",
    "plt.close(fig)\n",
    "\n",
    "print(ruta_svg.read_text()[:40])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "<?xml version=\"1.0\" encoding=\"utf-8\" sta\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un SVG es texto XML que describe las formas, y por eso no se pixela por mucho\n",
    "que lo agrandes. Para la web es el formato correcto casi siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Cerrar las figuras\n",
    "\n",
    "Genera diez gráficos en un bucle cerrándolos, y comprueba\n",
    "que no queda ninguno abierto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(10):\n",
    "    f, a = plt.subplots(figsize=(3, 2))\n",
    "    a.plot([1, 2, 3], [i, i + 1, i])\n",
    "    f.savefig(carpeta / f'bucle-{i}.png')\n",
    "    plt.close(f)\n",
    "\n",
    "print('figuras abiertas:', len(plt.get_fignums()))\n",
    "print('archivos:', len(list(carpeta.glob('ciudad-*.png'))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "figuras abiertas: 0\n",
    "archivos: 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si quitas el `plt.close(f)`, esa primera línea diría 10 y con\n",
    "cincuenta gráficos matplotlib te empieza a avisar. Cerrar es una línea y evita el\n",
    "problema entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. El ticket promedio, con el número escrito\n",
    "\n",
    "Grafica el monto promedio por categoría con el valor sobre\n",
    "cada barra."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_cat_media = df.groupby('categoria')['monto'].mean().sort_values()\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(7, 3.5))\n",
    "b = ax.barh(por_cat_media.index, por_cat_media.values, color='#4DB8E8')\n",
    "ax.bar_label(b, fmt='S/ %.0f', padding=4)\n",
    "ax.set_xlim(0, por_cat_media.max() * 1.15)\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'ticket-por-categoria.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print(por_cat_media.round(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "categoria\n",
    "Cuidado personal    749.03\n",
    "Limpieza            803.68\n",
    "Bebidas             807.72\n",
    "Snacks              820.42\n",
    "Abarrotes           836.49\n",
    "Name: monto, dtype: float64\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ochenta y siete soles separan la categoría de arriba de la de abajo, sobre\n",
    "tickets de ochocientos. O sea, un 11% 🤏\n",
    "\n",
    "Y ahí viene la decisión honesta: con esa diferencia, el título no puede decir\n",
    "\"Abarrotes es la categoría estrella\". Puede decir \"las cinco categorías tienen\n",
    "ticket parecido\", que es lo que pasa de verdad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Torta y barras, los mismos datos\n",
    "\n",
    "Reparte las ventas por canal en porcentaje y dibújalo de las\n",
    "dos formas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "reparto_canal = (por_canal / por_canal.sum() * 100).sort_values(ascending=False)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
    "axes[0].pie(reparto_canal.values, labels=reparto_canal.index, autopct='%1.0f%%')\n",
    "axes[1].barh(reparto_canal.index[::-1], reparto_canal.values[::-1],\n",
    "             color='#F092C7')\n",
    "fig.tight_layout()\n",
    "fig.savefig(carpeta / 'canales-dos-formas.png', dpi=120)\n",
    "plt.close(fig)\n",
    "\n",
    "print(reparto_canal.round(1))\n",
    "print('el mayor y el menor se llevan',\n",
    "      round((reparto_canal.iloc[0] - reparto_canal.iloc[-1]) / 100 * 360, 1),\n",
    "      'grados')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "canal\n",
    "Web            26.2\n",
    "Marketplace    25.7\n",
    "Tienda         24.5\n",
    "WhatsApp       23.5\n",
    "Name: monto, dtype: float64\n",
    "el mayor y el menor se llevan 9.7 grados\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí es todavía peor que con las ciudades: cuatro trozos casi idénticos. La\n",
    "torta dice \"todos iguales\" y es lo único que se puede leer de ella."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Los ajustes que te ahorran cuatro líneas por gráfico\n",
    "\n",
    "Vuelve a los ajustes de fábrica, mira cómo vienen, y pon\n",
    "los tuyos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.rcdefaults()\n",
    "\n",
    "print('de fabrica:')\n",
    "print('  marco arriba :', plt.rcParams['axes.spines.top'])\n",
    "print('  tamano       :', plt.rcParams['figure.figsize'])\n",
    "print('  titulo a la  :', plt.rcParams['axes.titlelocation'])\n",
    "\n",
    "plt.rcParams.update({'axes.spines.top': False,\n",
    "                     'figure.figsize': (9, 3.5),\n",
    "                     'axes.titlelocation': 'left'})\n",
    "\n",
    "print('con los mios:')\n",
    "print('  marco arriba :', plt.rcParams['axes.spines.top'])\n",
    "print('  tamano       :', plt.rcParams['figure.figsize'])\n",
    "print('  titulo a la  :', plt.rcParams['axes.titlelocation'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "de fabrica:\n",
    "  marco arriba : True\n",
    "  tamano       : [6.4, 4.8]\n",
    "  titulo a la  : center\n",
    "con los mios:\n",
    "  marco arriba : False\n",
    "  tamano       : [9.0, 3.5]\n",
    "  titulo a la  : left\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `plt.rcdefaults()` del principio es el botón de deshacer, y lo\n",
    "vas a querer el día que toquetees diez ajustes y ya no sepas cuál te dejó el\n",
    "gráfico raro 🔄\n",
    "\n",
    "Fíjate en los de fábrica: el marco cerrado por los cuatro lados y el título\n",
    "centrado. Los dos son decisiones de matplotlib de hace veinte años, y los dos son\n",
    "lo primero que cambia cualquiera que publique gráficos hoy."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. El error de los largos distintos\n",
    "\n",
    "Intenta graficar seis ciudades contra cuatro canales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(por_ciudad.index, por_canal.values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: x and y must have same first dimension, but have shapes (6,) and (4,)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El mensaje es bueno y por eso lo pongo: te dice las dos formas,\n",
    "`(6,)` y `(4,)`, así que sabes exactamente cuál sobra 📏\n",
    "\n",
    "Este error sale muchísimo cuando filtras uno de los dos lados y el otro no. Lo\n",
    "típico: quitas los nulos del monto y te olvidas de quitar las fechas\n",
    "correspondientes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('figuras abiertas tras el error:', len(plt.get_fignums()))\n",
    "plt.close('all')\n",
    "print('y despues de cerrar          :', len(plt.get_fignums()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "figuras abiertas tras el error: 1\n",
    "y despues de cerrar          : 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la otra mitad de la lección 🧹\n",
    "\n",
    "La figura que reventó **se quedó abierta**. Matplotlib la creó en\n",
    "el `subplots()`, y el error vino después, en el `plot()`.\n",
    "Nadie la cerró porque nunca llegamos a la línea que la cerraba.\n",
    "\n",
    "Por eso `plt.close('all')` es lo que uso al final de cada cuaderno:\n",
    "recoge también las que quedaron a medias."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El gráfico correcto que engaña a toda la reunión"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de cerrar el capítulo, la trampa de los gráficos. Y esta es distinta a todas las anteriores, porque el código no tiene nada malo 📊"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Haces el gráfico de ventas por ciudad para la reunión. matplotlib elige los ejes solo y el resultado se ve clarísimo.\n",
    "\n",
    "```\n",
    "plt.bar(ciudades, ventas)\n",
    "plt.ylim(480, 520)\n",
    "plt.show()\n",
    "\n",
    "# Lima 512, Cusco 495\n",
    "# la barra de Lima se ve\n",
    "# el triple de alta\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "El eje no empieza en cero, así que una diferencia del 3% se ve como si fuera del 200% 📊 Nadie mintió: los números están bien y el gráfico es correcto. Lo que engaña es la forma, que es lo que la gente lee.\n",
    "\n",
    "Y no hace falta mala fe. matplotlib ajusta los ejes al rango de los datos para que se aprecien las diferencias, así que este gráfico te sale solo si no miras.\n",
    "\n",
    "En un gráfico de barras el eje empieza en cero, sin excepciones, porque la barra se compara por su longitud. Si la diferencia real es tan pequeña que no se ve, la respuesta no es estirar el eje: es que **la diferencia no importa**, y eso también es un hallazgo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Quieres enseñar cómo se reparten los montos de 3.000 ventas. ¿Qué gráfico?\n",
    "\n",
    "a) Un histograma, que enseña la forma entera\n",
    "\n",
    "b) Un gráfico de barras con el promedio\n",
    "\n",
    "c) Un gráfico de líneas ordenado por monto\n",
    "\n",
    "d) Un gráfico de torta con tramos\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Una barra con el promedio esconde justamente lo que quieres enseñar.\n",
    "\n",
    "*c)* Eso dibuja el orden que acabas de imponer, no el reparto.\n",
    "\n",
    "*d)* La torta se lee mal en cuanto pasas de tres o cuatro trozos.\n",
    "\n",
    "Para ver un reparto se necesita la forma, no un resumen."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que un gráfico bueno no es el más bonito, es el que se entiende sin que estés\n",
    "al lado explicándolo 💜\n",
    "\n",
    "Y que las tres decisiones que lo deciden (el tipo, el orden y el título) se\n",
    "toman antes de escribir código.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En un gráfico de barras el eje empieza en cero. Sin excepciones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 16 juntamos todo el libro en un solo proyecto: del archivo\n",
    "sucio a una conclusión con su gráfico, de punta a punta.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 15 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/graficos/\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
}
