Resumir la demanda con estadística descriptiva sin caer en las trampas —el promedio que esconde la cola, el percentil que se confunde con un porcentaje, la correlación que se deshace por dentro— y detectar patrón estacional y tendencia traducidos a decisión.
El notebook de esta clase: https://gaa1104.vercel.app/notebooks/clase-13.ipynb
Ing. en Administración · mención Finanzas · Prof. Tomás Fernández
Dos preguntas: cuánto se vende normalmente y cuánto se sale de lo normal (distribución), y si eso se repite cada año (tiempo).
La forma la decide el trabajo que hace el dato —magnitud, identidad, polaridad, cambio en el tiempo, distribución—, no el gusto. Regla del curso: un solo eje.
Lo que quedó planteado la clase anterior (se proyecta la pregunta, NO la respuesta):
«Ya tienes el dato limpio y modelado. Si te preguntan cuánto compra "un cliente típico", ¿contestas con el promedio?»
Y antes, los 90 segundos que cierran la otra pregunta de la clase 12 —la que respondía el notebook espejo del sábado:
«pandas dio 1.009.103 y SQL 1.009.104. ¿Cuál era la fila culpable y qué convención la explica?» → el redondeo del medio decimal: DuckDBROUND()va half-up,numpyva al par. Una fila. Se cierra aquí y no se vuelve a tocar.
Dos preguntas más, en silencio, 60 segundos (espaciado 1–3–7–21):
dim_fecha se construye una sola vez, y qué columnas le pusimos que no estaban en el archivo original?Par (1 min) → plenaria (2 min).
Cerrar la pregunta colgada con ellos. Recoger las respuestas y no corregir todavía: hoy la clase es la corrección.
«El que dijo "depende" tiene razón y no sabe cuánta. Vamos a ponerle número al "depende".»
Partir por la Harina, que es la bien portada, para instalar el vocabulario:
h = f18[f18.producto == "Harina de trigo"].groupby("fecha").cantidad.sum() h.describe()
| Harina de trigo, demanda diaria | |
|---|---|
| días | 1.096 |
| promedio | 152,0 |
| desviación | 55,7 |
| mínimo | 44 |
| P25 | 112 |
| mediana (P50) | 144 |
| P75 | 188 |
| P90 | 232 |
| máximo | 377 |
El nombre de lo que están haciendo, dicho una vez y en voz alta: «esto se llama análisis exploratorio de datos, EDA. Es el requisito 2.4 del Parcial 2 y ahí aparece con ese nombre. No es un paso previo al trabajo: es el trabajo.»
La pregunta de negocio que ordena el bloque: «si compras 152 unidades de harina todos los días, ¿cuántos días te quedas corto?» La respuesta es casi la mitad de los días: la mediana es 144.
«El promedio planifica para el cliente promedio. El cliente promedio no existe. Existe el día de 44 unidades y existe el día de 377.»
Y la tabla completa, que es donde aparece el problema de verdad:
d = f18.groupby(["producto", "fecha"]).cantidad.sum().reset_index() t = d.groupby("producto").cantidad.agg( promedio="mean", mediana="median", desv="std", p75=lambda s: s.quantile(0.75), maximo="max") t["cv"] = (t.desv / t.promedio).round(2) t.sort_values("cv", ascending=False)
| producto | promedio | mediana | desviación | P75 | máximo | CV |
|---|---|---|---|---|---|---|
| Azúcar | 128,3 | 117,0 | 247,8 | 147,0 | 8.205 | 1,93 |
| Sal | 148,7 | 143,0 | 199,1 | 171,0 | 6.567 | 1,34 |
| Levadura | 51,1 | 46,0 | 61,6 | 62,0 | 1.974 | 1,21 |
| Aceite vegetal | 87,9 | 84,0 | 31,1 | 109,0 | 230 | 0,35 |
| Arroz | 104,3 | 99,5 | 36,2 | 126,0 | 302 | 0,35 |
| Cacao | 44,3 | 40,0 | 20,0 | 58,0 | 129 | 0,45 |
| Café | 65,6 | 62,0 | 24,7 | 80,0 | 167 | 0,38 |
| Harina de trigo | 152,0 | 144,0 | 55,7 | 188,0 | 377 | 0,37 |
| Leche en polvo | 77,4 | 74,0 | 27,7 | 94,0 | 218 | 0,36 |
| Mantequilla | 61,2 | 58,0 | 24,1 | 77,0 | 154 | 0,39 |
Parar. Proyectar. Callarse. La columna del máximo grita sola. «El azúcar vende 128 al día en promedio y tuvo un día de 8.205. ¿Qué pasó ese día?» Que lo busquen ellos:
f18.nlargest(3, "cantidad")[["id_venta", "fecha", "producto", "cantidad"]]
| id_venta | fecha | producto | cantidad |
|---|---|---|---|
| 5909 | 2018-07-19 | Azúcar | 8.205 |
| 14576 | 2020-10-17 | Sal | 6.567 |
| 9385 | 2019-06-12 | Levadura | 1.974 |
Tres líneas. De 11.541. Y la medición que cierra el bloque:
lim = d[d.cantidad < 1000]
| producto | promedio con | promedio sin | mediana con | mediana sin | CV con | CV sin |
|---|---|---|---|---|---|---|
| Azúcar | 128,3 | 120,9 | 117,0 | 117,0 | 1,93 | 0,35 |
| Sal | 148,7 | 142,9 | 143,0 | 143,0 | 1,34 | 0,31 |
| Levadura | 51,1 | 49,3 | 46,0 | 46,0 | 1,21 | 0,42 |
El número del día, escrito en la pizarra:
Una línea de 11.541 movió el promedio diario del azúcar 7,4 unidades. La mediana no se movió ni un decimal.
«La mediana no es "el promedio para cobardes". Es el estadístico que no se puede secuestrar por un dato. Y en un archivo que sale de un ERP siempre hay un dato capaz de secuestrarte el promedio.»
CV = desviación / promedio. Ruido relativo, sin unidades, comparable entre productos. 0,35 es demanda tranquila; 1,93 es o un producto salvaje o un dato sucio — y la primera tarea es averiguar cuál de los dos.
Volver a Austral. Declarar en voz alta que el dataset es sintético.
«La demanda de una alimentaria y la cartera de una distribuidora no se parecen en nada. La distribución sí. Y la decisión también.»
La tabla l ya está creada desde antes de clase —se proyecta y se comenta, no se teclea; el SELECT sí se corre en vivo:
CREATE OR REPLACE TABLE l AS SELECT UPPER(strip_accents(TRIM(segmento))) AS segmento, TRY_CAST(fecha_emision AS DATE) AS f_emision, TRY_CAST(NULLIF(fecha_pago,'') AS DATE) AS f_pago FROM read_csv_auto('datos/finanzas/ventas_extracto.csv', all_varchar = true); SELECT segmento, COUNT(*) AS lineas_pagadas, ROUND(AVG(f_pago - f_emision), 1) AS promedio, MEDIAN(f_pago - f_emision) AS mediana, QUANTILE_CONT(f_pago - f_emision, 0.75) AS p75, QUANTILE_CONT(f_pago - f_emision, 0.90) AS p90, MAX(f_pago - f_emision) AS maximo FROM l WHERE f_pago IS NOT NULL AND f_emision IS NOT NULL GROUP BY 1 ORDER BY promedio DESC;
| segmento | líneas pagadas | promedio | mediana | P75 | P90 | máximo |
|---|---|---|---|---|---|---|
| Público | 317 | 91,6 | 86,0 | 113 | 146 | 164 |
| Construcción | 1.308 | 72,5 | 72,0 | 97 | 118 | 163 |
| Minería | 911 | 60,6 | 61,0 | 82 | 101 | 142 |
| Industrial | 1.551 | 56,2 | 57,0 | 74 | 98 | 134 |
| Retail | 1.254 | 55,7 | 57,0 | 74 | 97 | 134 |
Tres lecturas, en este orden. La tercera es la que vale.
SELECT COUNT(*) FROM l WHERE segmento = 'PUBLICO' AND (f_pago - f_emision) > 120; -- 64
64 de las 317 líneas pagadas del sector Público —una de cada cinco— se cobraron después de 120 días. Si tu política de cobranza gatilla en el promedio (91,6), esas 64 ya llevaban un mes de atraso cuando la alerta sonó. Si gatilla en el P75 (113), llegas antes.
Hasta aquí la cola se leyó en una tabla. Ahora se mira, y es el primer gráfico de verdad del ramo.
import matplotlib.pyplot as plt d.boxplot(column="cantidad", by="producto", figsize=(11, 5), rot=45) plt.suptitle("") plt.title("Demanda diaria por producto — con los tres días atípicos") plt.ylabel("unidades por día") plt.show()
Cómo se lee una caja, dicho una vez y despacio —y no es materia nueva, es el IQR de la clase 5 dibujado:
Parar. Proyectar. Callarse otra vez. El gráfico sale ilegible: tres puntos aplastan las diez cajas contra el eje. Y esa fealdad es el hallazgo.
«Miren lo que hicieron tres filas de 11.541: dejaron el gráfico inservible. Eso es exactamente lo que le hicieron al promedio, sólo que en la tabla no se veía tan feo.»
lim.boxplot(column="cantidad", by="producto", figsize=(11, 5), rot=45) plt.suptitle("") plt.title("Demanda diaria por producto — sin los tres días atípicos") plt.ylabel("unidades por día") plt.show()
La regla de la que nadie se escapa, y se dicta: los dos gráficos van al informe, los dos, con la leyenda de qué se sacó y por qué. Publicar sólo el segundo es publicar un dato limpio sin decir que se limpió.
Por qué esto se hace en Python y no en Power BI. El Encargo del Parcial 3 pide textual, en su Etapa 3.3, «compara peso y talla entre las tres categorías con diagramas de caja», y el entregable de sus Etapas 1 a 3 es un .ipynb. La caja tiene que salir del notebook.
P75 = el punto que deja al 75 % de los días por debajo. Si compras el P75, te quedas corto uno de cada cuatro días. Si compras el P90, uno de cada diez. Si compras el máximo histórico, ninguno — y pagas bodega todo el año para el peor día que existió.
| Decisión | Percentil | Qué te cuesta pasarte | Qué te cuesta quedarte corto |
|---|---|---|---|
| Stock de seguridad (compras) | Base = mediana, colchón = P75 | Capital inmovilizado, bodega, merma | Quiebre de stock, venta perdida, cliente que se va |
| Política de cobranza (Finanzas) | Gatillo = P75 de los días de cobro | Llamar a un cliente que iba a pagar igual: cuesta relación | Enterarse tarde: cuesta capital de trabajo |
«El percentil que eliges es tu aversión al riesgo, puesta en un número. No lo elige la estadística: lo elige quien paga la bodega o quien firma la línea de crédito. Tú lo calculas y le muestras el costo de cada opción.»
Harina: base 144 (mediana), colchón hasta 188 (P75), 232 si el quiebre es caro (P90). 44 unidades de colchón. Ése es el número que se lleva el jefe de compras, no el 152.
El número, primero, sin comentarios:
f18[["cantidad", "precio_unitario"]].corr()
r = −0,225 entre cantidad y precio unitario, sobre las 11.541 líneas desde 2018.
La tentación, con las palabras exactas con que la dice un gerente: «bajemos el precio un 10 % y vendemos más.» No discutir. Medir —se proyecta corrido, tres líneas:
for p, g in f18.groupby("producto"): print(p, round(g[["cantidad", "precio_unitario"]].corr().iloc[0, 1], 3))
| producto | r dentro del producto |
|---|---|
| Leche en polvo | +0,176 |
| Arroz | +0,137 |
| Café | +0,118 |
| Sal | +0,041 |
| Harina de trigo | +0,036 |
| Azúcar | +0,016 |
| Levadura | +0,004 |
| Mantequilla | +0,004 |
| Cacao | +0,002 |
| Aceite vegetal | −0,056 |
La correlación global es −0,225. Dentro de cada producto va de −0,056 a +0,176, y OCHO de los diez son positivos.
Silencio. Y después, de dónde salía el −0,225:
m = f18.groupby("producto").agg(precio=("precio_unitario", "mean"), cantidad=("cantidad", "mean")) m.corr() # -0,695
«El −0,225 no dice "cuando el precio baja, se vende más". Dice "la sal es barata y el cacao es caro". La correlación entre los diez promedios es −0,695: ahí estaba todo el efecto.»
La regla, en la pizarra, y es la que se cobra en el requisito 2.3: una correlación calculada sobre una mezcla de cosas distintas mide la mezcla. Antes de interpretar un r, pregunta: ¿sobre qué población lo calculé?
Y el apunte de vocabulario, 30 segundos, porque el enunciado lo usa: la covarianza es la misma medición sin normalizar, cov(x, y) = r · σx · σy. Queda en unidades de x por unidades de y —pesos-por-días, unidades-por-peso— y nadie sabe si eso es mucho o poco. r está entre −1 y +1 y no tiene unidad: por eso se interpreta.
«Y ojo con el orden en que pasó esto: primero apareció un número que confirmaba lo que el gerente quería oír, y recién después lo abrimos. Los números que te dan la razón son los que hay que revisar dos veces.»
Pararse, estirar, subir la luz un segundo. Clase nocturna: el cuerpo también aprende.
Se responde aquí la pregunta que abrió la clase, y se abre la segunda mitad:
«Tienes el nivel —la mediana— y tienes la cola —el P75 y los tres atípicos—. Ahora mira el mismo número mes a mes.»
Se dibuja en la pizarra en diez segundos y ese dibujo queda ahí el resto de la clase: una recta que sube y una onda montada encima.
La tendencia es el piso que se mueve; la estación es la respiración encima del piso. Y las dos se planifican distinto: la estacionalidad vuelve todos los años y te dice cuándo comprar; la tendencia no vuelve y te dice cuánto más que el año pasado.
Y la trampa, ya armada, dicha antes de calcular nada:
«Vamos a buscar el patrón del año, y el azúcar va a aparecer con un pico de invierno espectacular. Guárdense la fila 5909.»
Definición, en una frase, y se copia:
Índice estacional de un mes = lo que ese mes vende, dividido por lo que vendería un mes típico. 1,00 = mes típico. 1,25 = un 25 % sobre lo típico. 0,70 = un 30 % bajo lo típico.
La fórmula operativa del curso, escrita en la pizarra antes de tocar el teclado:
índice(producto, mes) = unidades del mes / (unidades totales del producto / 12)
f18 = f18.copy() f18["mes"] = f18.fecha.dt.month def indices(df): total = df.groupby("producto").cantidad.sum() pm = df.groupby(["producto", "mes"]).cantidad.sum().reset_index() pm["indice"] = (pm.cantidad / (pm.producto.map(total) / 12)).round(2) return pm.pivot(index="producto", columns="mes", values="indice") indices(f18)
Del cuadro que aparece se marcan en rojo dos números y nada más, porque son los que hacen la lección:
«El azúcar sube un 67 % en julio y la sal un 40 % en octubre. Son los índices más grandes del cuadro. ¿Alguien tiene una explicación de negocio para el azúcar de julio?»
Van a inventar una: el invierno, la repostería, las conservas. Anotarla en la pizarra tal cual, con el nombre de quien la dijo.
«Al empezar el bloque les pedí que se guardaran la fila 5909. ¿Qué fecha tenía?» → 19 de julio de 2018.
indices(f18[f18.cantidad < 1000])
| producto | mes | con el atípico | sin el atípico |
|---|---|---|---|
| Azúcar | julio | 1,67 | 1,03 |
| Sal | octubre | 1,40 | 0,95 |
| Levadura | junio | 1,09 | 0,69 |
Escribirlo grande, y borrar de la pizarra la explicación de negocio que ellos inventaron:
El pico de invierno del azúcar no existe. Es una línea. La sal no tiene estación. El repunte de junio de la levadura tampoco existe.
Silencio de cuatro segundos. Y después:
«Yo les mostré un número, ustedes le encontraron una explicación de negocio convincente en once segundos, y era falsa. El cerebro le pone historia a cualquier número. Por eso la limpieza va antes que el análisis: no porque sea prolijo, sino porque después de ver el gráfico ya no puedes des-verlo.»
Y el hallazgo que sí sirve para comprar, del cuadro limpio: la sal no tiene estacionalidad —amplitud 0,20 contra 0,84 del cacao (pico 1,39 en marzo, valle 0,55 en septiembre)—. «A la sal la compras parejo todo el año y dejas de gastar reuniones en ella. Ese hallazgo vale más que los nueve picos juntos, y sólo aparece después de limpiar.»
f18.groupby(f18.fecha.dt.year).cantidad.sum()
| año | unidades | variación |
|---|---|---|
| 2018 | 320.873 | — |
| 2019 | 333.110 | +3,8 % |
| 2020 | 355.121 | +6,6 % |
CAGR 2018 → 2020: +5,2 % anual compuesto. Y la mirada de economista, 30 segundos: aquí medimos unidades, así que el crecimiento es real, no inflación. Si midiéramos pesos habría que deflactar antes de celebrar.
lim = f18[f18.cantidad < 1000] # el dato limpio, siempre dia = lim.groupby("fecha").cantidad.sum() dia.groupby(dia.index.dayofweek).mean().round(1)
Lunes 1.080,2 · martes 1.081,3 · miércoles 991,4 · jueves 990,4 · viernes 995,3 · sábado 592,3 · domingo 603,9.
El fin de semana vende un 44–45 % menos que el lunes. Con el mes y el año defines cuánto compras; con el día de semana defines cómo lo repartes en la bodega. Tres decisiones distintas para tres personas distintas.
Las tres frecuencias, juntas en el dibujo de la bisagra: la recta que sube (año), la onda larga encima (mes) y el diente de sierra chico (semana).
proyección del mes = base × índice estacional del mes × tendencia, donde base = mediana diaria (no el promedio) y tendencia ≈ +5 % anual.
| Decisión | Granularidad | Por qué |
|---|---|---|
| Compras al proveedor | mensual | El pedido tiene plazo de reposición y mínimo; el ruido diario se promedia |
| Programación de bodega | semanal, con el perfil de día de semana | El fin de semana cae 44 % y el galpón se organiza con eso |
| Reposición en sala | diaria | Aquí sí importa el día |
«La granularidad no se elige por elegancia: se elige por el costo de equivocarse. Si comprar de más y comprar de menos cuestan parecido, ve grueso. Si el quiebre es carísimo, ve fino y usa un percentil alto.»
La plantilla del mini-reporte estacional se proyecta y se fotografía —Producto · base (mediana diaria) · mes pico e índice · mes valle e índice · amplitud · CV limpio · colchón (P75 − mediana) · granularidad recomendada · una frase para el jefe de compras—. Se completa para dos productos, en casa, dentro de clase-13.ipynb, entrega del sábado 31-10 23:59.
Austral, otra vez. Declarar en voz alta que el dataset es sintético.
La pregunta que trae el Gerente de Administración y Finanzas:
«El margen bruto del trimestre bajó de 26,2 % a 22,6 %. ¿Estamos vendiendo más barato?»
Ojo: se reconstruyel. La primera mitad de la clase ya usó una tablal—la de segmentos y
fechas de pago— y ésta es otra, con otras columnas y para otra pregunta. En el curso de 36 vivían
en noches distintas y no se pisaban; fusionadas, la segunda reemplaza a la primera. Por eso vaCREATE OR REPLACEy por eso se dice en voz alta: «ojo, estamos rearmandol, ya no es la de hace
una hora». Si alguien vuelve a correr una consulta de la primera mitad después de esto, le va a dar
error de columna — y ése es un buen momento para explicar por qué.
Paso 1 — la base, con la fecha rescatada. Las 21 líneas escritas en dd/mm/yyyy no se pierden: TRY_CAST las convierte en NULL en silencio, y el COALESCE las recupera con el formato explícito.
CREATE OR REPLACE TABLE l AS SELECT numero_factura, linea_negocio, COALESCE(TRY_CAST(fecha_emision AS DATE), TRY_STRPTIME(fecha_emision, '%d/%m/%Y')::DATE) AS fe, TRY_CAST(cantidad AS DOUBLE) AS cant, TRY_CAST(precio_unitario AS DOUBLE) AS precio, TRY_CAST(costo_unitario AS DOUBLE) AS costo, TRY_CAST(descuento_factura AS DOUBLE) AS dsc, TRY_CAST(flete_despacho AS DOUBLE) AS flt FROM read_csv_auto('datos/finanzas/ventas_extracto.csv', all_varchar = true) WHERE moneda = 'CLP';
Paso 2 — el reparto de la clase 10. El descuento y el flete viven al grano factura y están repetidos en cada línea. Acá se dividen entre las líneas, en proporción a lo que cada una aporta a la venta de esa factura. Es la única forma de sumarlos sin contarlos dos veces.
CREATE OR REPLACE TABLE rep AS WITH tot AS (SELECT numero_factura, SUM(cant*precio) AS vf FROM l GROUP BY 1) SELECT l.*, EXTRACT(year FROM l.fe) || '-Q' || EXTRACT(quarter FROM l.fe) AS trim, CASE WHEN t.vf <> 0 THEN l.dsc * (l.cant*l.precio) / t.vf ELSE 0 END AS dsc_lin, CASE WHEN t.vf <> 0 THEN l.flt * (l.cant*l.precio) / t.vf ELSE 0 END AS flt_lin FROM l JOIN tot t USING (numero_factura);
Paso 3 — recién ahora, el margen. Año a año y por línea de negocio, con el descuento y el flete ya repartidos:
SELECT EXTRACT(year FROM fe) AS anio, linea_negocio, ROUND(100.0 * (SUM(cant*precio) - SUM(cant*costo) - SUM(dsc_lin) - SUM(flt_lin)) / SUM(cant*precio), 1) AS margen FROM rep GROUP BY 1, 2 ORDER BY 2, 1;
| línea de negocio | 2022 | 2023 | 2024 | período |
|---|---|---|---|---|
| Seguridad | 32,5 | 33,6 | 32,2 | 32,8 % |
| Herramientas | 27,3 | 26,6 | 27,2 | 27,1 % |
| Eléctrico | 18,7 | 19,7 | 20,6 | 19,7 % |
| Fijaciones | 12,4 | 11,6 | 13,4 | 12,5 % |
«El margen de cada línea es una roca. Seguridad se mueve entre 32,2 y 33,6 en tres años. Nadie está vendiendo más barato. Entonces, ¿de dónde salieron los 3,6 puntos que perdió el total?»
Y el segundo cuadro, que es la respuesta:
-- la tasa de margen de cada línea en el período completo (la que se "congela") CREATE OR REPLACE TABLE q AS SELECT trim, linea_negocio, SUM(cant*precio) AS vta, SUM(cant*precio) - SUM(cant*costo) - SUM(dsc_lin) - SUM(flt_lin) AS mb FROM rep GROUP BY 1, 2; CREATE OR REPLACE TABLE tasa AS SELECT linea_negocio, SUM(mb)/SUM(vta) AS mg FROM q GROUP BY 1; SELECT q.trim, ROUND(100.0 * SUM(q.mb) / SUM(q.vta), 1) AS margen_real, ROUND(100.0 * SUM(q.vta * t.mg) / SUM(q.vta), 1) AS margen_tasa_fija, ROUND(100.0 * SUM(CASE WHEN q.linea_negocio = 'Seguridad' THEN q.vta ELSE 0 END) / SUM(q.vta), 1) AS pct_seguridad FROM q JOIN tasa t USING (linea_negocio) GROUP BY 1 ORDER BY 1;
Cómo se lee la columna del medio, despacio, porque es la idea difícil de la noche:
«Congelé la tasa de margen de cada línea en su valor del período completo —Seguridad 32,8, Herramientas 27,1, Eléctrico 19,7, Fijaciones 12,5— y dejé cambiar sólo la mezcla: cuánto pesó cada línea en la venta de ese trimestre. Es el margen que habrías tenido si ningún precio se hubiera movido nunca.»
Los números del bloque, en la pizarra:
El margen real recorre 3,6 puntos (22,6 % a 26,2 %). Sólo con la mezcla, sin tocar un precio, recorre 1,5 puntos (23,8 % a 25,3 %): el 42 % del movimiento.
Y la mezcla se mueve muchísimo: Seguridad pesó 10,2 % de la venta en 2022-Q3 y 27,8 % en 2024-Q1.
«La respuesta al gerente no es "sí" ni "no". Es: casi la mitad de lo que se movió el margen no fue el precio, fue qué vendimos. El trimestre malo fue el trimestre en que Seguridad —la línea de 32,8 %— cayó al 10 % de la venta. Y eso no se arregla con una circular de descuentos: se arregla en la fuerza de venta.»
Individual, sin apuntes, 8 minutos de reloj; autocorrección con la pauta proyectada, 2 minutos. Es formativo (0 %) y hay que decirlo antes de repartirlo: mide si el método quedó, no la nota. Estructura y pauta intactas (recall/controles-24.md; contenido en controles.md): 4 selecciones múltiples · 1 V/F justificado · 1 mini-problema, 16 puntos.
El docente registra en qué ítem falló la sala completa — ése es el bloque que se re-entrena en la apertura de la clase 14.
Los dos en la misma lámina, uno detrás del otro. Se proyectan las preguntas concretas y no se responden:
Tesorería: «¿El DSO se informa como promedio o como mediana? ¿Y cuál mira el banco cuando evalúa nuestra línea de crédito?»
Los dos números son verdaderos. El promedio (91,6) es el que cuadra con el capital de trabajo, porque la plata inmovilizada es plata × días y la cola sí pesa en la caja. La mediana (86,0) describe la experiencia típica de cobranza. Un informe que entrega uno solo de los dos y no dice cuál eligió está escondiendo la mitad. Y esto no se decide con SQL: se decide con Tesorería y con quien firma el covenant del banco.
Jefe de compras (alimentaria) / Gerencia Comercial (Austral): «Diciembre sube todos los años. ¿Es campaña real del cliente, o somos nosotros adelantando despachos para cerrar la meta?»
Si sube porque el cliente compra, el índice es real y hay que comprar para diciembre. Si sube porque nosotros empujamos despachos el día 28, enero va a estar hundido y tu índice de enero también está mentido. La serie se ve idéntica en los dos casos. La diferencia está en la cabeza del jefe de compras, no en el CSV.
Y la tercera, para el jefe de bodega, en 30 segundos: «la línea 5909 vendió 8.205 unidades de azúcar el 19 de julio de 2018. ¿Es un pedido real de un mayorista, o son 82,05 con la coma corrida?» Si es real, es un cliente que hay que modelar aparte; si es un error, es una fila que hay que corregir antes de que ensucie el índice.
Regla del curso en la pizarra, otra vez: una consulta SQL sin una conversación previa es una opinión con formato de tabla.
Construida con ellos, no dictada (6 min):
La consigna de entrega, dicha en voz alta y por segunda vez con este nombre (4 min):
«Lo que se sube es el notebook,clase-13.ipynb, no una tabla suelta ni un pantallazo. Adentro tienen que estar cuatro cosas: la tabla de estadísticos, los dos boxplot con una celda de texto cada uno diciendo qué se ve y qué se sacó, el cuadro de índices con y sin los atípicos, y la plantilla de los dos productos como celda de texto. Y no es una manía mía: el entregable de la etapa 1 del Parcial 2 es un.ipynby el de las tres primeras etapas del encargo de cierre también. Es condición de entrega, no criterio de nota — un trabajo impecable en otro formato simplemente no se recibe.
El mini-reporte estacional de los dos productos se termina en casa y se entrega el sábado 31-10, 23:59, dentro del mismo archivo.»
El simulacro del Parcial 2, anunciado con una semana de anticipación (3 min):
«El martes 03-11 es el simulacro, en grupo, con el gemelo: un archivo distinto, del mismo negocio, más sucio que el oficial — a propósito. Dos horas. Los grupos y los tres roles —modelador (teclea), verificadora (pregunta "¿de dónde salió ese número?" antes de que el número salga del computador) y redactor (escribe requisitos y decisiones)— se publican esta semana en klearning y se releen al abrir la clase 14. Los tres firman.»
Ticket TC13 (3 min, se proyecta). Anki del día: mediana vs promedio, CV, P75 como decisión, índice estacional, r vs covarianza.
Preguntas que quedan planteadas (se escriben en la pizarra y no se responden hoy):
→ Sabes limpiar, modelar, resumir y detectar patrón. En grupo, con dos horas y un archivo nuevo: ¿quién hace qué, y en qué orden?
→ Si el índice del azúcar mentía por una fila, ¿cuál es la primera consulta que corres en el simulacro antes de calcular nada?
Tres minutos. No suma nota. Sirve para armar la apertura de la próxima clase.
El notebook de esta clase:
https://gaa1104.vercel.app/notebooks/clase-13.ipynb