{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cuando las filas van en orden\n",
    "\n",
    "Tres años de venta diaria peruana, y el corte al azar que se ve un 23% mejor de lo que es.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 17 de **Machine learning 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/machine-learning-desde-cero/series-de-tiempo/\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": [
    "El capítulo 16 enseñó a partir los datos y a repetir el\n",
    "corte varias veces. Todo correcto, y con una suposición escondida: **que las\n",
    "filas son intercambiables** 🔀\n",
    "\n",
    "Con una fecha de por medio dejan de serlo, y ahí todo lo anterior se rompe de\n",
    "una forma que no da ningún error.\n",
    "\n",
    "Una pregunta antes de empezar: **¿qué tiene de malo predecir el martes\n",
    "pasado usando datos del viernes siguiente?** Suena absurdo dicho así, y es\n",
    "exactamente lo que hace `train_test_split` sobre una serie 📆"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tres años de venta diaria"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este archivo es distinto del que usa el resto del libro. Aquí no hay una fila\n",
    "por pedido con su ciudad, su canal y su segmento: hay **una fila por\n",
    "día**, con lo que se facturó ese día en toda la distribuidora. Y esa\n",
    "diferencia lo cambia todo, porque las filas dejan de ser intercambiables 📆"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.metrics import mean_absolute_error\n",
    "from sklearn.model_selection import TimeSeriesSplit, train_test_split\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-diarias-peru.csv'\n",
    "d = pd.read_csv(URL, parse_dates=['fecha'])\n",
    "print('filas:', len(d), '| de', d['fecha'].min().date(), 'a', d['fecha'].max().date())\n",
    "print('fechas duplicadas:', int(d['fecha'].duplicated().sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres fechas repetidas, de un reproceso de la carga. En una tabla normal un\n",
    "duplicado infla una suma; en una serie además **rompe el orden**, y\n",
    "todo lo que viene después asume que hay una fila por día."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que sale si no las quitas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esas tres fechas repetidas no son un detalle estético. Mira qué pasa si\n",
    "intentas ordenar la serie por día sin quitarlas:"
   ]
  },
  {
   "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",
    "    d.set_index('fecha').reindex(\n",
    "        pd.date_range(d['fecha'].min(), d['fecha'].max(), freq='D'))\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: cannot reindex on an axis with duplicate labels\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*duplicate labels*. pandas no sabe cuál de las dos ventas del mismo día\n",
    "poner, y hace lo correcto: negarse 🛑\n",
    "\n",
    "Este error es de los buenos, porque te obliga a decidir. Y decidir es de\n",
    "negocio, no técnico: si son dos cargas del mismo día hay que quedarse con una, y\n",
    "si de verdad hubo dos cierres de caja hay que sumarlas. Aquí son un reproceso,\n",
    "así que se quitan."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El hueco que no es un cero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = d.drop_duplicates(subset='fecha').sort_values('fecha').reset_index(drop=True)\n",
    "esperados = (d['fecha'].max() - d['fecha'].min()).days + 1\n",
    "print('dias que deberia haber:', esperados, '| hay:', len(d), '| faltan:', esperados - len(d))\n",
    "\n",
    "completo = d.set_index('fecha').reindex(\n",
    "    pd.date_range(d['fecha'].min(), d['fecha'].max(), freq='D'))\n",
    "print('nulos tras reindexar:', int(completo['venta'].isna().sum()))\n",
    "print('nulos en transacciones:', int(completo['transacciones'].isna().sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Faltan doce días, y esto es de lo más importante del capítulo: **un día\n",
    "sin fila no es un día de venta cero** 🕳️\n",
    "\n",
    "Son días en que no se cerró caja. Si los tratas como ceros hundes el promedio y\n",
    "te inventas doce caídas que no existieron. Y si simplemente los ignoras, cualquier\n",
    "cuenta que dependa del orden (un retardo de siete días, una media móvil) se\n",
    "desplaza sin avisar.\n",
    "\n",
    "Por eso el `reindex`: obliga a que exista una fila por día y\n",
    "convierte el hueco invisible en un `NaN` visible, que es lo que sabes\n",
    "tratar desde el capítulo 6 👀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que hace distinta a una serie"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = completo['venta']\n",
    "print('venta media por dia de la semana:')\n",
    "print(s.groupby(s.index.dayofweek).mean().round(0).to_string())\n",
    "print()\n",
    "print('autocorrelacion:')\n",
    "for k in [1, 7, 14, 28, 365]:\n",
    "    print(f'  retardo {k:3}: {s.autocorr(k):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira las dos tablas juntas, que cuentan lo mismo 🔍\n",
    "\n",
    "El sábado vende 14.642 y el domingo 4.728, o sea tres veces menos. Y la\n",
    "autocorrelación lo dice en números: **ayer te informa poco (0,2341) y el\n",
    "mismo día de la semana pasada te informa muchísimo (0,7686)**.\n",
    "\n",
    "La autocorrelación es la correlación de la serie consigo misma corrida N días.\n",
    "Es la herramienta para descubrir el ritmo sin saber de antemano cuál es, y aquí\n",
    "grita \"siete\" 📢\n",
    "\n",
    "Y si te preguntas por el 0,1209 del retardo 365: con tres años de datos hay\n",
    "muy pocos pares separados un año exacto, así que ese número dice más de la falta\n",
    "de historia que de la estacionalidad anual."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora el error caro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Armamos un modelo normal, con las columnas que uno armaría: qué día es, qué\n",
    "mes, cuánto se vendió hace 1, 7 y 14 días, la media de la última semana y si es\n",
    "feriado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = completo.copy()\n",
    "t['venta'] = t['venta'].interpolate()\n",
    "t['dia_semana'] = t.index.dayofweek\n",
    "t['mes'] = t.index.month\n",
    "for k in [1, 7, 14]:\n",
    "    t[f'lag{k}'] = t['venta'].shift(k)\n",
    "t['media7'] = t['venta'].shift(1).rolling(7).mean()\n",
    "t = t.dropna()\n",
    "\n",
    "COLS = ['dia_semana', 'mes', 'lag1', 'lag7', 'lag14', 'media7', 'feriado']\n",
    "X, y = t[COLS], t['venta']\n",
    "print('filas para modelar:', len(t))\n",
    "print('columnas:', COLS)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 1.096 días quedan 1.024, y conviene saber de dónde salen los 72 que se\n",
    "fueron. Unos pocos son el arranque, porque los primeros catorce días no tienen\n",
    "retardo de catorce. Y el resto se los llevó `dropna()` por una columna\n",
    "que **ni siquiera estamos usando**: `transacciones`, que\n",
    "tiene 59 nulos de los días que el contador estuvo caído 😅\n",
    "\n",
    "Eso es un descuido clásico y silencioso. `dropna()` a secas mira\n",
    "todas las columnas, así que una que te da igual puede borrarte filas buenas. Si\n",
    "te importan esas 59, se escribe `t.dropna(subset=COLS + ['venta'])` y\n",
    "listo.\n",
    "\n",
    "Fíjate en el `.shift(1)` de la media móvil, que no sobra. Sin él la\n",
    "media de siete días incluiría **el día que estamos prediciendo**, y\n",
    "eso es fuga de información del capítulo 12 con ropa de\n",
    "serie 🚩\n",
    "\n",
    "Ahora los dos cortes, el de siempre y el correcto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Xa, Xb, ya, yb = train_test_split(X, y, test_size=0.25, random_state=42)\n",
    "m1 = LinearRegression().fit(Xa, ya)\n",
    "err_azar = mean_absolute_error(yb, m1.predict(Xb))\n",
    "\n",
    "corte = int(len(t) * 0.75)\n",
    "m2 = LinearRegression().fit(X.iloc[:corte], y.iloc[:corte])\n",
    "err_fecha = mean_absolute_error(y.iloc[corte:], m2.predict(X.iloc[corte:]))\n",
    "\n",
    "print(f'error medio con corte al azar  : S/ {err_azar:,.2f}')\n",
    "print(f'error medio con corte por fecha: S/ {err_fecha:,.2f}')\n",
    "print(f'el corte al azar se ve {100*(err_fecha-err_azar)/err_fecha:.1f}% mejor de lo que es')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Un 23% mejor de lo que es** 😳\n",
    "\n",
    "Y no hubo ningún error, ninguna advertencia y ningún aviso. El código corre\n",
    "igual y devuelve un número más bonito, que es la peor combinación posible.\n",
    "\n",
    "La prueba de qué pasó cabe en dos líneas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('con el corte al azar:')\n",
    "print('  ultima fecha de entrenamiento:', Xa.index.max().date())\n",
    "print('  primera fecha de prueba      :', Xb.index.min().date())\n",
    "print('con el corte por fecha:')\n",
    "print('  ultima fecha de entrenamiento:', X.index[:corte].max().date())\n",
    "print('  primera fecha de prueba      :', X.index[corte:].min().date())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está, negro sobre blanco: el modelo del corte al azar **se entrenó\n",
    "hasta el 31 de diciembre de 2025 y se probó en enero de 2023** 🤯\n",
    "\n",
    "Predijo el pasado sabiendo el futuro. Por eso le fue tan bien, y por eso ese\n",
    "número no vale nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si tus filas tienen fecha, la prueba va después del entrenamiento. Siempre, y sin excepciones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Repetir el corte, pero hacia adelante"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La validación cruzada del capítulo 16 también hay que\n",
    "rehacerla, y scikit-learn ya trae la versión correcta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tscv = TimeSeriesSplit(n_splits=5)\n",
    "errores = []\n",
    "for i, (a, b) in enumerate(tscv.split(X), 1):\n",
    "    mm = LinearRegression().fit(X.iloc[a], y.iloc[a])\n",
    "    e = mean_absolute_error(y.iloc[b], mm.predict(X.iloc[b]))\n",
    "    errores.append(e)\n",
    "    print(f'  ventana {i}: entrena {len(a):4} dias, prueba {len(b):3} -> S/ {e:,.0f}')\n",
    "print(f'  media: S/ {np.mean(errores):,.0f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en la columna de la izquierda: el entrenamiento **crece** y\n",
    "la prueba siempre va después. Es como se usaría de verdad, reentrenando cada\n",
    "cierto tiempo con todo lo que ya pasó ⏩\n",
    "\n",
    "Y mira la ventana 5, que es la peor de las cinco con S/ 2.323. Eso no es ruido:\n",
    "es el modelo enfrentándose al tramo más reciente, que es el que más se parece a lo\n",
    "que va a tener que predecir mañana. Si hay una ventana a la que hacerle caso, es\n",
    "la última."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El modelo tonto, que aquí es obligatorio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El capítulo 1 insiste en tener contra qué\n",
    "comparar. En series el punto de comparación es tan simple que da vergüenza y hay\n",
    "que hacerlo igual: **repetir lo que pasó el mismo día de la semana\n",
    "pasada**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tonto = mean_absolute_error(y.iloc[corte:], X.iloc[corte:]['lag7'])\n",
    "print(f'repetir el mismo dia de la semana pasada: S/ {tonto:,.2f}')\n",
    "print(f'nuestro modelo                          : S/ {err_fecha:,.2f}')\n",
    "print(f'mejora: {100*(tonto-err_fecha)/tonto:.1f}%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un 17,8% mejor que copiar la semana pasada 🎉\n",
    "\n",
    "Y eso sí es un resultado que se puede contar, porque tiene contra qué. Si el\n",
    "modelo hubiera salido peor que esta línea, lo honesto sería decirlo y usar la\n",
    "línea, que además no hay que mantener."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que este capítulo no cubre"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Series de tiempo es una especialidad entera y aquí cabe lo imprescindible.\n",
    "Para que sepas qué buscar cuando te haga falta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Si necesitas | Busca |\n",
    "|---|---|\n",
    "| Separar tendencia, ciclo y ruido para verlos | Descomposición estacional |\n",
    "| Un pronóstico clásico bien hecho | ARIMA y suavizado exponencial, que sobre pocas series le ganan a cualquier red |\n",
    "| Predecir varios días de golpe | Pronóstico a varios pasos, que es otro problema y no este |\n",
    "| Un intervalo alrededor del pronóstico | Predicción conforme, que ya está en el capítulo 24 |\n",
    "| Saber si el quiebre de 2024 fue real | Detección de puntos de cambio |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una que no está en la tabla porque es de criterio: **con una sola\n",
    "serie y tres años, casi siempre gana lo simple**. Las redes del libro de\n",
    "[deep learning desde cero](https://missyera.com/guias/deep-learning-desde-cero/) piden\n",
    "muchísimas más series o muchísimo más histórico para justificarse 📉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Un equipo pronostica la venta del mes que viene. Escalan las columnas antes de partir, como se hace siempre, y el error de prueba les sale buenísimo.\n",
    "\n",
    "```\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "X_escalado = StandardScaler().fit_transform(X)      # sobre TODO el historico\n",
    "\n",
    "corte = int(len(X) * 0.75)\n",
    "modelo = LinearRegression().fit(X_escalado[:corte], y[:corte])\n",
    "error = mean_absolute_error(y[corte:], modelo.predict(X_escalado[corte:]))\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "El corte por fecha está bien hecho y aun así hay fuga, porque el `fit_transform` se hizo **antes** y sobre todo el histórico. La media y la desviación con las que se escala salen de datos que incluyen el tramo de prueba, o sea del futuro. Es el mismo error del capítulo de fuga, y sobre una serie es más difícil de ver porque el corte sí parece correcto. Se arregla igual que allá: el escalado va dentro de un pipeline que se ajusta solo con el tramo de entrenamiento. Y hay una versión todavía más escondida de lo mismo: rellenar los huecos con `interpolate` sobre la serie entera, que también mira hacia adelante. En este capítulo se hace a propósito y se puede porque los doce huecos están repartidos, pero en un proyecto de verdad hay que rellenar solo con lo que ya pasó 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis, y el 2 es el que más veces vas a repetir en el trabajo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Encuentra el ritmo sin que te lo digan\n",
    "\n",
    "Recorre la autocorrelación del retardo 1 al 40 y saca los\n",
    "cinco más altos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "autos = {k: s.autocorr(k) for k in range(1, 41)}\n",
    "top = sorted(autos.items(), key=lambda x: -x[1])[:5]\n",
    "for k, v in top:\n",
    "    print(f'  retardo {k:3}: {v:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los cinco tienen que ser múltiplos de 7 o estar pegados. Así se descubre el\n",
    "ritmo de una serie que no conoces, y funciona igual con datos por hora, donde el\n",
    "número mágico es 24."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Mueve el corte y mira si aguanta\n",
    "\n",
    "Prueba el corte por fecha en el 60%, 70%, 80% y 90% y\n",
    "compara los errores."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for pct in [0.6, 0.7, 0.8, 0.9]:\n",
    "    k = int(len(t) * pct)\n",
    "    mm = LinearRegression().fit(X.iloc[:k], y.iloc[:k])\n",
    "    e = mean_absolute_error(y.iloc[k:], mm.predict(X.iloc[k:]))\n",
    "    print(f'  corte en {pct:.0%}: S/ {e:,.0f}  ({len(t)-k} dias de prueba)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si el error cambia muchísimo según dónde cortes, tu estimación depende del\n",
    "tramo que te tocó y hay que reportar el rango, no un número. Es el mismo\n",
    "argumento del capítulo 16."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Los huecos, tratados de tres formas\n",
    "\n",
    "Compara rellenar los doce huecos con cero, con la media y\n",
    "con interpolación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for nombre, serie in [('con cero', s.fillna(0)),\n",
    "                      ('con la media', s.fillna(s.mean())),\n",
    "                      ('interpolando', s.interpolate())]:\n",
    "    print(f'{nombre:14} media S/ {serie.mean():,.0f}   minimo S/ {serie.min():,.0f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con cero la media se hunde y aparecen doce días catastróficos que nunca\n",
    "pasaron. Es el error del capítulo hecho a propósito para que lo veas en números."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El quiebre del segundo almacén\n",
    "\n",
    "Compara la venta media antes y después del 1 de marzo de\n",
    "2024, y después entrena solo con datos posteriores a esa fecha."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "antes = s[s.index < '2024-03-01'].mean()\n",
    "despues = s[s.index >= '2024-03-01'].mean()\n",
    "print(f'antes del quiebre : S/ {antes:,.0f}')\n",
    "print(f'despues           : S/ {despues:,.0f}  ({despues/antes:.2f}x)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hay un salto grande. La pregunta que abre esto es de negocio y no técnica:\n",
    "¿los datos de antes del quiebre ayudan a predecir el después, o ensucian? Prueba\n",
    "las dos y decide con el número, no con la intuición."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Los feriados, que valen mucho\n",
    "\n",
    "Quita la columna `feriado` del modelo y mira\n",
    "cuánto empeora."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sin_feriado = [c for c in COLS if c != 'feriado']\n",
    "mm = LinearRegression().fit(X.iloc[:corte][sin_feriado], y.iloc[:corte])\n",
    "e = mean_absolute_error(y.iloc[corte:], mm.predict(X.iloc[corte:][sin_feriado]))\n",
    "print(f'con feriado : S/ {err_fecha:,.0f}')\n",
    "print(f'sin feriado : S/ {e:,.0f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una columna que sabes de antemano y que vale dinero. En Perú, además, hay que\n",
    "acordarse de que Semana Santa se mueve cada año, así que una lista fija de fechas\n",
    "no sirve."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Tu propia serie\n",
    "\n",
    "Sin dataset. Exporta la venta diaria o semanal de tu negocio\n",
    "(o de tu propio gasto) y aplícale las tres primeras cosas del capítulo: reindexar\n",
    "por fecha, mirar la autocorrelación y partir por fecha."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vale cualquier cosa que tenga fecha: la venta de tu bodega, las facturas\n",
    "emitidas por semana, o los pedidos que entran por WhatsApp. Con doce meses ya se\n",
    "ve la estacionalidad semanal.\n",
    "\n",
    "Y si la autocorrelación no grita ningún número, eso también es un hallazgo:\n",
    "quiere decir que lo que mueve tu serie no es el calendario, y entonces hay que ir\n",
    "a buscar qué es."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tienes dos años de venta diaria y te piden un pronóstico para el mes que viene. ¿Cuál es tu primer paso?\n",
    "\n",
    "a) Reindexar por fecha, mirar la autocorrelación y medir el modelo tonto\n",
    "\n",
    "b) Armar las columnas de retardo y entrenar un boosting\n",
    "\n",
    "c) Partir con train_test_split y validación cruzada normal\n",
    "\n",
    "d) Una red neuronal, que es lo que se usa para series\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Ese es el paso tres. Antes hay que saber si faltan días y contra qué vas a comparar, o no vas a poder decir si el modelo sirvió.\n",
    "\n",
    "*c)* Ese es justo el error del capítulo: con fecha de por medio eso entrena con el futuro.\n",
    "\n",
    "*d)* Con una sola serie y dos años, lo simple gana casi siempre. Y sigue faltando el punto de comparación.\n",
    "\n",
    "Y si el modelo tonto te da un error que el negocio ya acepta, ahí se acabó el proyecto y es el mejor final posible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📆 Con fecha de por medio, la prueba va siempre después del\n",
    "entrenamiento.\n",
    "\n",
    "- 💸 Aquí el corte al azar se veía un 23% mejor, y entrenaba hasta 2025 para\n",
    "predecir 2023.\n",
    "\n",
    "- 🕳️ Un día sin fila no es un día de venta cero. Reindexa y trata el hueco.\n",
    "\n",
    "- 📢 La autocorrelación te dice el ritmo sin que nadie te lo cuente: 0,7686 en\n",
    "el retardo 7.\n",
    "\n",
    "- 🔁 `TimeSeriesSplit` es la validación cruzada de las series, y\n",
    "la última ventana es la que más importa.\n",
    "\n",
    "- 🪞 Y compara siempre contra repetir la semana pasada, que aquí daba\n",
    "S/ 2.613,19.\n",
    "\n",
    "Y si lo que quieres es entender la serie en vez de predecirla, con sus\n",
    "intervalos y sus pruebas, eso es el\n",
    "[libro de estadística desde cero](https://missyera.com/guias/estadistica-desde-cero/) 📐\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 17 de **Machine learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/machine-learning-desde-cero/series-de-tiempo/\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
}
