IoT e IA

Detección de outliers en datos de sensores con Z-score, IQR y EWMA

Tres métodos sencillos y explicables para encontrar outliers en flujos de sensores, con implementaciones en Python que puedes ejecutar sobre una fuente MQTT de temperatura en directo y orientación sobre cuándo falla cada uno.

Gráfica de detección de outliers en datos de sensores que compara bandas de Z-score, IQR y EWMA en un flujo de temperatura con los valores atípicos marcados
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

La detección de outliers en datos de sensores no necesita machine learning para ser útil. Tres técnicas clásicas, el Z-score, el rango intercuartílico (IQR) y la media móvil ponderada exponencialmente (EWMA), detectan la mayoría de los picos, caídas y derivas en flujos de temperatura. Este artículo explica cómo funciona cada una, dónde falla, y te da código Python que aplica las tres a una fuente MQTT en directo.

Outliers frente a anomalías en flujos de sensores

Un outlier es una lectura que se aleja mucho de las demás. Una anomalía es una lectura o un patrón que indica que está ocurriendo algo inusual en el mundo real. Ambos conceptos se solapan, pero no son idénticos. Una única lectura disparatada por ruido eléctrico es un outlier y no una anomalía de proceso; una subida lenta de dos grados en una hora puede no producir nunca un outlier y, sin embargo, señalar un ventilador de refrigeración que falla.

Por tanto, la detección de outliers en datos de sensores tiene dos cometidos:

  1. Limpieza. Eliminar o marcar picos y valores imposibles antes de que lleguen a dashboards, promedios y modelos.
  2. Alerta temprana. Destacar lecturas que merecen atención porque rompen el patrón reciente.

Los métodos siguientes cubren ambos, con distintas fortalezas. Para la arquitectura general, incluidos los detectores de machine learning, consulta métodos de detección de anomalías IoT.

Z-score: distancia en desviaciones típicas

El Z-score mide a cuántas desviaciones típicas está un valor de la media:

z = (x - mean) / std

Una regla habitual considera outlier una lectura con un Z-score absoluto superior a 3. En un contexto de streaming, calculas la media y la desviación típica sobre una ventana móvil de lecturas recientes en lugar de sobre todo el historial, para que la línea base siga los cambios lentos.

Fortalezas: fácil de explicar, rápido y funciona bien cuando la señal es aproximadamente normal en torno a un nivel estable.

Debilidades: la media y la desviación típica se ven distorsionadas a su vez por los outliers. Una ráfaga de picos infla la desviación típica y oculta el siguiente pico. Además, supone un ruido simétrico, que muchos sensores no tienen.

Una variante robusta sustituye la media por la mediana y la desviación típica por la desviación absoluta mediana (MAD). Esa versión es la que se usa en el tutorial de detección de anomalías en tiempo real con Python.

IQR: límites basados en cuartiles

El rango intercuartílico usa percentiles en lugar de promedios:

Q1 = 25th percentile, Q3 = 75th percentile, IQR = Q3 - Q1
lower fence = Q1 - 1.5 * IQR
upper fence = Q3 + 1.5 * IQR

Todo lo que quede fuera de los límites es un outlier. Usa 3 en lugar de 1,5 para outliers "extremos".

Fortalezas: robusto frente a los outliers que busca, no supone normalidad y gestiona datos asimétricos como las amplitudes de vibración o el consumo de corriente.

Debilidades: necesita una ventana lo bastante grande para que los cuartiles sean estables, y en sensores muy cuantizados (por ejemplo, un sensor de temperatura con resolución de 0,5 grados) el IQR puede reducirse a cero y marcar cualquier cambio pequeño. Añade una anchura mínima de los límites igual a la resolución del sensor.

EWMA: línea base suavizada con límites de control

La EWMA mantiene un valor suavizado acumulado en el que las lecturas recientes pesan más:

ewma = alpha * x + (1 - alpha) * ewma_previous

Junto a ella sigues una varianza ponderada exponencialmente y marcas las lecturas que quedan fuera de ewma ± k * ewm_std. Un alpha pequeño (de 0,01 a 0,05) da una línea base lenta y estable; un alpha mayor sigue los cambios con rapidez.

Fortalezas: memoria constante (dos números por señal), ideal para microcontroladores, se adapta de forma natural a la deriva lenta y, usada como gráfico de control, es sensible a desplazamientos pequeños y sostenidos.

Debilidades: como se adapta, un fallo lento puede quedar absorbido en la línea base. Combínala con un límite absoluto para los valores que nunca deben superarse.

Comparación de los métodos

Método Memoria por señal Robusto frente a outliers Gestiona asimetría Detecta deriva lenta Cabe en un microcontrolador
Z-score móvil Ventana de N valores No No Mal Sí, con N pequeño
Z robusto (mediana/MAD) Ventana de N valores Sí En parte Mal Sí, con N pequeño
Límites IQR Ventana de N valores Sí Sí Mal Sí, con N pequeño
Límites de control EWMA Dos números En parte No Sí, con alpha pequeño Fácilmente

Ningún método gana por sí solo. Una combinación práctica para la temperatura es: límites físicos estrictos, IQR para los picos y EWMA con un alpha pequeño para la deriva.

Detección de outliers en datos de sensores con Python

El siguiente código implementa los tres detectores como pequeñas clases con un método update, para que puedas ejecutarlos en paralelo sobre el mismo flujo.

# detectors.py
import math
import statistics
from collections import deque


class RollingZ:
    def __init__(self, window=60, k=3.0):
        self.buf, self.k = deque(maxlen=window), k

    def update(self, x):
        flagged = False
        if len(self.buf) >= 20:
            mean = statistics.fmean(self.buf)
            std = statistics.pstdev(self.buf) or 1e-9
            flagged = abs(x - mean) / std > self.k
        if not flagged:
            self.buf.append(x)
        return flagged


class IQRFence:
    def __init__(self, window=60, k=1.5, min_width=0.2):
        self.buf, self.k, self.min_width = deque(maxlen=window), k, min_width

    def update(self, x):
        flagged = False
        if len(self.buf) >= 20:
            q1, _, q3 = statistics.quantiles(self.buf, n=4)
            iqr = max(q3 - q1, self.min_width)
            flagged = x < q1 - self.k * iqr or x > q3 + self.k * iqr
        if not flagged:
            self.buf.append(x)
        return flagged


class EWMA:
    def __init__(self, alpha=0.05, k=4.0, warmup=20):
        self.alpha, self.k, self.warmup = alpha, k, warmup
        self.mean, self.var, self.n = None, 0.0, 0

    def update(self, x):
        self.n += 1
        if self.mean is None:
            self.mean = x
            return False
        diff = x - self.mean
        std = math.sqrt(self.var) or 1e-9
        flagged = self.n > self.warmup and abs(diff) > self.k * std
        if not flagged:
            self.mean += self.alpha * diff
            self.var = (1 - self.alpha) * (self.var + self.alpha * diff * diff)
        return flagged

Cada detector deja de aprender de los valores que marca, lo que evita que un pico contamine la línea base.

Aplicarlo a un flujo MQTT de temperatura en directo

Para probarlo de principio a fin, publica temperaturas simuladas en el servidor MQTT público gratuito en broker.connectmq.com y ejecuta los detectores en un suscriptor. Sustituye t9x4q2 por tu propio ID aleatorio; el área pública es legible por cualquiera, así que usa solo datos sintéticos.

# temp_sim.py
import json, random, time, uuid
import paho.mqtt.client as mqtt

TOPIC = "connectmq/demo/t9x4q2/lab/room1/temperature"
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tsim-{uuid.uuid4().hex[:6]}")
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_start()
base = 21.0
while True:
    base += random.gauss(0, 0.01)                 # deambular lento
    value = base + random.gauss(0, 0.15)
    if random.random() < 0.02:
        value += random.choice([-6, 7])           # pico ocasional
    c.publish(TOPIC, json.dumps({"tempC": round(value, 2), "ts": time.time()}))
    time.sleep(1)
# temp_outliers.py
import json, uuid
import paho.mqtt.client as mqtt
from detectors import RollingZ, IQRFence, EWMA

TOPIC = "connectmq/demo/t9x4q2/lab/room1/temperature"
detectors = {"z": RollingZ(), "iqr": IQRFence(), "ewma": EWMA()}


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(TOPIC)


def on_message(client, userdata, msg):
    x = json.loads(msg.payload)["tempC"]
    flags = {name: d.update(x) for name, d in detectors.items()}
    if any(flags.values()):
        print(f"{x:6.2f}  flagged by: {[n for n, f in flags.items() if f]}")
        client.publish(TOPIC.rsplit("/", 1)[0] + "/outliers",
                       json.dumps({"tempC": x, "flags": flags}), qos=1)


c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tout-{uuid.uuid4().hex[:6]}")
c.on_connect = on_connect
c.on_message = on_message
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_forever()

Ejecuta ambos y observa qué detectores coinciden. Normalmente verás que los tres detectan los picos grandes, mientras que el Z-score a veces pasa por alto un pico que llega justo después de otro, que es exactamente la debilidad descrita antes.

Detectar en el dispositivo o después del broker

Puedes ejecutar estos detectores en dos lugares, y la elección afecta a lo que publicas.

Ejecutarlos en el nodo sensor, por ejemplo un ESP32, significa que el dispositivo puede añadir un indicador de outlier a cada mensaje o publicar solo cuando ocurre algo interesante. Eso ahorra ancho de banda y funciona incluso cuando la red está caída, pero los umbrales quedan grabados en el firmware y son más difíciles de cambiar.

Ejecutarlos después del broker, en un suscriptor como el anterior, mantiene sencillos los dispositivos y te permite cambiar métodos y umbrales en cualquier momento, comparar varios detectores en paralelo y reprocesar el historial. El coste es que cada lectura en bruto debe viajar hasta el broker.

Un término medio habitual es un límite estricto y una comprobación de valores bloqueados en el dispositivo, que detecta de inmediato los sensores averiados, y los detectores estadísticos en un servicio suscriptor donde son fáciles de ajustar. Con un broker de ConnectMQ en medio, ambas salidas llegan a topics que pueden leer los mismos dashboards.

Lista de comprobación práctica

  1. Aplica primero límites físicos estrictos (un sensor de sala que marca 150 grados es una avería, no un outlier).
  2. Detecta los valores bloqueados por separado comprobando si hay lecturas idénticas durante un periodo.
  3. Elige el tamaño de las ventanas en tiempo, no en muestras, si los intervalos de publicación varían.
  4. Fija anchuras mínimas de banda iguales a la resolución del sensor.
  5. Marca los outliers en lugar de eliminarlos y conserva el valor en bruto para análisis posteriores.
  6. Exige dos o tres marcas consecutivas antes de avisar a una persona.
  7. Revisa los umbrales tras una semana de datos reales.

Cuando las señales interactúan, por ejemplo la corriente y el caudal de una bomba, estos métodos por señal llegan a su límite y merece la pena un modelo multivariante como Isolation Forest con datos de sensores.

Dónde encaja el broker

Cada pieza anterior es un cliente MQTT corriente: el sensor publica y el detector se suscribe y vuelve a publicar. El broker MQTT simplemente enruta los mensajes, lo que significa que puedes añadir, quitar o sustituir detectores sin tocar los dispositivos. Para despliegues reales, un broker MQTT en la nube privado como ConnectMQ, que funciona como broker MQTT gestionado, añade TLS, credenciales y un explorador de topics en directo donde puedes ver las lecturas en bruto y los outliers marcados uno al lado del otro. Para conocer más formas de combinar la analítica con los dispositivos, consulta la guía de IA e IoT.

Empieza con ConnectMQ

Crea un broker en el plan gratuito Serverless en app.connectmq.com sin tarjeta de crédito, conecta tus sensores a él y ejecuta el detector de outliers sobre tus propios datos en lugar del simulador.

Preguntas frecuentes

¿Qué método de detección de outliers es mejor para sensores de temperatura?

Lo que mejor funciona es una combinación: límites físicos, límites IQR para los picos y EWMA con un factor de suavizado pequeño para la deriva lenta. La temperatura cambia despacio, así que la EWMA resulta especialmente eficaz.

¿Qué tamaño de ventana debo usar para las estadísticas móviles?

Abarca el tiempo suficiente para captar el ruido normal, pero no tanto que los cambios legítimos tarden una eternidad en absorberse. Con una lectura por segundo, de uno a cinco minutos es un punto de partida razonable.

¿Hay que eliminar los outliers de los datos?

Márcalos en lugar de borrarlos. Conserva el valor en bruto con un indicador de outlier para poder auditar las decisiones y reentrenar modelos más adelante.

¿Pueden ejecutarse estos métodos en un ESP32?

Sí. La EWMA solo necesita dos floats por señal, y una ventana móvil de 30 a 60 valores cabe fácilmente en la memoria de un ESP32, así que puedes marcar los outliers antes de publicar.

Artículos relacionados

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.