IoT e IA

Detecção de Outliers em Dados de Sensores com Z-Score, IQR e EWMA

Três métodos simples e explicáveis para encontrar outliers em fluxos de sensores, com implementações em Python que você pode rodar sobre um feed MQTT de temperatura ao vivo e orientações sobre quando cada um falha.

Gráfico de detecção de outliers em dados de sensores comparando as faixas de Z-score, IQR e EWMA em um fluxo de temperatura com outliers sinalizados
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

A detecção de outliers em dados de sensores não precisa de machine learning para ser útil. Três técnicas clássicas, o Z-score, o intervalo interquartil (IQR) e a média móvel exponencialmente ponderada (EWMA), detectam a maior parte dos picos, quedas e derivas em fluxos de temperatura. Este artigo explica como cada uma funciona, onde ela falha e traz código Python que aplica as três a um feed MQTT ao vivo.

Outliers vs anomalias em fluxos de sensores

Um outlier é uma leitura que fica longe das demais. Uma anomalia é uma leitura ou um padrão que indica que algo incomum está acontecendo no mundo real. Os dois se sobrepõem, mas não são idênticos. Uma única leitura absurda causada por ruído elétrico é um outlier, e não uma anomalia de processo; uma subida lenta de dois graus ao longo de uma hora pode nunca gerar um outlier e ainda assim indicar um ventilador de resfriamento com defeito.

Por isso, a detecção de outliers em dados de sensores tem duas funções:

  1. Limpeza. Remover ou sinalizar picos e valores impossíveis antes que cheguem a dashboards, médias e modelos.
  2. Alerta antecipado. Destacar leituras que merecem atenção porque quebram o padrão recente.

Os métodos abaixo lidam com as duas, com pontos fortes diferentes. Para a arquitetura mais ampla, incluindo detectores de machine learning, veja métodos de detecção de anomalias em IoT.

Z-Score: distância em desvios-padrão

O Z-score mede quantos desvios-padrão um valor está distante da média:

z = (x - mean) / std

Uma leitura com Z-score absoluto acima de 3 é uma regra comum para outliers. Em um contexto de streaming, você calcula a média e o desvio-padrão sobre uma janela móvel de leituras recentes em vez de todo o histórico, para que a linha de base acompanhe mudanças lentas.

Pontos fortes: fácil de explicar, rápido e funciona bem quando o sinal é aproximadamente normal em torno de um nível estável.

Pontos fracos: a média e o desvio-padrão são, eles próprios, distorcidos pelos outliers. Uma rajada de picos infla o desvio-padrão e esconde o próximo pico. O método também pressupõe ruído simétrico, o que muitos sensores não têm.

Uma variante robusta substitui a média pela mediana e o desvio-padrão pelo desvio absoluto mediano (MAD). Essa versão é usada no tutorial de detecção de anomalias em tempo real com Python.

IQR: limites por quartis

O intervalo interquartil usa percentis em vez de médias:

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

Tudo o que estiver fora desses limites é um outlier. Use 3 em vez de 1,5 para outliers "extremos".

Pontos fortes: robusto aos outliers que procura, não pressupõe normalidade e lida com dados assimétricos, como amplitudes de vibração ou consumo de corrente.

Pontos fracos: precisa de uma janela grande o bastante para quartis estáveis e, em sensores muito quantizados (um sensor de temperatura com resolução de 0,5 grau, por exemplo), o IQR pode colapsar para zero e sinalizar qualquer pequena mudança. Adicione uma largura mínima de limite igual à resolução do sensor.

EWMA: linha de base suavizada com limites de controle

A EWMA mantém um valor suavizado contínuo em que as leituras recentes pesam mais:

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

Junto com ela, você acompanha uma variância exponencialmente ponderada e sinaliza leituras que caem fora de ewma ± k * ewm_std. Um alpha pequeno (0,01 a 0,05) gera uma linha de base lenta e estável; um alpha maior acompanha as mudanças rapidamente.

Pontos fortes: memória constante (dois números por sinal), ideal para microcontroladores, adapta-se naturalmente a derivas lentas e é sensível a pequenas mudanças sustentadas quando usada como gráfico de controle.

Pontos fracos: como se adapta, uma falha lenta pode ser absorvida pela linha de base. Combine-a com um limite absoluto para valores que nunca podem ser ultrapassados.

Comparando os métodos

Método Memória por sinal Robusto a outliers Lida com assimetria Detecta deriva lenta Cabe em microcontrolador
Z-score móvel Janela de N valores Não Não Mal Sim, com N pequeno
Z robusto (mediana/MAD) Janela de N valores Sim Em parte Mal Sim, com N pequeno
Limites por IQR Janela de N valores Sim Sim Mal Sim, com N pequeno
Limites de controle EWMA Dois números Em parte Não Sim, com alpha pequeno Facilmente

Nenhum método vence sozinho. Uma combinação prática para temperatura é: limites físicos rígidos, IQR para picos e EWMA com alpha pequeno para deriva.

Detecção de outliers em dados de sensores com Python

O código abaixo implementa os três detectores como pequenas classes com um método update, para que você possa rodá-los lado a lado no mesmo fluxo.

# 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 deixa de aprender com os valores que sinaliza, o que impede que um pico contamine a linha de base.

Aplicando a um fluxo MQTT de temperatura ao vivo

Para testar de ponta a ponta, publique temperaturas simuladas no servidor MQTT público (gratuito) em broker.connectmq.com e rode os detectores em um assinante. Substitua t9x4q2 pelo seu próprio ID aleatório; a área pública pode ser lida por qualquer pessoa, então use apenas dados sintéticos, nunca dados de produção.

# 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)                 # slow wander
    value = base + random.gauss(0, 0.15)
    if random.random() < 0.02:
        value += random.choice([-6, 7])           # occasional spike
    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()

Rode os dois e observe quais detectores concordam. Normalmente, você verá os três pegarem os picos grandes, enquanto o Z-score às vezes deixa passar um pico que vem logo depois de outro, exatamente o ponto fraco descrito acima.

Detectar no dispositivo vs depois do broker

Você pode rodar esses detectores em dois lugares, e a escolha afeta o que você publica.

Rodar no nó sensor, por exemplo um ESP32, significa que o dispositivo pode anexar uma flag de outlier a cada mensagem ou publicar apenas quando algo interessante acontecer. Isso economiza banda e funciona mesmo com a rede fora do ar, mas os limites ficam embutidos no firmware e são mais difíceis de mudar.

Rodar depois do broker, em um assinante como o acima, mantém os dispositivos simples e permite mudar métodos e limites a qualquer momento, comparar vários detectores em paralelo e reprocessar o histórico. O custo é que toda leitura bruta precisa viajar até o broker.

Um meio-termo comum é um limite rígido e uma verificação de valor travado no dispositivo, que detectam sensores quebrados na hora, e os detectores estatísticos em um serviço assinante, onde são fáceis de ajustar. Com um broker ConnectMQ no meio, as duas saídas chegam a topics que os mesmos dashboards podem ler.

Checklist prática

  1. Aplique primeiro limites físicos rígidos (um sensor de ambiente marcando 150 graus é uma falha, não um outlier).
  2. Detecte valores travados separadamente, verificando leituras idênticas ao longo de um período.
  3. Defina o tamanho das janelas em tempo, não em amostras, se os intervalos de publicação variarem.
  4. Defina larguras mínimas de faixa iguais à resolução do sensor.
  5. Sinalize os outliers em vez de excluí-los e guarde o valor bruto para análise posterior.
  6. Exija duas ou três sinalizações consecutivas antes de alertar uma pessoa.
  7. Revise os limites após uma semana de dados reais.

Quando os sinais interagem, por exemplo corrente e vazão em uma bomba de uma estação de saneamento, esses métodos por sinal chegam ao seu limite, e um modelo multivariado como o Isolation Forest em dados de sensores passa a valer a pena.

Onde o broker se encaixa

Cada peça acima é um cliente MQTT comum: o sensor publica, o detector assina e republica. O broker MQTT apenas roteia as mensagens, o que significa que você pode adicionar, remover ou substituir detectores sem tocar nos dispositivos. Em implantações reais, um broker MQTT gerenciado privado como o ConnectMQ, um broker MQTT na nuvem, acrescenta TLS, credenciais e um explorador de topics ao vivo onde você pode ver leituras brutas e outliers sinalizados lado a lado. Para mais formas de combinar análises com dispositivos, veja o guia de IA e IoT.

Comece a usar o ConnectMQ

Crie um broker no plano Serverless gratuito em app.connectmq.com, sem cartão de crédito, aponte os seus sensores para ele e rode o detector de outliers sobre os seus próprios dados em vez do simulador.

Perguntas frequentes

Qual método de detecção de outliers é melhor para sensores de temperatura?

Uma combinação funciona melhor: limites físicos, limites por IQR para picos e EWMA com um fator de suavização pequeno para derivas lentas. A temperatura muda devagar, então a EWMA é particularmente eficaz.

Que tamanho de janela devo usar para estatísticas móveis?

Cubra tempo suficiente para capturar o ruído normal, mas não tanto que mudanças legítimas levem uma eternidade para ser absorvidas. Para uma leitura por segundo, de um a cinco minutos é um ponto de partida razoável.

Os outliers devem ser removidos dos dados?

Sinalize-os em vez de excluí-los. Guarde o valor bruto com uma flag de outlier para poder auditar as decisões e treinar modelos novamente mais tarde.

Esses métodos podem rodar em um ESP32?

Sim. A EWMA precisa de apenas dois floats por sinal, e uma janela móvel de 30 a 60 valores cabe facilmente na memória do ESP32, então você pode sinalizar outliers antes de publicar.

Artigos relacionados

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.