{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python: de datos sucios a una conclusion\n",
    "\n",
    "Cuaderno de practica de la guia **Python para analisis de datos** de Miss Yera.\n",
    "Corre de arriba abajo. No necesitas instalar nada si lo abres en Google Colab.\n",
    "\n",
    "Los datos son ventas de una distribuidora peruana, con los defectos que traen\n",
    "los datos reales: fechas en dos formatos, la ciudad escrita de cuatro maneras,\n",
    "montos como texto, duplicados y tres tipos distintos de dato faltante.\n",
    "\n",
    "Guia completa: https://missyera.com/guias/python-para-analisis-de-datos/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Mirar antes de tocar\n",
    "\n",
    "Los tres `print` de abajo son el paso que mas gente se salta y el que evita la mitad de los analisis que salen mal: cuantas filas hay, de que tipo es cada columna y cuantos valores faltan."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "URL = \"https://missyera.com/static/datasets/ventas-miss-yera.csv\"\n",
    "df = pd.read_csv(URL)\n",
    "\n",
    "print(df.shape)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.dtypes)\n",
    "print()\n",
    "print(df.isna().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fijate en `monto`: llego como **object**, o sea texto. Si sumas esa columna ahora mismo, pandas concatena cadenas en vez de sumar numeros."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. La ciudad esta escrita de cuatro maneras\n",
    "\n",
    "Un `GROUP BY` sobre esta columna devolveria cuatro Limas distintas. Es el defecto que mas veces he visto arruinar un informe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"ciudad\"].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"ciudad\"] = (df[\"ciudad\"]\n",
    "                 .str.strip()\n",
    "                 .str.normalize(\"NFKD\")\n",
    "                 .str.encode(\"ascii\", \"ignore\").str.decode(\"utf-8\")\n",
    "                 .str.title())\n",
    "\n",
    "df[\"ciudad\"].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. El monto, que viaja como texto con coma decimal\n",
    "\n",
    "Parte de las filas traen `1234,56` en vez de `1234.56`. Convertir sin arreglar la coma deja nulos silenciosos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"monto\"] = (df[\"monto\"].astype(str)\n",
    "                            .str.replace(\",\", \".\", regex=False))\n",
    "df[\"monto\"] = pd.to_numeric(df[\"monto\"], errors=\"coerce\")\n",
    "\n",
    "print(\"nulos tras convertir:\", df[\"monto\"].isna().sum())\n",
    "print(\"montos negativos:\", (df[\"monto\"] < 0).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los negativos son notas de credito mal cargadas. La decision de que hacer con ellos es de negocio, no tecnica: aqui se marcan y se sacan del analisis de venta, pero no se borran de la tabla."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"es_nota_credito\"] = df[\"monto\"] < 0\n",
    "ventas = df[~df[\"es_nota_credito\"]].copy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Las fechas, en dos formatos\n",
    "\n",
    "Dos sistemas exportan distinto. `format=\"mixed\"` deja que pandas resuelva cada fila."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ventas[\"fecha\"] = pd.to_datetime(ventas[\"fecha\"], format=\"mixed\", dayfirst=True)\n",
    "print(ventas[\"fecha\"].min(), \"a\", ventas[\"fecha\"].max())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Los duplicados exactos\n",
    "\n",
    "Son del reproceso de una carga. Si no se quitan, esas ventas se cuentan dos veces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "antes = len(ventas)\n",
    "ventas = ventas.drop_duplicates()\n",
    "print(f\"{antes - len(ventas)} filas duplicadas eliminadas\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Una fila por cliente\n",
    "\n",
    "Aqui empieza el analisis de verdad: cuanto compra cada cliente, cuantas veces y hace cuanto que no vuelve."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corte = ventas[\"fecha\"].max()\n",
    "\n",
    "r = ventas.groupby(\"cliente_id\").agg(\n",
    "    total=(\"monto\", \"sum\"),\n",
    "    compras=(\"monto\", \"count\"),\n",
    "    ticket=(\"monto\", \"mean\"),\n",
    "    ultima=(\"fecha\", \"max\"),\n",
    "    segmento=(\"segmento\", lambda s: s.mode().iat[0]),\n",
    ")\n",
    "r[\"dias_sin_comprar\"] = (corte - r[\"ultima\"]).dt.days\n",
    "r.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. La pregunta: quien concentra la venta\n",
    "\n",
    "El clasico 80/20, calculado sobre tus datos en vez de citado como frase."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r = r.sort_values(\"total\", ascending=False)\n",
    "r[\"acumulado_%\"] = 100 * r[\"total\"].cumsum() / r[\"total\"].sum()\n",
    "\n",
    "top = r[r[\"acumulado_%\"] <= 80]\n",
    "print(f\"{len(top)} clientes ({100*len(top)/len(r):.1f}%) hacen el 80% de la venta\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Los que importan y se estan enfriando\n",
    "\n",
    "Esta es la salida que se lleva a una reunion: no un grafico, una lista con nombre y apellido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "riesgo = top[top[\"dias_sin_comprar\"] > 90].sort_values(\"total\", ascending=False)\n",
    "print(f\"{len(riesgo)} clientes grandes llevan mas de 90 dias sin comprar\")\n",
    "print(f\"representan S/{riesgo['total'].sum():,.0f} de venta historica\")\n",
    "riesgo.head(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Vectorizar, o por que no hay ningun bucle aqui\n",
    "\n",
    "Compara los dos enfoques. La diferencia no es de estilo: en un archivo de millones de filas es la diferencia entre que corra y que no."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%timeit -n 3 -r 3\n",
    "ventas[\"margen_lento\"] = [m * 0.3 for m in ventas[\"monto\"]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%timeit -n 3 -r 3\n",
    "ventas[\"margen_rapido\"] = ventas[\"monto\"] * 0.3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas\n",
    "\n",
    "No se entreno ningun modelo y no hizo falta. Casi todo lo que se pide en una empresa se resuelve con limpiar bien, un `groupby`, un orden y una resta de fechas.\n",
    "\n",
    "El siguiente paso, cuando la pregunta sea *que va a pasar* y no *que paso*, es el cuaderno de machine learning."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}