Indice / Practicas / EJ-8.3
EJ-8.3 · Banda por hora y anomalias del edificio
Dos salidas que se calculan contra dos referencias distintas, a propósito. Elegir contra qué se compara cada punto decide qué se encuentra, y esta práctica está armada para que se note.
Qué hay que hacer
A partir de numpy_sensores.csv, producir dos archivos:
banda_por_hora.csv: una fila por hora del día, con la media, el desvío y los dos límites de la banda de dos desvíos de esa hora.lecturas_anomalas.csv: cada lectura cuya distancia a la normal de todo el edificio supera los dos desvíos, con su z.
Y además, aunque no se compare: el gráfico de la banda con las diez curvas por detrás y las anomalías marcadas en rojo, guardado como PNG.
ELI5Explicado facil
Diez termómetros en distintas salas de un edificio, midiendo cada hora durante un día.
- La banda contesta: a las tres de la tarde, ¿cuánto se diferencian las salas entre sí?
- Las anomalías contestan otra cosa: ¿qué medición está lejos de lo que es normal en este edificio, en cualquier momento?
Son dos preguntas distintas y por eso se comparan contra cosas distintas.
Archivos
- Entrada ·
numpy_sensores.csv - Objetivo ·
banda_por_hora.csv - Objetivo ·
lecturas_anomalas.csv - Herramienta ·
comparar.py
Por qué las dos referencias no son la misma
La banda se calcula hora por hora, sobre diez lecturas. El z de las anomalías se calcula sobre las 235 lecturas válidas juntas.
No es un capricho: con diez datos, el z más grande posible ronda 2,85, y en estos datos ninguno por hora llega a 2. Si las anomalías se midieran hora por hora, el archivo tendría tres filas en vez de dieciséis, y las tres serían casi empates. Medirlas contra la normal del edificio contesta la pregunta que interesa: qué sala funciona a otra temperatura.
Con ella adentro, el desvío global pasa de 2,6 a 63,79 y el corte de
dos desvíos se va a 152 grados. lecturas_anomalas.csv sale
vacío.
Y ahí está lo peor de este ejercicio: el script no falla, la banda se dibuja, el archivo se escribe. Un detector que no marca nada parece un sistema sano.
Cómo saber si te salió
python comparar.py mi_banda.csv banda_por_hora.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
| sensor_id | ubicacion | h00 | h01 | h02 | h03 | h04 | h05 | h06 | h07 | h08 | h09 | h10 | h11 | h12 | h13 | h14 | h15 | h16 | h17 | h18 | h19 | h20 | h21 | h22 | h23 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| S01 | deposito | 20.8 | 19.8 | 19.2 | 19.0 | 19.6 | 20.3 | 20.0 | 21.0 | 21.1 | 21.7 | 21.9 | 22.9 | 22.6 | 23.7 | 23.9 | 24.3 | 24.4 | 23.3 | 23.2 | 22.3 | 22.4 | 21.9 | 21.2 | 21.1 |
| S02 | sala_servidores | 18.5 | 17.1 | 16.6 | 16.1 | 16.6 | 17.0 | 18.3 | 18.4 | 18.9 | 19.9 | 19.7 | 21.2 | 21.7 | 21.9 | 22.1 | 23.3 | 22.6 | 22.0 | 21.6 | 20.2 | 20.3 | 19.5 | 19.2 | 18.2 |
| S03 | oficina | 17.8 | 16.6 | 16.6 | 15.5 | 16.1 | 16.5 | 17.4 | vacio | vacio | 18.5 | 19.6 | 19.6 | 20.3 | 20.3 | 21.4 | 20.7 | 20.9 | 20.4 | 20.4 | 19.1 | 19.5 | 19.0 | 18.3 | 17.9 |
| S04 | planta_baja | 21.2 | 20.8 | 20.0 | 19.9 | 19.6 | 20.1 | 20.8 | 21.0 | 21.0 | 21.9 | 21.6 | 22.8 | 22.8 | 22.9 | 23.1 | 23.9 | 23.0 | 23.2 | 22.5 | 21.9 | 21.8 | 21.5 | 21.0 | 20.9 |
| S05 | laboratorio | 19.9 | 18.7 | 18.9 | 18.2 | 18.5 | 18.9 | 18.8 | 19.5 | 19.8 | 19.8 | 20.2 | 999.9 | 20.8 | 20.5 | 20.4 | 20.8 | 21.2 | 21.0 | 20.0 | 20.7 | 20.2 | 20.3 | 19.4 | 19.2 |
El contrato de salida
banda_por_hora.csv · 24 filas
| Columna | Tipo | Que lleva |
|---|---|---|
hora | entero | 0 a 23, tomado del nombre de la columna |
lecturas | entero | Cuantas lecturas validas tiene esa hora |
media | decimal | Media de esa hora, con 2 decimales |
desvio | decimal | Desvio muestral de esa hora (ddof=1), con 2 decimales |
limite_bajo | decimal | media menos 2 desvios, con 2 decimales |
limite_alto | decimal | media mas 2 desvios, con 2 decimales |
lecturas_anomalas.csv · 16 filas
| Columna | Tipo | Que lleva |
|---|---|---|
sensor_id | texto | El sensor de la lectura |
ubicacion | texto | La ubicacion de ese sensor |
hora | entero | La hora de la lectura |
valor | decimal | La lectura, con 1 decimal, como viene en el archivo |
z | decimal | Distancia a la media global en desvios globales, con 2 decimales |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| lectura de 999.9 | 1 celda: sensor S05 a las 11 | anular anular el valor, no descartar el sensor. Ninguna sala de este edificio supera los 60 grados, asi que es una lectura rota del equipo y no una temperatura | no aparece en ninguna de las dos salidas; la hora 11 queda con 9 lecturas en vez de 10 |
| lecturas ausentes | 4 celdas: S03 a las 7 y a las 8, S06 a las 13 y S09 a las 21 | se se ignoran al calcular. La hora correspondiente queda con 9 lecturas | no aparecen en ninguna de las dos salidas |
| las dos salidas usan referencias distintas | en todo el ejercicio | la la banda usa la media y el desvio DE CADA HORA; el z de las anomalias usa la media y el desvio de TODAS las lecturas validas juntas | es la decision central del ejercicio y esta declarada a proposito: con solo diez sensores por hora, ningun z por hora llega a 2 y la segunda salida quedaria vacia |
| una anomalia no es un error | 14 de las 16 son de dos sensores | se se informan igual, sin corregir ni descartar nada | lecturas_anomalas.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- anular las lecturas mayores a 60
- calcular la media y el desvio globales sobre todas las lecturas validas
- calcular la media y el desvio de cada hora y armar banda_por_hora.csv
- calcular el z de cada lectura contra la referencia global y quedarse con las de |z| mayor a 2
- ordenar las dos salidas
La anulacion va primero y no es negociable: con el 999.9 adentro, el desvio global pasa de 2.6 a 63.79 y el corte de dos desvios se va a 152 grados, con lo cual lecturas_anomalas.csv sale vacio. Un detector que no marca nada parece un sistema sano.
Conteos clave
| sensores | 10 |
|---|---|
| celdas horarias | 240 |
| anuladas por imposibles | 1 |
| ausentes en el archivo | 4 |
| lecturas validas | 235 |
| banda_por_hora.csv | 24 |
| lecturas_anomalas.csv | 16 |
| por motivo |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(banda) == 24
list(banda['hora']) == list(range(24))
int(banda['lecturas'].sum()) == 235
(((banda['limite_alto'] - banda['limite_bajo']) - 4 * banda['desvio']).abs() < 0.05).all()
(banda['media'] > 14).all() and (banda['media'] < 30).all()
len(anomalas) == 16
not anomalas.duplicated(subset=['sensor_id', 'hora']).any()
(anomalas['z'].abs() > 2).all()
(anomalas['valor'] < 60).all()
PISTASSi te trabas, abri esto
- mask(tabla > 60) anula sin borrar la fila. Es distinto de filtrar: el sensor S05 sigue existiendo y sus otras 23 lecturas siguen contando.
- std() de pandas usa ddof=1 y std() de numpy usa ddof=0. Con diez datos la diferencia es del 5 %, suficiente para que un limite redondeado a dos decimales no coincida. El contrato pide el de pandas.
- stack() convierte la tabla ancha en una serie con todas las celdas, que es lo que hace falta para la media global. Ignora los NaN sin que haya que pedirselo.
- La media global sale de las 235 lecturas, no del promedio de las 24 medias horarias. Con distinta cantidad de lecturas por hora, los dos numeros no coinciden.
- Los limites se calculan con la media y el desvio SIN redondear, y recien despues se redondea todo a dos decimales. Por eso limite_alto menos limite_bajo no da exactamente cuatro veces el desvio publicado: la diferencia es de centesimos y esta prevista en la invariante.
Así tienen que quedar
| hora | lecturas | media | desvio | limite_bajo | limite_alto |
|---|---|---|---|---|---|
| 0 | 10 | 19.8 | 2.05 | 15.71 | 23.89 |
| 1 | 10 | 18.83 | 2.26 | 14.31 | 23.35 |
| 2 | 10 | 18.29 | 2.11 | 14.07 | 22.51 |
| 3 | 10 | 17.93 | 2.06 | 13.81 | 22.05 |
| 4 | 10 | 18.27 | 1.97 | 14.33 | 22.21 |
| sensor_id | ubicacion | hora | valor | z |
|---|---|---|---|---|
| S03 | oficina | 3 | 15.5 | -2.03 |
| S07 | sala_servidores | 12 | 26.2 | 2.09 |
| S07 | sala_servidores | 13 | 26.7 | 2.29 |
| S07 | sala_servidores | 14 | 27.6 | 2.63 |
| S07 | sala_servidores | 15 | 27.8 | 2.71 |
tabla.mask(tabla > 60) pone NaN en esa celda y deja la
fila donde estaba. El sensor S05 sigue existiendo y sus otras 23 lecturas siguen
contando en las otras 23 horas.
Descartar el sensor entero por una lectura rota costaría 23 mediciones buenas. Es la regla de la unidad 5: antes de rechazar una fila, preguntarse si alcanza con anular el valor.
Ocho de las dieciséis son del sensor de la sala de servidores y seis del laboratorio. Eso no son catorce incidentes para investigar: es que esas dos salas funcionan a otra temperatura, siempre.
Cuando las anomalías se concentran en un grupo, el modelo de «normal» está mal armado, no pasaron muchas cosas. La salida honesta es calcular el umbral dentro de cada sala, o tratarlas aparte, y decirlo. Este contrato pide la versión global justamente para que el resultado obligue a esa conversación.