Indice / Practicas / EJ-3.2
EJ-3.2 · Normalizar por z-score con broadcasting
Tres evaluaciones con dificultades distintas. Un 7 en la más difícil no vale lo mismo que un 7 en la más fácil, y el z-score es lo que las vuelve comparables.
Qué hay que hacer
A partir de estudiantes_notas.csv (40 filas), producir
notas1_normalizadas.csv: las tres notas de cada estudiante expresadas en
desvíos respecto de su propia evaluación, más el promedio de esos
tres.
Archivos
- Entrada ·
estudiantes_notas.csv - Objetivo ·
notas1_normalizadas.csv - Herramienta ·
comparar.py
ELI5Explicado facil
Es lo que hace un profesor cuando dice «un 6 en mi parcial es como un 8 en el de la otra cátedra».
El z-score pone esa frase en un número: a cuántos desvíos del promedio de su examen está cada nota. Después sí se pueden sumar.
El detalle de broadcasting que hace al ejercicio
(40, 3), y la media con axis=0
Una fila por estudiante, una columna por evaluación. La media por evaluación se
calcula con axis=0 y tiene shape (3,): alineada a la derecha
contra (40, 3), ese 3 coincide con el 3 y funciona sin
reshape ni keepdims.
Es el caso «fácil» de la lección 3.5, y está elegido a propósito: el promedio por
fila de la última columna es el otro caso, y ahí el axis=1 vuelve a
aparecer.
El orden importa más de lo que parece
nota_1 tiene un 12,5 y un 10,5. Si participan del cálculo,
la media y el desvío de esa columna quedan corridos, y los 38 z restantes salen
todos mal - sin que nada falle.
Es el mismo enmascaramiento de la lección 3.7, aplicado a la referencia en vez de al criterio: el valor que había que detectar contamina la medida con la que se lo iba a detectar.
El chequeo de treinta segundos
Cada columna z tiene que dar media 0 y desvío 1. Es lo primero que hay que mirar: si una da 0,4, esa columna se normalizó con la referencia equivocada.
for c in ["z_nota_1", "z_nota_2", "z_nota_3"]:
print(c, salida[c].mean(), salida[c].std(ddof=0))
Cómo saber si te salió
python comparar.py mis_normalizadas.csv notas1_normalizadas.csv
Antes de arrancar, mirá los datos
| legajo | estudiante | nota_1 | nota_2 | nota_3 |
|---|---|---|---|---|
| L2601 | Karina Acosta | 9.7 | 4.7 | 8.6 |
| L2602 | Santiago Ramos | 6.0 | 3.8 | 4.2 |
| L2603 | Carla Luna | 8.1 | 3.6 | 3.8 |
| L2604 | Lucas Núñez | 7.3 | 7.4 | 3.4 |
| L2605 | Tamara Torres | 9.5 | 9.5 | 7.8 |
El contrato de salida
notas1_normalizadas.csv · 40 filas
| Columna | Tipo | Que lleva |
|---|---|---|
legajo | texto | El legajo tal cual vino |
estudiante | texto | El nombre tal cual vino |
z_nota_1 | decimal | (nota_1 - media de nota_1) / desvio de nota_1, 3 decimales. Vacio si la nota no es usable |
z_nota_2 | decimal | Idem para nota_2 |
z_nota_3 | decimal | Idem para nota_3 |
z_promedio | decimal | El promedio de los z que ese estudiante tenga, ignorando los ausentes. 3 decimales |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| tokens de ausente | 5 celdas de nota_3 | van van a NaN y no participan de la media ni del desvio de su columna. La celda z queda vacia | notas1_normalizadas.csv, celda vacia |
| notas fuera del rango 0 a 10 | 4 celdas: 12.5 y 10.5 en nota_1, 1 en nota_2 y -1.0 en nota_3 | van van a NaN igual que los ausentes. Una nota imposible no puede participar de la media que despues sirve de referencia | notas1_normalizadas.csv, celda vacia |
| estudiante sin ninguna nota usable | 0 estudiantes en este archivo | z_promedio z_promedio queda vacio. La comprobacion va igual, porque nanmean sobre una fila toda NaN emite RuntimeWarning y devuelve nan | notas1_normalizadas.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- convertir a float mandando a NaN los tokens de ausente y lo que caiga fuera de 0-10
- calcular media y desvio POR COLUMNA, con axis=0 y las funciones nan*
- restar y dividir: el broadcasting de (40, 3) con (3,) funciona sin reshape
- el promedio por fila se calcula sobre los z ya normalizados, con axis=1
El rango se valida ANTES de calcular la media: si el 12.5 entra, la media y el desvio de nota_1 quedan corridos y los 38 z restantes salen todos mal. Es el mismo enmascaramiento de la leccion 3.7, aplicado a la referencia en vez de al criterio.
Conteos clave
| filas de entrada | 40 |
|---|---|
| notas1_normalizadas.csv | 40 |
| celdas z vacias | 9 |
| notas usables | 111 |
| por motivo | ausente: 5, fuera_de_rango: 4 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(normalizadas) == 40
normalizadas['legajo'].is_unique
normalizadas['legajo'].is_monotonic_increasing
normalizadas[['z_nota_1','z_nota_2','z_nota_3']].isna().sum().sum() == 9
normalizadas['z_promedio'].notna().all()
abs(normalizadas['z_nota_1'].mean()) < 0.005
abs(normalizadas['z_nota_2'].mean()) < 0.005
abs(normalizadas['z_nota_3'].mean()) < 0.005
abs(normalizadas['z_nota_1'].std(ddof=0) - 1) < 0.005
normalizadas[['z_nota_1','z_nota_2','z_nota_3']].abs().max().max() < 4
PISTASSi te trabas, abri esto
- La media va con axis=0: una por evaluacion, no una por estudiante. Con shape (3,) el broadcasting contra (40, 3) funciona sin reshape ni keepdims.
- El desvio va con ddof=0 (el default de NumPy). Con ddof=1 los z quedan todos multiplicados por un factor y ninguno coincide.
- Las notas fuera de rango se descartan ANTES de calcular la media. Si el 12.5 participa, los 38 z de esa columna salen corridos y nada avisa.
- Cada columna z tiene que dar media 0 y desvio 1. Es el chequeo mas rapido: si una da 0.4, esa columna se normalizo con la referencia equivocada.
Así tiene que quedar
| legajo | estudiante | z_nota_1 | z_nota_2 | z_nota_3 | z_promedio |
|---|---|---|---|---|---|
| L2601 | Karina Acosta | 1.732 | -0.602 | 1.101 | 0.744 |
| L2602 | Santiago Ramos | -0.169 | -1.079 | -0.943 | -0.73 |
| L2603 | Carla Luna | 0.91 | -1.185 | -1.129 | -0.468 |
| L2604 | Lucas Núñez | 0.499 | 0.829 | -1.315 | 0.004 |
| L2605 | Tamara Torres | 1.629 | 1.942 | 0.73 | 1.434 |
5 vienen de los tokens de ausente de nota_3 y 4 de notas fuera de rango.
Una celda z vacía significa «esta nota no era usable», y eso no es un hueco del
resultado: es el resultado.
z_promedio, en cambio, tiene valor en las 40 filas, porque ningún
estudiante se quedó sin las tres notas.
ddof=0, otra vez
El desvío va con el default de NumPy. Con ddof=1 todos los z quedan
multiplicados por el mismo factor y ninguno coincide con el objetivo: no es un error de
lógica, es un parámetro que el contrato fija porque las dos opciones son
defendibles.