Indice / Practicas / EJ-3.3
EJ-3.3 · Asistencia con denominador variable
Treinta alumnos, doce sesiones, y un porcentaje que parece una división. La parte difícil no es el numerador: es darse cuenta de que el denominador no es 12 para todos.
Qué hay que hacer
A partir de asistencia_sesiones.csv (30 filas × 12 sesiones), producir
asistencia_porcentaje.csv con una fila por alumno: cuántas sesiones tiene
registradas, cuántas presencias, cuántas ausencias, cuántas celdas sin registro, y el
porcentaje.
Archivos
- Entrada ·
asistencia_sesiones.csv - Objetivo ·
asistencia_porcentaje.csv - Herramienta ·
comparar.py
ELI5Explicado facil
Si un alumno faltó a una clase, eso cuenta en su contra. Si el profesor no tomó asistencia ese día, no.
Las dos situaciones se ven casi iguales en la planilla - una celda que no dice «P» - y son completamente distintas. Contar la segunda como falta es inventar un dato en contra de alguien.
Las cinco celdas que no dicen ni P ni A
| Alumno | Sesión | Dice | Qué es | Qué se hace |
|---|---|---|---|---|
| L2717 | 08 | p | Una presencia, en minúscula | Normalizar: cuenta como presencia |
| L2724 | 03 | PRESENTE | Una presencia, escrita entera | Normalizar: cuenta como presencia |
| L2703 | 05 | (vacío) | No se tomó asistencia | Sin registro: sale del numerador y del denominador |
| L2728 | 10 | - | Ídem | Sin registro |
| L2711 | 01 | X | Un estado que nadie sabe interpretar | Sin registro: no se puede adivinar |
Dos se recuperan y tres no. Y esa distinción - entre escrito distinto y no escrito - es todo el ejercicio.
Lo que cambia el denominador
| Alumno | Presencias | Dividiendo por 12 | Dividiendo por lo registrado |
|---|---|---|---|
| L2703 | 10 | 83,3 % | 90,9 % (sobre 11) |
| L2711 | 9 | 75,0 % | 81,8 % (sobre 11) |
| L2728 | 4 | 33,3 % | 36,4 % (sobre 11) |
Hasta 7,6 puntos de diferencia, sobre un umbral de aprobación que suele estar en el 75 %. A L2703 y a L2711 los cruza de lado.
La trampa del orden
L2703 y L2728 se llaman los dos Fabián Bravo
No es un duplicado que haya que resolver: son dos legajos distintos, y por lo tanto
dos alumnos. Lo único que obliga es a ordenar por legajo, porque
estudiante no es único y no daría un orden total.
Es la lección 2.2 apareciendo donde no se la esperaba.
El invariante
assert (salida["presencias"] + salida["ausencias"]
== salida["sesiones_registradas"]).all()
assert (salida["sesiones_registradas"] + salida["sin_registro"] == 12).all()
Los dos tienen que valer en las 30 filas. Es la versión por fila del invariante de la lección 1.6: si no cierra, hay una celda que no fue clasificada.
Cómo saber si te salió
python comparar.py mi_asistencia.csv asistencia_porcentaje.csv
Antes de arrancar, mirá los datos
| legajo | estudiante | sesion_01 | sesion_02 | sesion_03 | sesion_04 | sesion_05 | sesion_06 | sesion_07 | sesion_08 | sesion_09 | sesion_10 | sesion_11 | sesion_12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| L2701 | Rocío Duarte | P | P | A | P | P | A | P | A | A | P | A | A |
| L2702 | Yamila Escobar | P | P | P | P | A | A | P | P | P | P | P | P |
| L2703 | Fabián Bravo | A | P | P | P | vacio | P | P | P | P | P | P | P |
| L2704 | Pablo Luna | A | P | P | P | P | P | P | P | P | P | A | A |
| L2705 | Valeria Vidal | P | P | P | P | P | P | P | A | P | P | P | P |
El contrato de salida
asistencia_porcentaje.csv · 30 filas
| Columna | Tipo | Que lleva |
|---|---|---|
legajo | texto | El legajo tal cual vino |
estudiante | texto | El nombre tal cual vino. OJO: se repite, no sirve como clave |
sesiones_registradas | entero | Presencias mas ausencias: el DENOMINADOR del porcentaje |
presencias | entero | Cuantas veces asistio |
ausencias | entero | Cuantas veces falto, con registro |
sin_registro | entero | Cuantas celdas no son un estado utilizable |
porcentaje | decimal | 100 * presencias / sesiones_registradas, 1 decimal |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| sinonimos de presente escritos distinto | 2 celdas: 'p' en L2717 sesion_08 y 'PRESENTE' en L2724 sesion_03 | NORMALIZAR: NORMALIZAR: se pasa a mayusculas y se aceptan P, PRESENTE y PRES como presencia. Son registros validos escritos con otra convencion | asistencia_porcentaje.csv, cuentan como presencia |
| celda vacia | 1 celda: L2703 sesion_05 | NO NO es una ausencia: es una sesion sin registro. Sale del numerador Y del denominador | asistencia_porcentaje.csv, columna sin_registro |
| el token '-' | 1 celda: L2728 sesion_10 | igual igual que la celda vacia: sin registro | asistencia_porcentaje.csv, columna sin_registro |
| un estado fuera del dominio | 1 celda: 'X' en L2711 sesion_01 | sin sin registro. No se puede adivinar si esa X queria decir presente o ausente | asistencia_porcentaje.csv, columna sin_registro |
| dos alumnos con el mismo nombre | L2703 y L2728 se llaman los dos Fabian Bravo | no no se corrige ni se fusiona: son dos legajos distintos y por lo tanto dos alumnos. Solo obliga a ordenar por legajo | asistencia_porcentaje.csv, dos filas |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- normalizar cada celda a mayusculas y sin espacios
- clasificar en presente, ausente, o sin registro
- contar los tres por alumno
- el porcentaje se calcula sobre presencias + ausencias, NO sobre 12
La normalizacion va primero, o la 'p' minuscula y el 'PRESENTE' se pierden como sin registro y dos alumnos quedan con un punto menos. Y el denominador sale del conteo, no de la cantidad de columnas: es la unica decision que cambia tres filas del resultado.
Conteos clave
| filas de entrada | 30 |
|---|---|
| celdas de asistencia | 360 |
| asistencia_porcentaje.csv | 30 |
| alumnos con menos de 12 sesiones registradas | 3 |
| celdas sin registro | 3 |
| celdas normalizadas | 2 |
| por motivo | ausente: 2, dominio_desconocido: 1 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(asistencia) == 30
asistencia['legajo'].is_unique
asistencia['legajo'].is_monotonic_increasing
not asistencia.isna().any().any()
(asistencia['presencias'] + asistencia['ausencias'] == asistencia['sesiones_registradas']).all()
(asistencia['sesiones_registradas'] + asistencia['sin_registro'] == 12).all()
asistencia['sin_registro'].sum() == 3
(asistencia['sesiones_registradas'] < 12).sum() == 3
asistencia['porcentaje'].between(0, 100).all()
asistencia['presencias'].sum() == 285
not asistencia['estudiante'].is_unique
PISTASSi te trabas, abri esto
- El porcentaje se divide por sesiones_registradas, no por 12. Son tres alumnos los que cambian, y para uno de ellos la diferencia es de 7,6 puntos.
- Normalizar con .strip().upper() ANTES de comparar recupera la 'p' y el 'PRESENTE'. Sin eso, dos alumnos pierden una presencia cada uno.
- Una 'X' no es una ausencia: es un estado que nadie sabe interpretar. Contarla como falta seria inventar un dato en contra del alumno.
- El invariante sesiones_registradas + sin_registro == 12 tiene que valer en las 30 filas. Es la version por fila del invariante de la leccion 1.6.
Así tiene que quedar
| legajo | estudiante | sesiones_registradas | presencias | ausencias | sin_registro | porcentaje |
|---|---|---|---|---|---|---|
| L2701 | Rocío Duarte | 12 | 6 | 6 | 0 | 50.0 |
| L2702 | Yamila Escobar | 12 | 10 | 2 | 0 | 83.3 |
| L2703 | Fabián Bravo | 11 | 10 | 1 | 1 | 90.9 |
| L2704 | Pablo Luna | 12 | 9 | 3 | 0 | 75.0 |
| L2705 | Valeria Vidal | 12 | 11 | 1 | 0 | 91.7 |
Normalizar (.strip().upper()) va antes de clasificar. Si
se clasifica primero, la p minúscula y el PRESENTE caen en
«sin registro» y dos alumnos más pierden una presencia cada uno - sin que nada
avise.