{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pandas\n",
    "\n",
    "El capítulo por el que la mayoría aprende Python, sobre un archivo sucio de verdad y con el aviso que sale en rojo.\n",
    "\n",
    "Cuaderno de práctica del capítulo 12 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/pandas/\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": [
    "## 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": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"KDMwMzcsIDE0KQpzdHIKMjM0\",\n",
    "    2: \"Y2FuYWwKV2ViICAgICAgICAgICAgODAxCk1hcmtldHBsYWNlICAgIDc3NgpUaWVuZGEgICAgICAgICA3MzUKV2hhdHNBcHAgICAgICAgNzI1Ck5hbWU6IGNvdW50LCBkdHlwZTogaW50NjQ=\",\n",
    "    3: \"NDM0\",\n",
    "    4: \"VmFsdWVFcnJvcjogVGhlIHRydXRoIHZhbHVlIG9mIGEgU2VyaWVzIGlzIGFtYmlndW91cy4gVXNlIGEuZW1wdHksIGEuYm9vbCgpLCBhLml0ZW0oKSwgYS5hbnkoKSBvciBhLmFsbCgpLg==\",\n",
    "    5: \"ICAgICAgICBjaXVkYWQgIHVuaWRhZGVzICAgIG1vbnRvICBwcmVjaW9fdW5pdGFyaW8KMjM5OCAgICAgUGl1cmEgICAgICAgICAxICAzNTg2LjI2ICAgICAgICAgIDM1ODYuMjYKMTkwNSAgVHJ1amlsbG8gICAgICAgICAxICAzNTAzLjI3ICAgICAgICAgIDM1MDMuMjcKMTMxNyAgVHJ1amlsbG8gICAgICAgICAxICAzMDE4LjMxICAgICAgICAgIDMwMTguMzE=\",\n",
    "    6: \"ICAgICAgICAgICAgdmVudGFzICAgICAgIHRvdGFsICBtZWRpYW5hCnNlZ21lbnRvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApNYXlvcmlzdGEgICAgICA3NTAgIDEzOTIyNTIuODMgIDE4NjkuMjYKSG9yZWNhICAgICAgICAgNzQyICAgNTYwMzQyLjg3ICAgNzQzLjAwCk1pbmltYXJrZXQgICAgIDgwMSAgIDMzMjM0OC42NyAgIDQxMy43NApCb2RlZ2EgICAgICAgICA3MDcgICAxMjYzMzkuNDYgICAxODMuMjY=\",\n",
    "    7: \"c2luX2Rlc2N1ZW50bwowICAgIDAuNjAxNwoxICAgIDAuNDgwNwpOYW1lOiBjb21wcm8sIGR0eXBlOiBmbG9hdDY0\",\n",
    "    8: \"Y2F0ZWdvcmlhICAgQWJhcnJvdGVzICBCZWJpZGFzICBDdWlkYWRvIHBlcnNvbmFsICBMaW1waWV6YSAgU25hY2tzCnNlZ21lbnRvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApCb2RlZ2EgICAgICAgICAgIDE1NjQgICAgIDE2NTkgICAgICAgICAgICAgIDE4NjIgICAgICAxMzQ0ICAgIDE1OTUKSG9yZWNhICAgICAgICAgICAxODcyICAgICAxNTcxICAgICAgICAgICAgICAxNTM1ICAgICAgMTcxNiAgICAxODY4Ck1heW9yaXN0YSAgICAgICAgMTk0NCAgICAgMTc5NiAgICAgICAgICAgICAgMTQyNSAgICAgIDE3MTggICAgMTkxMgpNaW5pbWFya2V0ICAgICAgIDE5ODEgICAgIDE2NzggICAgICAgICAgICAgIDIyNzQgICAgICAxODEzICAgIDE2NzU=\",\n",
    "    9: \"NDk2IDc3NDQ1MC43Ng==\",\n",
    "    10: \"WzU2NzM5Mi40OSwgNTkxNTE5LjA0LCA2MTk3NjAuNzMsIDYzMjYxMS41N10KWzU2NzM5Mi40OSwgNTkxNTE5LjA0LCA2MTk3NjAuNzMsIDYzMjYxMS41N10KVHJ1ZQ==\",\n",
    "    11: \"ICAgICAgICAgIHZlbnRhcyAgdGlja2V0ICBncmFuZGVzICAgcGN0CmNpdWRhZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAp0cnVqaWxsbyAgICAgNDY1ICAgODU2LjcgICAgICAxMzUgIDI5LjAKY2hpY2xheW8gICAgIDUwNyAgIDgyMC4wICAgICAgMTMzICAyNi4yCmFyZXF1aXBhICAgICA1NDEgICA4MTUuMCAgICAgIDE0NCAgMjYuNgpsaW1hICAgICAgICAgNDcwICAgNzk5LjUgICAgICAxMzIgIDI4LjEKcGl1cmEgICAgICAgIDUwMyAgIDc5OS4yICAgICAgMTMzICAyNi40CmN1c2NvICAgICAgICA0OTMgICA3ODUuOSAgICAgIDEzNCAgMjcuMg==\",\n",
    "    12: \"Y2l1ZGFkICAgICAgICAgICA2IGRpc3RpbnRvcyAgIDI4MCwxOTggLT4gICA5Myw0NjggIGFob3JyYSA2NyUKc2VnbWVudG8gICAgICAgICA0IGRpc3RpbnRvcyAgIDI4NCw1NDIgLT4gICA5MywzNDcgIGFob3JyYSA2NyUKY2FuYWwgICAgICAgICAgICA0IGRpc3RpbnRvcyAgIDI4MSw5NjUgLT4gICA5MywzNDQgIGFob3JyYSA2NyUKY2F0ZWdvcmlhICAgICAgICA1IGRpc3RpbnRvcyAgIDI4OCw4MDcgLT4gICA5Myw0MTkgIGFob3JyYSA2OCUKY2xpZW50ZV9pZCAgICAgNjE3IGRpc3RpbnRvcyAgIDI3NiwwODggLT4gIDEzNCwzNDIgIGFob3JyYSA1MSU=\",\n",
    "    13: \"Y2l1ZGFkCmFyZXF1aXBhICAgIDU0NgpjaGljbGF5byAgICA1MTAKcGl1cmEgICAgICAgNTA2CmN1c2NvICAgICAgIDQ5Ngp0cnVqaWxsbyAgICA0NzEKbGltYSAgICAgICAgNDcxCk5hbWU6IGNvdW50LCBkdHlwZTogaW50NjQKY2l1ZGFkCmFyZXF1aXBhICAgIDU0NgpjaGljbGF5byAgICA1MTAKcGl1cmEgICAgICAgNTA2CmN1c2NvICAgICAgIDQ5NgpsaW1hICAgICAgICA0NzEKdHJ1amlsbG8gICAgNDcxCk5hbWU6IGNvdW50LCBkdHlwZTogaW50NjQKbWlzbW9zIG51bWVyb3M6IFRydWUKbWlzbW8gb3JkZW4gICA6IEZhbHNlCmVxdWFscyBkaWNlICAgOiBGYWxzZQ==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Llegamos al capítulo por el que casi todos aprendemos Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te voy a ser honesta: mucha gente aprende Python solo para llegar aquí 😄\n",
    "\n",
    "pandas es una tabla con superpoderes. Todo lo de los capítulos anteriores\n",
    "sigue valiendo por debajo, y ahora se escribe muchísimo más corto.\n",
    "\n",
    "Y aprovecho para preguntarte algo: **¿cuál es la tabla que abres todas las semanas en tu trabajo?** Esa es con la que tienes que repetir este capítulo cuando lo termines, porque con datos tuyos se aprende el triple 🐼"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cargar el archivo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "\n",
    "print(df.shape)\n",
    "print(df.columns.tolist())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una línea. Todo el capítulo 10 (abrir, decodificar, partir por comas, armar\n",
    "diccionarios) resuelto en una línea 🌸\n",
    "\n",
    "Ese `df` es la convención universal para \"DataFrame\". Vas a verlo en\n",
    "todo internet.\n",
    "\n",
    "Y lo primero que se hace, siempre, en este orden:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.dtypes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Mira `monto`.** Salió como texto, no como número.\n",
    "\n",
    "Ya sabemos por qué del capítulo 11: son los 612 montos con coma decimal. pandas\n",
    "intentó convertir la columna, encontró comas, y la dejó entera como texto en vez\n",
    "de inventarse nada 👏\n",
    "\n",
    "Si tu pandas es anterior a la versión 3.0 vas a ver `object` donde\n",
    "aquí dice `str`. Es lo mismo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.head(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en la fila 1: la ciudad dice `lima` en minúscula. Ya vamos a\n",
    "llegar a eso 👀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Series y DataFrame"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una columna sola es una **Series**; la tabla entera es un\n",
    "**DataFrame**. Es la misma diferencia que entre un arreglo de una\n",
    "dimensión y uno de dos en el capítulo 11."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(type(df['ciudad']))\n",
    "print(type(df[['ciudad', 'monto']]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Corchetes simples te dan una columna (Series). Corchetes dobles te dan una\n",
    "tabla con esas columnas (DataFrame). Esa confusión es de las más comunes al\n",
    "empezar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df['ciudad'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el desastre 😅 **Lima escrita de cuatro formas.**\n",
    "\n",
    "Si agrupas así, Lima aparece como cuatro ciudades chiquitas de 115 a 123\n",
    "ventas, en vez de una de 474 que sería la segunda del país. Un informe con ese\n",
    "error se presenta y nadie lo nota."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Limpiar, que es el trabajo de verdad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Duplicados primero\n",
    "print('antes  :', len(df))\n",
    "df = df.drop_duplicates(subset='id_venta')\n",
    "print('despues:', len(df))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Treinta y siete ventas duplicadas. Si sumas montos sin quitarlas, tu total\n",
    "está inflado y nadie te va a avisar.\n",
    "\n",
    "Ese `subset='id_venta'` importa: sin él, pandas solo quita filas\n",
    "idénticas en *todas* las columnas, y un duplicado real muchas veces tiene\n",
    "alguna diferencia tonta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# La ciudad, con los metodos .str\n",
    "df['ciudad'] = (df['ciudad'].str.strip().str.lower()\n",
    "                .str.normalize('NFKD')\n",
    "                .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "\n",
    "print(df['ciudad'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De nueve ciudades a seis, y Lima dejó de estar partida en cuatro pedazos 🌟\n",
    "\n",
    "Ese `.str` del medio es la clave: **aplica un método de texto\n",
    "a toda la columna de golpe**. Es la vectorización del capítulo 11, aplicada\n",
    "a cadenas. Sin él tendrías que recorrer las tres mil filas.\n",
    "\n",
    "Lo del `normalize('NFKD')` es lo que quita las tildes: separa la\n",
    "letra de su tilde y después bota lo que no es ASCII. Es un truco feo y es el que\n",
    "funciona."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# El monto, con la coma decimal\n",
    "df['monto'] = pd.to_numeric(df['monto'].str.replace(',', '.'), errors='coerce')\n",
    "\n",
    "print(df['monto'].dtype)\n",
    "print('no convertidos:', df['monto'].isna().sum())\n",
    "print(round(df['monto'].sum(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`errors='coerce'` significa \"lo que no puedas convertir, déjalo\n",
    "como nulo en vez de reventar\". Y esa segunda línea, la que cuenta cuántos no\n",
    "convirtió, es obligatoria: si no da cero, tienes que ir a mirarlos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los nulos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.isna().sum().sort_values(ascending=False).head(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí viene la parte que casi ningún tutorial dice. La pregunta correcta\n",
    "**no es \"con qué relleno\", es \"por qué falta\"**.\n",
    "\n",
    "Mira lo que pasa cuando lo preguntas de verdad:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['sin_compra_previa'] = df['fecha_ultima_compra'].isna().astype(int)\n",
    "\n",
    "print(df.groupby('sin_compra_previa')['compro'].mean().round(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los que sí tenían una compra anterior compran el 61%; los que no, el 41% 😮\n",
    "\n",
    "**Veinte puntos de diferencia.** Si hubiera rellenado esa fecha\n",
    "con un promedio, habría borrado lo más predictivo de todo el archivo.\n",
    "\n",
    "Ese hueco no era un dato que falta, era un dato en sí mismo: el cliente es\n",
    "nuevo. Y la forma correcta de tratarlo es marcarlo con una bandera, no taparlo 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Seleccionar y filtrar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.loc[0, 'ciudad'])\n",
    "print(df.loc[0:2, ['ciudad', 'monto']])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grandes = df[df['monto'] > 1000]\n",
    "print(len(grandes))\n",
    "\n",
    "lima_grandes = df[(df['ciudad'] == 'lima') & (df['monto'] > 1000)]\n",
    "print(len(lima_grandes))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es exactamente la máscara del capítulo 11, con `&` y con\n",
    "paréntesis en cada condición. Si usas `and`, pandas te lo dice\n",
    "clarito:"
   ]
  },
  {
   "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",
    "    df[(df['ciudad'] == 'lima') and (df['monto'] > 1000)]\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: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y para filtrar por varios valores, en vez de encadenar `|`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(df[df['ciudad'].isin(['lima', 'arequipa'])]))\n",
    "print(len(df[df['canal'].str.contains('What', na=False)]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El aviso rojo que todo el mundo ve y nadie entiende"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este te va a salir. Vamos a provocarlo para que sepas qué significa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "solo_lima = df[df['ciudad'] == 'lima']\n",
    "solo_lima['con_igv'] = solo_lima['monto'] * 1.18\n",
    "\n",
    "print(solo_lima['con_igv'].head(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Puede que en tu versión ese bloque te saque un\n",
    "`SettingWithCopyWarning` en rojo. Funciona igual, y ese es justo el\n",
    "problema: **pandas no sabe si `solo_lima` es una tabla nueva o\n",
    "una vista de la original**, así que no puede garantizarte a cuál de las\n",
    "dos le estás escribiendo.\n",
    "\n",
    "La solución es una palabra:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "solo_lima = df[df['ciudad'] == 'lima'].copy()\n",
    "solo_lima['con_igv'] = solo_lima['monto'] * 1.18\n",
    "\n",
    "print(len(solo_lima), round(solo_lima['con_igv'].sum(), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La regla que te ahorra el aviso para siempre: **si filtras y después vas\n",
    "a modificar, pon `.copy()` al filtrar**. Es la misma historia\n",
    "del capítulo 4, la copia que no se copió 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## groupby: la operación estrella"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.groupby('ciudad')['monto'].sum().round(2).sort_values(ascending=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Te acuerdas del bucle con `por_ciudad.get(ciudad, 0) + monto` del\n",
    "capítulo 4? Esto es lo mismo, en una línea. Por eso te hice escribir el bucle\n",
    "primero 💜"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "resumen = df.groupby('ciudad').agg(\n",
    "    ventas=('id_venta', 'count'),\n",
    "    total=('monto', 'sum'),\n",
    "    ticket_medio=('monto', 'mean'),\n",
    "    ticket_mediano=('monto', 'median'),\n",
    ").round(2).sort_values('total', ascending=False)\n",
    "\n",
    "print(resumen)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa es una tabla que puedes mandar tal cual, y mira lo que cuenta: el ticket\n",
    "medio y el mediano se llevan entre doscientos y trescientos soles de diferencia\n",
    "en todas las ciudades. O sea que en todas hay unas pocas ventas grandes que jalan\n",
    "el promedio hacia arriba.\n",
    "\n",
    "Y se puede agrupar por dos cosas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.groupby(['ciudad', 'canal'])['monto'].sum().round(0).head(6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y darle la vuelta para que se lea como una tabla de doble entrada, que es lo\n",
    "que en Excel sería una tabla dinámica:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tabla = df.pivot_table(index='ciudad', columns='canal',\n",
    "                       values='monto', aggfunc='sum').round(0)\n",
    "print(tabla)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Juntar dos tablas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "metas = pd.DataFrame({\n",
    "    'ciudad': ['lima', 'arequipa', 'cusco', 'piura', 'chiclayo', 'trujillo'],\n",
    "    'meta': [400000, 420000, 380000, 400000, 390000, 400000],\n",
    "})\n",
    "\n",
    "comparativo = resumen.reset_index().merge(metas, on='ciudad')\n",
    "comparativo['cumplimiento'] = (comparativo['total'] / comparativo['meta'] * 100).round(1)\n",
    "\n",
    "print(comparativo[['ciudad', 'total', 'meta', 'cumplimiento']])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un aviso importante con `merge`: **comprueba siempre cuántas\n",
    "filas te quedaron**. Si una ciudad estaba escrita distinta en las dos\n",
    "tablas, esa fila desaparece sin decir nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(resumen), len(metas), len(comparativo))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis, seis y seis. Si el tercero fuera cinco, faltaría una ciudad y habría que\n",
    "ir a buscar por qué antes de seguir 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Encadenar, para no llenar el cuaderno de variables"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en cómo hemos ido trabajando: `parte = ...`,\n",
    "`resumen = ...`, `comparativo = ...`. Funciona, y a las\n",
    "veinte celdas ya no te acuerdas de cuál era cuál 😅\n",
    "\n",
    "pandas deja escribir todo el camino de una vez, y se lee de arriba abajo como\n",
    "una receta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "resumen_canal = (df\n",
    "                 .query('monto > 0')\n",
    "                 .assign(con_igv=lambda t: t['monto'] * 1.18,\n",
    "                         grande=lambda t: t['monto'] > 1000)\n",
    "                 .groupby('canal')\n",
    "                 .agg(ventas=('id_venta', 'count'),\n",
    "                      total=('con_igv', 'sum'),\n",
    "                      grandes=('grande', 'sum'))\n",
    "                 .assign(pct_grandes=lambda t: (t['grandes'] / t['ventas'] * 100).round(1))\n",
    "                 .sort_values('total', ascending=False))\n",
    "\n",
    "print(resumen_canal.round(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso son cinco pasos y ni una variable intermedia. Las piezas nuevas 🧩\n",
    "\n",
    "- 🔎 `query('monto > 0')` es lo mismo que\n",
    "`df[df['monto'] > 0]`, escrito como se dice. Con dos o tres\n",
    "condiciones se nota mucho: `query('monto > 0 and canal == \"Web\"')`,\n",
    "sin paréntesis ni ampersands.\n",
    "\n",
    "- ➕ `assign` crea columnas y devuelve una tabla nueva, así que no\n",
    "toca la original. Ese `lambda t:` es \"la tabla tal como está en este\n",
    "punto de la cadena\", que es lo que hace que funcione en medio del encadenado.\n",
    "\n",
    "- 📊 `agg` con nombres (`ventas=('id_venta', 'count')`)\n",
    "te deja bautizar cada columna del resultado. Sin eso salen apiladas y con nombres\n",
    "raros.\n",
    "\n",
    "Lo que devuelve la tabla, dicho en cristiano: los cuatro canales facturan\n",
    "parecido, y entre el 25% y el 29% de sus ventas pasan de mil soles. Otra vez\n",
    "cuatro canales que se parecen mucho 🤷‍♀️\n",
    "\n",
    "Y el detalle de formato que hace que esto sea legible: los paréntesis\n",
    "alrededor de todo. Sin ellos, Python no deja partir la línea y te queda una fila\n",
    "kilométrica."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que pesa tu tabla, y cómo bajarlo a la mitad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto no importa con tres mil filas. Importa muchísimo con tres millones, que\n",
    "es cuando el cuaderno se pone lento y no sabes por qué 🐢"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "texto = df['ciudad'].memory_usage(deep=True)\n",
    "categoria = df['ciudad'].astype('category').memory_usage(deep=True)\n",
    "\n",
    "print('como texto   :', f'{texto:,} bytes')\n",
    "print('como category:', f'{categoria:,} bytes')\n",
    "print('se ahorra    :', round((1 - categoria / texto) * 100, 1), '%')\n",
    "print('valores distintos:', df['ciudad'].nunique(), 'de', len(df), 'filas')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el porqué: **seis valores distintos repetidos tres mil\n",
    "veces**.\n",
    "\n",
    "Como texto, pandas guarda las ocho letras de \"arequipa\" quinientas cuarenta y\n",
    "seis veces. Como `category`, guarda las seis palabras una vez y luego\n",
    "tres mil numeritos que apuntan a ellas 🏷️\n",
    "\n",
    "Y ese `deep=True` no es opcional. Sin él, pandas te cuenta lo que\n",
    "pesan los punteros y no el texto al que apuntan, así que te sale un número\n",
    "bonito y falso."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "antes = df.memory_usage(deep=True).sum()\n",
    "\n",
    "compacto = df.copy()\n",
    "for columna in ['ciudad', 'segmento', 'canal', 'categoria']:\n",
    "    compacto[columna] = compacto[columna].astype('category')\n",
    "\n",
    "despues = compacto.memory_usage(deep=True).sum()\n",
    "\n",
    "print('antes  :', f'{antes:,} bytes')\n",
    "print('despues:', f'{despues:,} bytes')\n",
    "print('ahorro :', round((1 - despues / antes) * 100, 1), '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro columnas convertidas y la tabla pesa la mitad. Sin perder un solo\n",
    "dato 💜\n",
    "\n",
    "La regla para saber cuándo aplicarlo: **si los valores distintos son\n",
    "muchos menos que las filas**, va a `category`. Ciudad, canal,\n",
    "segmento, estado, sí. Nombre de cliente o código de venta, no, porque ahí casi\n",
    "cada fila es distinta y encima le añades el diccionario."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Las tres primeras miradas\n",
    "\n",
    "Carga el archivo y muestra su forma, sus tipos y cuántos\n",
    "nulos tiene la columna satisfaccion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuántas ventas por canal\n",
    "\n",
    "Cuenta las ventas de cada canal, ordenadas de mayor a\n",
    "menor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Filtrar con dos condiciones\n",
    "\n",
    "Cuenta las ventas del canal WhatsApp con más de 10\n",
    "unidades."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El error del and\n",
    "\n",
    "Escribe el filtro anterior con `and` y lee el\n",
    "error."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Una columna nueva\n",
    "\n",
    "Crea una columna con el monto por unidad y muestra las tres\n",
    "ventas con el precio unitario más alto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Resumen por segmento\n",
    "\n",
    "Saca ventas, total y ticket mediano por segmento, ordenado\n",
    "por total."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El hueco que dice algo\n",
    "\n",
    "Comprueba si el hueco de `descuento` también\n",
    "cambia la tasa de compra."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Tabla de doble entrada\n",
    "\n",
    "Arma una tabla con las unidades vendidas por segmento y\n",
    "categoría."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Provoca el aviso\n",
    "\n",
    "Filtra sin `.copy()`, agrega una columna, y\n",
    "después hazlo bien."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Del bucle a pandas\n",
    "\n",
    "Escribe el total por canal de las dos formas: con el bucle\n",
    "del capítulo 4 y con `groupby`. Comprueba que dan lo mismo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Todo el análisis en una sola cadena\n",
    "\n",
    "Ticket promedio, número de ventas y porcentaje de ventas\n",
    "grandes, por ciudad, sin variables intermedias."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 11\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Qué columnas vale la pena convertir\n",
    "\n",
    "Mira cuántos valores distintos tiene cada columna de texto y\n",
    "cuánto pesa cada una."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 12\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 13. Los mismos números y aun así distintos\n",
    "\n",
    "Compara el conteo de ciudades antes y después de convertir a\n",
    "`category`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 13\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 14. El merge que multiplica las filas\n",
    "\n",
    "Junta las ventas con una tabla de vendedores por ciudad y\n",
    "mira qué le pasa al total."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Corregir un dato y que no se corrija nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es, con diferencia, la trampa que más veces me han traído en consultoría. Sin error, sin aviso, y con el dato sucio intacto 🫥"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Filtras las filas de Lima, corriges la ciudad en ese trozo, y sigues. Sin error y sin aviso.\n",
    "\n",
    "```\n",
    "lima = ventas[ventas['ciudad'] == 'Lima']\n",
    "lima['ciudad'] = 'LIMA'\n",
    "\n",
    "print((ventas['ciudad'] == 'LIMA').sum())\n",
    "# 0\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Filtras un DataFrame, le cambias un valor al resultado y pandas te avisa con un warning. ¿Qué te está diciendo?\n",
    "\n",
    "a) Que no sabe si estás tocando el original o una copia\n",
    "\n",
    "b) Que el filtro está mal escrito\n",
    "\n",
    "c) Que la columna no existe\n",
    "\n",
    "d) Que hay que usar loc siempre"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📥 `read_csv` y después `.dtypes`, siempre. Una columna\n",
    "de dinero que salió como texto tiene algo dentro.\n",
    "\n",
    "- 🧹 Duplicados con `subset`, texto con `.str`, números\n",
    "con `to_numeric(errors='coerce')` y contando cuántos no\n",
    "convirtieron.\n",
    "\n",
    "- ❓ Ante un hueco, la pregunta es por qué falta, no con qué se rellena.\n",
    "\n",
    "- 🔗 Filtros con `&` y `|`, con paréntesis, nunca con\n",
    "`and`.\n",
    "\n",
    "- 📋 Si filtras y vas a modificar, `.copy()`.\n",
    "\n",
    "- 📊 `groupby` con `.agg()` te da la tabla que se manda\n",
    "tal cual.\n",
    "\n",
    "- 🔍 Después de un `merge`, cuenta las filas.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se corrige sobre el original y diciendo dónde. Todo lo demás es corregir\n",
    "una copia."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 15 hacemos que todo esto se vea, con matplotlib. Y ahí hay una\n",
    "regla que vale más que todas las opciones de color juntas.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 12 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/pandas/\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
}
