Indice / Practicas / EJ-3.1
EJ-3.1 · Estadisticas de una columna con NaN
Once medidas sobre dos columnas de notas. La parte que importa no es calcularlas: es separar lo que falta de lo que es imposible, y que las dos categorías sumen.
Qué hay que hacer
A partir de estudiantes_notas.csv (40 filas), producir
notas1_estadisticas.json con dos claves - nota_1 y
nota_3 - y para cada una: cuántas hay, cuántas faltan, cuántas están
fuera de rango, cuántas quedan válidas, y siete medidas sobre esas válidas.
Archivos
- Entrada ·
estudiantes_notas.csv - Objetivo ·
notas1_estadisticas.json - Herramienta ·
comparar.py
ELI5Explicado facil
Es la ficha de un análisis clínico: no alcanza con el valor, hace falta el rango de referencia y cuántas muestras se procesaron.
Acá igual: un promedio de 6,33 no dice nada si no viene con «sobre 38 de 40 notas, y estas 2 se descartaron por imposibles».
Las dos columnas están elegidas a propósito
| Columna | Ausentes | Fuera de rango | Qué enseña |
|---|---|---|---|
nota_1 | 0 | 2 (12,5 y 10,5) | Que una columna sin un solo hueco igual puede tener datos imposibles |
nota_3 | 5 | 1 (−1,0) | Que las dos cosas son independientes y se cuentan por separado |
Si tu n_validas de nota_1 da 40, no chequeaste el
rango. La media cambia de 6,59 a 6,33, y las dos son números correctos de
cosas distintas.
La trampa que no avisa
nota_3 dice NaN
float("NaN") no falla: devuelve nan. Si
convertís antes de filtrar los tokens, esa celda entra al array como un número, y
después np.mean devuelve nan para la columna entera.
El orden correcto es: primero mirar si el texto está en
NULOS = {"", "-", "N/A", "NaN", "null"}, y recién después convertir.
El invariante
assert n_ausentes + n_fuera_de_rango + n_validas == n_total
Tiene que dar 40 en las dos columnas. Es el mismo invariante de la lección 1.6, aplicado a celdas en vez de a filas: si no cierra, hay una nota que no fue ni contada ni descartada.
Cómo saber si te salió
python comparar.py mis_estadisticas.json notas1_estadisticas.json
comparar.py compara JSON recursivamente y te dice la ruta exacta, por
ejemplo nota_3.p25.
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_estadisticas.json · 2 filas
| Columna | Tipo | Que lleva |
|---|---|---|
n_total | entero | Filas del archivo: 40 en las dos columnas |
n_ausentes | entero | Cuantas celdas son un token de ausente o no convierten a numero |
n_fuera_de_rango | entero | De las presentes, cuantas caen fuera de 0 a 10 |
n_validas | entero | Las que quedan: n_total - n_ausentes - n_fuera_de_rango |
minima | decimal | El minimo de las validas, 2 decimales |
p25 | decimal | Percentil 25 de las validas |
mediana | decimal | Percentil 50 de las validas |
p75 | decimal | Percentil 75 de las validas |
maxima | decimal | El maximo de las validas |
media | decimal | El promedio de las validas |
desvio | decimal | Desvio estandar POBLACIONAL de las validas: ddof=0, que es el default de NumPy |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| tokens de ausente en nota_3 | 5 celdas: '', 'N/A', '-' (x2) y 'NaN' | convertir convertir a NaN y contarlos en n_ausentes. NO participan de ninguna medida | notas1_estadisticas.json, nota_3.n_ausentes = 5 |
| el token 'NaN' | 1 celda de nota_3 | tratarlo tratarlo como ausente. float('NaN') NO falla: devuelve nan, asi que si se convierte antes de filtrar, entra igual y arruina la media | notas1_estadisticas.json, contado dentro de los 5 ausentes |
| notas fuera del rango 0 a 10 | 2 en nota_1 (12.5 y 10.5) y 1 en nota_3 (-1.0) | descartar descartar de las medidas y contarlas aparte. El rango lo fija el reglamento de la materia, no los datos | notas1_estadisticas.json, n_fuera_de_rango |
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 no convierta
- contar los NaN: eso es n_ausentes
- de los presentes, contar los que caen fuera de 0 a 10: eso es n_fuera_de_rango
- las once medidas se calculan sobre las validas, que son las restantes
Las tres categorias son excluyentes y tienen que sumar el total. Si se calcula la media sobre 'los presentes' en vez de sobre 'las validas', nota_1 da 6.59 en vez de 6.33: la diferencia son las dos notas imposibles.
Conteos clave
| filas de entrada | 40 |
|---|---|
| notas1_estadisticas.json | 2 |
| nota_1: validas | 38 |
| nota_3: validas | 34 |
| por motivo | ausente: 5, fuera_de_rango: 3 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
todas['nota_1']['n_total'] == 40
todas['nota_3']['n_total'] == 40
todas['nota_1']['n_validas'] == 38
todas['nota_3']['n_validas'] == 34
for c in ('nota_1', 'nota_3'): n_ausentes + n_fuera_de_rango + n_validas == n_total
0 <= minima <= p25 <= mediana <= p75 <= maxima <= 10
0 <= media <= 10
desvio >= 0
todas['nota_1']['n_ausentes'] == 0
todas['nota_3']['n_ausentes'] == 5
PISTASSi te trabas, abri esto
- nota_1 no tiene ningun ausente y aun asi pierde dos notas: son 12.5 y 10.5. Si tu n_validas de nota_1 da 40, no chequeaste el rango.
- El desvio va con ddof=0, que es el default de NumPy (a.std()). pandas usa ddof=1 por defecto: si comparas contra .std() de una Series, no van a coincidir.
- Las tres categorias tienen que sumar 40 en las dos columnas. Es el mismo invariante de la leccion 1.6, aplicado a celdas en vez de a filas.
- sort_keys=True en el json.dump: sin eso, dos soluciones correctas producen archivos distintos y no hay nada que comparar.
Así tiene que quedar
{
"nota_1": {
"desvio": 1.95,
"maxima": 10.0,
"media": 6.33,
"mediana": 6.05,
"minima": 3.0,
"n_ausentes": 0,
"n_fuera_de_rango": 2,
"n_total": 40,
"n_validas": 38,
"p25": 4.7,
"p75": 7.8
},
"nota_3": {
"desvio": 2.15,
"maxima": 9.9,
"media": 6.23,
"mediana": 6.05,
"minima": 3.2,
"n_ausentes": 5,
"n_fuera_de_rango": 1,
"n_total": 40,
"n_validas": 34,
"p25": 4.12,
"p75": 7.8
}
}ddof=0
Es el default de NumPy (a.std()) y es el que pide el contrato. pandas
usa ddof=1 por defecto: si comparás contra el .std() de una
Series, no van a coincidir, y no es un error de nadie.
Es exactamente la clase de detalle que un contrato tiene que fijar, porque las dos respuestas son legítimas.
sort_keys=True
Sin eso, dos soluciones correctas producen archivos con las claves en distinto orden y no hay nada que comparar.