Indice / Practicas / EJ-8.2
EJ-8.2 · Distribucion de notas como tabla de conteos
Un histograma es una tabla de conteos dibujada. Acá se pide la tabla, porque la tabla es lo que se puede comparar, citar y discutir. El dibujo sale solo después.
Qué hay que hacer
A partir de estudiantes_notas.csv, producir dos archivos:
distribucion_notas.csv: para cada una de las tres notas, los diez tramos de un punto entre 0 y 10, con cuántos alumnos caen en cada uno.notas_descartadas.csv: cada valor que no se pudo contar, con el valor tal cual venía y el motivo.
Y además, aunque no se compare: el histograma de las tres notas, con los mismos bordes en los tres, guardado como PNG.
ELI5Explicado facil
Es agrupar a los alumnos por nota: los que sacaron entre 3 y 4 en un montón, entre 4 y 5 en otro, y así hasta el 10. Después se cuenta cuántos hay en cada montón.
Lo que hay que decidir es qué hacer con los que no tienen nota, y con uno que tiene un 99. Ninguno de los dos entra en un montón, y ninguno de los dos se puede tirar sin dejar constancia.
Archivos
- Entrada ·
estudiantes_notas.csv - Objetivo ·
distribucion_notas.csv - Objetivo ·
notas_descartadas.csv - Herramienta ·
comparar.py
Los tramos vacíos van igual
Nadie sacó menos de 3 en ninguna de las tres notas, así que los tramos de 0 a 3 tienen cero alumnos. Esas filas se escriben igual, con un 0. Una tabla de histograma a la que le faltan tramos no se puede dibujar sin adivinar cuáles faltan, y dos personas correctas producirían archivos distintos.
El archivo tiene 40 filas por 3 notas, o sea 120 celdas. Al terminar:
la suma de la columna alumnos más la cantidad de filas descartadas tiene
que dar exactamente 120.
Si da menos, hay observaciones desapareciendo en silencio, que es la forma en que falla casi siempre un histograma. Si da más, estás contando alguna dos veces.
Cómo saber si te salió
python comparar.py mi_distribucion.csv distribucion_notas.csv
Si no dice nada, tu archivo es idéntico al esperado. Si difiere, te dice qué fila y qué columna.
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
distribucion_notas.csv · 30 filas
| Columna | Tipo | Que lleva |
|---|---|---|
nota | texto | nota_1, nota_2 o nota_3 |
desde | entero | Extremo inferior del tramo, incluido |
hasta | entero | Extremo superior del tramo, excluido salvo en el ultimo tramo |
alumnos | entero | Cuantas notas validas caen en ese tramo |
notas_descartadas.csv · 9 filas
| Columna | Tipo | Que lleva |
|---|---|---|
legajo | texto | El legajo del estudiante |
nota | texto | Cual de las tres columnas |
valor | texto | El valor tal cual viene en el archivo; para un campo vacio, la palabra vacio |
motivo | texto | Uno del vocabulario cerrado de motivos |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| los diez tramos siempre estan, aunque no tengan a nadie | nota_2 no tiene a nadie entre 0 y 3, por ejemplo | escribir escribir la fila con alumnos = 0. Una tabla de histograma con tramos faltantes no se puede dibujar sin adivinar cuales faltan | distribucion_notas.csv |
| tokens de ausente en nota_3 | 3 celdas: una vacia, una con N/A y una con NaN | descartar descartar ese valor, no la fila: las otras dos notas del mismo estudiante siguen contando | notas_descartadas.csv con motivo ausente; el valor se informa tal cual vino, y la celda vacia se escribe 'vacio' para que no quede un campo vacio en el objetivo |
| el token '-' en nota_3 | 2 celdas | descartar descartar ese valor. El guion es uno de los cuatro tokens de ausente del corpus, junto con NaN, N/A y null | notas_descartadas.csv con motivo ausente y valor '-' |
| notas mayores que 10 | 3 celdas: 12.5 y 10.5 en nota_1, y 99.0 en nota_2 | descartar descartar el valor. El dominio de una nota de este curso es de 0 a 10 inclusive | notas_descartadas.csv con motivo fuera_de_rango |
| nota negativa | 1 celda: -1.0 en nota_3 | descartar descartar el valor, por el mismo dominio | notas_descartadas.csv con motivo fuera_de_rango |
| el 10.0 exacto | no aparece en estos datos y hay que decidirlo igual | un un 10 va en el ultimo tramo, el de 9 a 10, que es el unico con los dos extremos incluidos | distribucion_notas.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- convertir las tres columnas a numero con errors='coerce'
- anotar los descartes por ausente y por fuera de rango, con el valor original
- contar por tramo lo que quedo, con los diez tramos siempre presentes
- ordenar las dos salidas
El valor original hay que leerlo antes de convertir: despues de to_numeric, el guion y el campo vacio son los dos NaN y ya no se distinguen. Por eso el informe de descartes se arma sobre el texto crudo.
Conteos clave
| filas de entrada | 40 |
|---|---|
| celdas de nota en el archivo | 120 |
| distribucion_notas.csv | 30 |
| notas_descartadas.csv | 9 |
| notas validas contadas | 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(distribucion) == 30
distribucion.groupby('nota').size().eq(10).all()
int(distribucion['alumnos'].sum()) == 111
(distribucion['hasta'] - distribucion['desde']).eq(1).all()
len(descartadas) == 9
not descartadas.duplicated(subset=['legajo', 'nota']).any()
set(descartadas['motivo']) <= {'ausente', 'fuera_de_rango'}
int(distribucion['alumnos'].sum()) + len(descartadas) == 120
PISTASSi te trabas, abri esto
- La ultima invariante es la comprobacion que hay que hacer siempre con un histograma: contadas mas descartadas tiene que dar el total de celdas. Si no da, hay observaciones desapareciendo en silencio.
- pd.cut con right=False da tramos [desde, hasta) y deja el 10 afuera. El ultimo tramo necesita un tratamiento aparte, o bins con include_lowest y right=True y una decision explicita sobre el 0.
- Contar a mano con una comparacion doble tambien vale y es mas facil de defender: (v >= desde) & (v < hasta), y el ultimo con <=.
- El motivo de un guion es ausente, no no_numerico. Que no sea un numero es la consecuencia; que no haya dato es el hecho.
Así tienen que quedar
| nota | desde | hasta | alumnos |
|---|---|---|---|
| nota_1 | 0 | 1 | 0 |
| nota_1 | 1 | 2 | 0 |
| nota_1 | 2 | 3 | 0 |
| nota_1 | 3 | 4 | 5 |
| nota_1 | 4 | 5 | 6 |
| legajo | nota | valor | motivo |
|---|---|---|---|
| L2606 | nota_3 | vacio | ausente |
| L2609 | nota_1 | 12.5 | fuera_de_rango |
| L2613 | nota_3 | N/A | ausente |
| L2618 | nota_3 | -1.0 | fuera_de_rango |
| L2621 | nota_3 | - | ausente |
Después de pd.to_numeric(..., errors="coerce"), un guion, un
N/A y un campo vacío son los tres NaN: ya no se distinguen. El
informe de descartes tiene que decir qué había escrito, así que se arma sobre el texto
crudo, leyendo con dtype=str, keep_default_na=False.
Es la misma idea de la práctica EJ-5.1: hay preguntas que solo se pueden contestar antes de convertir.
Los tramos son [desde, hasta): el extremo de abajo entra y el de arriba
no. Con esa regla, un 10 no cae en ningún tramo, porque el último es
[9, 10).
El contrato lo resuelve haciendo que el último incluya los dos extremos. No es la única salida posible (también se podría poner un tramo de 10 a 11), y es la que produce diez tramos parejos. Lo importante es que en estos datos no hay ningún 10, así que la decisión no cambia una sola celda del resultado, y hay que tomarla igual: el día que aparezca un 10 no va a haber nadie mirando.