Saltar al contenido

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:

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

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.

La lectura de 999.9 hay que anularla antes de calcular nada

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

numpy_sensores.csv (entrada) · 10 filas, se muestran 5
sensor_idubicacionh00h01h02h03h04h05h06h07h08h09h10h11h12h13h14h15h16h17h18h19h20h21h22h23
S01deposito20.819.819.219.019.620.320.021.021.121.721.922.922.623.723.924.324.423.323.222.322.421.921.221.1
S02sala_servidores18.517.116.616.116.617.018.318.418.919.919.721.221.721.922.123.322.622.021.620.220.319.519.218.2
S03oficina17.816.616.615.516.116.517.4vaciovacio18.519.619.620.320.321.420.720.920.420.419.119.519.018.317.9
S04planta_baja21.220.820.019.919.620.120.821.021.021.921.622.822.822.923.123.923.023.222.521.921.821.521.020.9
S05laboratorio19.918.718.918.218.518.918.819.519.819.820.2999.920.820.520.420.821.221.020.020.720.220.319.419.2

El contrato de salida

banda_por_hora.csv · 24 filas

ColumnaTipoQue lleva
horaentero0 a 23, tomado del nombre de la columna
lecturasenteroCuantas lecturas validas tiene esa hora
mediadecimalMedia de esa hora, con 2 decimales
desviodecimalDesvio muestral de esa hora (ddof=1), con 2 decimales
limite_bajodecimalmedia menos 2 desvios, con 2 decimales
limite_altodecimalmedia mas 2 desvios, con 2 decimales

Orden de las filas: hora ascendente. Es unico. Indice: no se escribe: to_csv(index=False)

lecturas_anomalas.csv · 16 filas

ColumnaTipoQue lleva
sensor_idtextoEl sensor de la lectura
ubicaciontextoLa ubicacion de ese sensor
horaenteroLa hora de la lectura
valordecimalLa lectura, con 1 decimal, como viene en el archivo
zdecimalDistancia a la media global en desvios globales, con 2 decimales

Orden de las filas: sensor_id ascendente, y dentro de cada sensor hora ascendente. El par (sensor_id, hora) es unico. Indice: no se escribe: to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
lectura de 999.91 celda: sensor S05 a las 11anular 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 temperaturano aparece en ninguna de las dos salidas; la hora 11 queda con 9 lecturas en vez de 10
lecturas ausentes4 celdas: S03 a las 7 y a las 8, S06 a las 13 y S09 a las 21se se ignoran al calcular. La hora correspondiente queda con 9 lecturasno aparecen en ninguna de las dos salidas
las dos salidas usan referencias distintasen todo el ejerciciola 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 juntases 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 error14 de las 16 son de dos sensoresse se informan igual, sin corregir ni descartar nadalecturas_anomalas.csv
Orden de comprobacion

Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:

  1. anular las lecturas mayores a 60
  2. calcular la media y el desvio globales sobre todas las lecturas validas
  3. calcular la media y el desvio de cada hora y armar banda_por_hora.csv
  4. calcular el z de cada lectura contra la referencia global y quedarse con las de |z| mayor a 2
  5. 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

Si estos numeros no te dan, algo se desvio antes de que llegues a comparar los archivos.

sensores10
celdas horarias240
anuladas por imposibles1
ausentes en el archivo4
lecturas validas235
banda_por_hora.csv24
lecturas_anomalas.csv16
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

banda_por_hora.csv (objetivo) · 24 filas, se muestran 5
horalecturasmediadesviolimite_bajolimite_alto
01019.82.0515.7123.89
11018.832.2614.3123.35
21018.292.1114.0722.51
31017.932.0613.8122.05
41018.271.9714.3322.21
lecturas_anomalas.csv (objetivo) · 16 filas, se muestran 5
sensor_idubicacionhoravalorz
S03oficina315.5-2.03
S07sala_servidores1226.22.09
S07sala_servidores1326.72.29
S07sala_servidores1427.62.63
S07sala_servidores1527.82.71
Anular no es descartar

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.

Lo que el archivo de anomalías dice y lo que no dice

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.