Início ⚙️Princípios 🔌Sensores 🌡️Pressão 🪣Nível 🌡️Temperatura 💧Vazão 🎛️Controle ⚗️ Lab PID 🛡️Segurança 🔧 Calibração 📡 Comunicação 🤖 IIoT 4.0 📊 Análise 📜 Normas 📋 Prova
📊 Módulo 12 — Análise de Dados de Processo

Análise de Dados
de Processo

📖 65 min de leitura 📋 10 tópicos 🚀 Diferencial de mercado

1. Por que o Instrumentista precisa analisar dados?

Toda planta industrial gera terabytes de dados por dia — cada transmissor registra no historian centenas de leituras por hora. A diferença entre uma planta que apenas coleta dados e uma que gera valor está na capacidade de transformar séries temporais em decisões.

O instrumentista que domina análise de dados não apenas mantém os instrumentos funcionando — ele identifica padrões de degradação antes da falha, comprova o impacto de ajustes de malha e comunica problemas de processo com evidência numérica.

💡

Ferramentas do módulo

Python 3.x com as bibliotecas: pandas (manipulação de dados) · numpy (cálculo numérico) · matplotlib / plotly (visualização) · scipy (estatística) · sklearn (machine learning). Todos gratuitos e open-source.

1.1 Pipeline de Análise de Dados Industriais

🗄️
Coleta
Historian · OPC-UA · MQTT · CSV export do SDCD
🧹
Limpeza
Remove NaN · Trata outliers · Reamostragen
🔍
Exploração
Estatísticas descritivas · Distribuições · Correlação
📈
Análise
Tendências · FFT · Controle estatístico (SPC)
🤖
Modelagem
Regressão · Detecção de anomalia · Preditivo
📊
Comunicação
Dashboard · Relatório · Alerta automático

2. Importação e Limpeza de Dados do Historian

Os dados exportados do historian geralmente vêm em CSV com timestamps, valores e qualidade. O primeiro passo é importar, parsear o índice temporal e tratar os dados ruins.

#timestampPT101_barTT201_C FT301_m3hLT401_pctquality
0 2024-03-01 08:00:00 42.31 185.4 12.80 67.2 GOOD
1 2024-03-01 08:01:00 42.28 185.6 12.75 67.4 GOOD
2 2024-03-01 08:02:00 NaN 185.5 12.82 67.3 BAD
3 2024-03-01 08:03:00 42.35 999.0 12.78 67.1 GOOD
4 2024-03-01 08:04:00 42.30 185.8 12.81 67.5 GOOD
5 2024-03-01 08:05:00 42.27 185.9 12.76 67.2 GOOD
6 2024-03-01 08:06:00 112.50 186.0 12.80 67.3 GOOD
7 2024-03-01 08:07:00 42.29 186.1 NaN 67.6 UNCERTAIN
8 2024-03-01 08:08:00 42.32 186.0 12.77 67.4 GOOD

🔴 Vermelho = dado problemático (NaN, outlier grosseiro, quality BAD)  ·  🟡 Amarelo = incerto  ·  🟢 Verde = dado válido

🐍 python · pandas 01_importacao_limpeza.py
import pandas as pd
import numpy as np

# ── 1. Importar CSV do historian ──────────────────────────────
df = pd.read_csv(
    "historian_export.csv",
    parse_dates=["timestamp"],
    index_col="timestamp"
)
print(df.info())
print(f"Shape inicial: {df.shape}")

# ── 2. Remover registros com quality ruim ─────────────────────
df = df[df["quality"] == "GOOD"].drop(columns=["quality"])

# ── 3. Converter para numérico (coerce transforma erros em NaN)
for col in df.columns:
    df[col] = pd.to_numeric(df[col], errors="coerce")

# ── 4. Remover outliers grosseiros por faixa de engenharia ────
limites = {
    "PT101_bar" : (0,   100),   # transmissor 0–100 bar
    "TT201_C"   : (0,   400),   # termopar 0–400 °C
    "FT301_m3h" : (0,   50),    # vazão 0–50 m³/h
    "LT401_pct" : (0,   100),   # nível 0–100 %
}
for col, (lo, hi) in limites.items():
    df[col] = df[col].where(df[col].between(lo, hi))

# ── 5. Interpolação linear para NaN isolados (máx 3 min) ─────
df = df.interpolate(method="time", limit=3)

# ── 6. Reamostrar para 1 minuto (média) ──────────────────────
df = df.resample("1min").mean().round(3)

print(f"Shape final: {df.shape}")
print(df.head())

3. Estatística Descritiva de Variáveis de Processo

Antes de qualquer análise avançada, as estatísticas descritivas revelam o comportamento central e a dispersão de cada variável — essencial para validar faixas de operação e identificar deriva.

📊 Estatísticas Descritivas — PT-101 (Pressão do Reator)
Histograma de distribuição — barras vermelhas = fora de ±2σ (possíveis anomalias)
🐍 python · estatística descritiva 02_estatistica.py
# Estatísticas descritivas completas
desc = df.describe(percentiles=[.05, .25, .5, .75, .95])
print(desc.round(3))

# Detectar assimetria e curtose (relevante para alarmes)
print("\nAssimetria (skewness):")
print(df.skew().round(3))

print("\nCurtose (kurtosis):")
print(df.kurt().round(3))

# Verificar operação fora da faixa normal (±2σ)
for col in df.columns:
    media  = df[col].mean()
    sigma  = df[col].std()
    fora   = ((df[col] < media - 2*sigma) |
              (df[col] > media + 2*sigma)).sum()
    pct    = fora / len(df) * 100
    print(f"{col}: {pct:.1f}% fora de ±2σ"
          f"  (μ={media:.2f}, σ={sigma:.3f})")

4. Análise de Tendências e Deriva de Instrumentos

A deriva (drift) é a variação lenta e sistemática do zero ou span de um instrumento ao longo do tempo. Detectá-la antes da falha ou saída de calibração é um dos maiores valores que a análise de dados agrega.

📈 Trend Analysis — Pressão e Tendência Linear
🐍 python · tendência e deriva 03_tendencia_deriva.py
import numpy as np
from scipy import stats

col = "PT101_bar"
serie = df[col].dropna()

# ── Regressão linear (detecção de deriva) ─────────────────────
x = np.arange(len(serie))
slope, intercept, r, p, se = stats.linregress(x, serie.values)

print(f"Inclinação:  {slope*60:.4f} unid/hora")   # 60 amostras/hora
print(f"R²:          {r**2:.4f}")
print(f"p-valor:     {p:.6f} {'→ deriva significativa!' if p < 0.05 else '→ estável'}")

# ── Alerta se deriva diária superar 0.5% do span ──────────────
span       = 100    # bar (URV - LRV)
min_hora   = 60     # amostras por hora
deriva_dia = abs(slope) * min_hora * 24
limite     = span * 0.005

if deriva_dia > limite:
    print(f"⚠️  ALERTA: deriva de {deriva_dia:.3f} bar/dia"
          f" supera limite de {limite:.2f} bar/dia")
    print("   → Agendar calibração preventiva!")

# ── Média móvel (suavização de ruído) ─────────────────────────
df["PT101_MA10"] = df[col].rolling(window=10, center=True).mean()
df["PT101_MA60"] = df[col].rolling(window=60, center=True).mean()

# ── Banda de controle ±2σ (janela deslizante 1h) ──────────────
df["PT101_upper"] = df["PT101_MA60"] + 2 * df[col].rolling(60).std()
df["PT101_lower"] = df["PT101_MA60"] - 2 * df[col].rolling(60).std()

5. Correlação entre Variáveis de Processo

A matriz de correlação revela quais variáveis se movem juntas — fundamental para identificar relações de causa-efeito e variáveis redundantes ou compensatórias no processo.

MATRIZ DE CORRELAÇÃO DE PEARSON — VARIÁVEIS DO REATOR
PT-101
TT-201
FT-301
LT-401
PT-101
1.00
0.87
0.72
-0.45
TT-201
0.87
1.00
0.61
-0.38
FT-301
0.72
0.61
1.00
-0.22
LT-401
-0.45
-0.38
-0.22
1.00
🔴 Vermelho = correlação positiva forte  ·  🔵 Azul = correlação negativa  ·  Diagonal = autocorrelação (1,00)
🐍 python · correlação e causalidade 04_correlacao.py
import seaborn as sns
import matplotlib.pyplot as plt

# ── Matriz de correlação de Pearson ───────────────────────────
corr_matrix = df.corr(method="pearson")
print(corr_matrix.round(3))

# ── Heatmap visual ────────────────────────────────────────────
plt.figure(figsize=(8, 6))
sns.heatmap(
    corr_matrix,
    annot=True, fmt=".2f",
    cmap="RdBu_r", vmin=-1, vmax=1,
    square=True, linewidths=0.5
)
plt.title("Correlação entre variáveis do Reator")
plt.tight_layout()
plt.savefig("correlacao_reator.png", dpi=150)

# ── Correlação cruzada com lag (causa-efeito) ─────────────────
# Ex.: mudança de FT-301 afeta PT-101 após quantos minutos?
lags = range(-30, 31)   # ±30 minutos
xcorr = [
    df["FT301_m3h"].corr(df["PT101_bar"].shift(lag))
    for lag in lags
]
lag_max = lags[xcorr.index(max(xcorr))]
print(f"Máxima correlação FT→PT em lag = {lag_max} min")
print(f"Isso indica que FT-301 afeta PT-101 com {lag_max} min de atraso")

6. SPC — Controle Estatístico de Processo

O SPC (Statistical Process Control) utiliza gráficos de controle para distinguir variação natural (aleatória) de variação especial (causas assinaláveis) — fundamentado nas regras de Western Electric (WECO) e nos trabalhos de Walter Shewhart.

GráficoO que monitoraQuando usar
X̄ (Média)Variação da média do processoDados contínuos, amostras de 2–10
R (Amplitude)Variabilidade dentro do subgrupoPar com X̄, n < 10
X individual (I)Cada ponto individualmenteUm dado por vez (historian, 1 min)
MR (Moving Range)Variação entre pontos consecutivosPar com gráfico I
CUSUMDesvios acumulados da médiaDetectar pequenas derives < 1σ
EWMAMédia móvel exponencialmente ponderadaProcessos autocorrelacionados
🐍 python · gráfico I-MR (SPC individual) 05_spc_imr.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def grafico_IMR(serie, titulo=""):
    """Gráfico de controle Individual e Amplitude Móvel."""
    n  = len(serie)
    MR = serie.diff().abs()   # amplitude móvel

    # Constantes d2 e D4 para n=2 (tabela Shewhart)
    d2, D4 = 1.128, 3.267

    # Limites do gráfico I
    CL_I  = serie.mean()
    sigma = MR.mean() / d2
    UCL_I = CL_I + 3 * sigma
    LCL_I = CL_I - 3 * sigma

    # Limites do gráfico MR
    CL_MR  = MR.mean()
    UCL_MR = D4 * CL_MR

    # Plot duplo I-MR
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 6), sharex=True)

    # Gráfico I
    ax1.plot(serie.values, color="#22c55e", lw=1.2)
    ax1.axhline(UCL_I, color="#ef4444", ls="--", label=f"UCL={UCL_I:.2f}")
    ax1.axhline(CL_I,  color="#f59e0b", ls="-",  label=f"CL={CL_I:.2f}")
    ax1.axhline(LCL_I, color="#ef4444", ls="--", label=f"LCL={LCL_I:.2f}")

    # Destacar pontos fora de controle
    oos = (serie > UCL_I) | (serie < LCL_I)
    ax1.scatter(serie.index[oos], serie[oos], color="#ef4444", s=40, zorder=5)
    ax1.set_title(f"Gráfico I — {titulo} ({oos.sum()} pontos OOS)")
    ax1.legend(fontsize=8)

    # Gráfico MR
    ax2.bar(range(n), MR.values, color="#3b82f6", alpha=0.6)
    ax2.axhline(UCL_MR, color="#ef4444", ls="--", label=f"UCL={UCL_MR:.2f}")
    ax2.axhline(CL_MR,  color="#f59e0b", ls="-",  label=f"CL={CL_MR:.2f}")
    ax2.set_title("Gráfico MR — Amplitude Móvel")
    ax2.legend(fontsize=8)

    plt.tight_layout()
    return fig, sigma, oos.sum()

fig, sigma, n_oos = grafico_IMR(df["PT101_bar"], "PT-101 Pressão")
print(f"σ estimado: {sigma:.4f} bar | Pontos OOS: {n_oos}")
plt.savefig("spc_PT101.png", dpi=150)

7. Análise Espectral — FFT para Diagnóstico de Vibração

A Transformada Rápida de Fourier (FFT) decompõe um sinal temporal em suas componentes de frequência. Em instrumentação, é usada para diagnóstico de vibração em bombas, compressores e motores — identificando frequências características de falhas (desbalanceamento, desalinhamento, folga de rolamento).

🐍 python · fft e diagnóstico de vibração 06_fft_vibracao.py
import numpy as np
from scipy.fft import fft, fftfreq
import matplotlib.pyplot as plt

# Sinal de vibração de uma bomba (acelerômetro, 1000 Hz)
fs     = 1000                        # frequência de amostragem (Hz)
signal = df_vib["accel_g"].values   # aceleração em g
N      = len(signal)

# ── FFT ───────────────────────────────────────────────────────
yf   = fft(signal)
xf   = fftfreq(N, 1/fs)[:N//2]     # frequências positivas
amp  = (2.0/N) * np.abs(yf[:N//2])  # amplitude real

# ── Frequências características da bomba ─────────────────────
RPM        = 1450           # rotação nominal
f_rotor    = RPM / 60       # = 24,2 Hz → 1× rotação
f_palhetas = f_rotor * 6    # = 145 Hz → 6 palhetas (BPF)
f_rolamento= 87.3           # frequência de falha BPFO (calculada)

# ── Extrair amplitude nas frequências de interesse ────────────
def amp_em(freq, tol=2.0):
    mask = np.abs(xf - freq) < tol
    return amp[mask].max() if mask.any() else 0

print(f"1× rotação ({f_rotor:.1f} Hz):    {amp_em(f_rotor):.4f} g")
print(f"BPF        ({f_palhetas:.1f} Hz): {amp_em(f_palhetas):.4f} g")
print(f"BPFO       ({f_rolamento:.1f} Hz): {amp_em(f_rolamento):.4f} g")

# Alarme: amplitude 1× > 1g indica desbalanceamento
if amp_em(f_rotor) > 1.0:
    print("⚠️  ALERTA: Desbalanceamento! → Verificar balanceamento dinâmico")

8. Regressão e Predição de Variáveis de Processo

Modelos de regressão permitem prever o valor de uma variável a partir de outras, criar sensores virtuais (soft sensors) e validar leituras de instrumentos com redundância analítica.

🐍 python · sensor virtual (soft sensor) 07_sensor_virtual.py
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.preprocessing import StandardScaler
import joblib

# ── Sensor virtual: prever PT-101 a partir de FT, TT e LT ────
# Útil quando PT-101 está em manutenção ou indica erro

features = ["TT201_C", "FT301_m3h", "LT401_pct"]
target   = "PT101_bar"

X = df[features].dropna()
y = df.loc[X.index, target]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, shuffle=False  # não embaralhar séries temporais!
)

# Normalização
scaler  = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

# ── Modelo GBR ───────────────────────────────────────────────
modelo = GradientBoostingRegressor(
    n_estimators=200, max_depth=4,
    learning_rate=0.05, random_state=42
)
modelo.fit(X_train, y_train)

y_pred = modelo.predict(X_test)
mae    = mean_absolute_error(y_test, y_pred)
r2     = r2_score(y_test, y_pred)

print(f"Sensor virtual PT-101:")
print(f"  MAE = {mae:.4f} bar")
print(f"  R²  = {r2:.4f} ({'excelente' if r2>0.95 else 'bom' if r2>0.85 else 'revisar'})")

# Detectar discrepância: instrumento real vs sensor virtual
diferenca = abs(df[target] - modelo.predict(scaler.transform(df[features])))
if diferenca.iloc[-1] > 2.0:  # 2 bar de diferença
    print("⚠️  Divergência entre PT-101 real e virtual → Verificar instrumento!")

# Salvar modelo treinado
joblib.dump((modelo, scaler), "sensor_virtual_PT101.pkl")

9. Relatório Automático de Processo

A etapa final da análise é comunicar os resultados de forma clara. Com Python é possível gerar relatórios PDF ou Excel automaticamente, publicar em dashboards web ou enviar alertas por e-mail/Teams.

🐍 python · relatório automático + alerta e-mail 08_relatorio_alerta.py
import pandas as pd
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from datetime import date
import openpyxl

def gerar_relatorio_excel(df, anomalias, arquivo):
    """Gera relatório diário em Excel com múltiplas abas."""
    with pd.ExcelWriter(arquivo, engine="openpyxl") as writer:
        # Aba 1 — Resumo estatístico
        df.describe().round(3).to_excel(writer, sheet_name="Resumo")

        # Aba 2 — Dados brutos
        df.to_excel(writer, sheet_name="Dados")

        # Aba 3 — Anomalias detectadas
        anomalias.to_excel(writer, sheet_name="Anomalias")

    print(f"✅ Relatório salvo: {arquivo}")

def enviar_alerta(destinatario, assunto, corpo, anexo=None):
    """Envia alerta por e-mail com relatório em anexo."""
    msg = MIMEMultipart()
    msg["From"]    = "planta@empresa.com.br"
    msg["To"]      = destinatario
    msg["Subject"] = assunto
    msg.attach(MIMEText(corpo, "html"))

    if anexo:
        with open(anexo, "rb") as f:
            part = MIMEBase("application", "octet-stream")
            part.set_payload(f.read())
            encoders.encode_base64(part)
            part.add_header("Content-Disposition",
                            f"attachment; filename={anexo}")
            msg.attach(part)

    with smtplib.SMTP("mail.empresa.com.br", 587) as smtp:
        smtp.starttls()
        smtp.login("planta@empresa.com.br", "senha")
        smtp.send_message(msg)
    print(f"📧 Alerta enviado para {destinatario}")

# ── Execução diária (agendar com cron ou Task Scheduler) ──────
hoje     = date.today().isoformat()
arquivo  = f"relatorio_{hoje}.xlsx"

gerar_relatorio_excel(df, anomalias, arquivo)

if len(anomalias) > 0:
    corpo = f"""
    <h2>⚠️ Relatório de Processo — {hoje}</h2>
    <p><b>{len(anomalias)} anomalias</b> detectadas nas últimas 24h.</p>
    <p>Ver relatório em anexo para detalhes.</p>
    """
    enviar_alerta(
        "instrumentacao@empresa.com.br",
        f"⚠️ Anomalias Processo — {hoje}",
        corpo, arquivo
    )

10. Guia Rápido de Ferramentas e Recursos

AMBIENTE
Jupyter Lab
IDE interativa para análise exploratória. Instale: pip install jupyterlab. Suporte a gráficos inline e markdown.
DADOS
Pandas + NumPy
Base de qualquer análise. Pandas para séries temporais indexadas; NumPy para vetorização e cálculo matricial eficiente.
VISUALIZAÇÃO
Plotly + Dash
Gráficos interativos com zoom, pan e tooltip. Dash transforma scripts Python em dashboards web sem HTML/JS.
ESTATÍSTICA
SciPy + Statsmodels
Testes de hipótese, regressão avançada, análise espectral (FFT, PSD), séries temporais (ARIMA, ACF, PACF).
MACHINE LEARNING
Scikit-Learn
Isolation Forest, Gradient Boosting, Random Forest, SVM, PCA — todos aplicáveis a dados industriais com poucas linhas.
CONEXÃO
opcua + paho-mqtt
Leitura direta do historian OPC-UA (pip install opcua) e do broker MQTT (pip install paho-mqtt) em tempo real.
🚀

Por onde começar?

1. Exporte um CSV do seu historian ou SDCD com 30 dias de dados de um transmissor crítico.
2. Instale: pip install pandas matplotlib scipy scikit-learn openpyxl jupyter
3. Aplique os scripts deste módulo em sequência — coleta → limpeza → estatística → tendência → SPC.
4. Apresente os resultados para a equipe de manutenção. Isso é análise de dados industrial na prática.

⚡ Questão Rápida — 1
Ao analisar uma série temporal de um transmissor de pressão, você aplica regressão linear e obtém slope = +0,008 bar/amostra com p-valor = 0,001 (1 amostra/minuto). Qual é a interpretação correta?
A O instrumento está estável — variação dentro do ruído normal
B Deriva estatisticamente significativa de +11,5 bar/dia — agendar calibração
C O processo está aumentando de pressão normalmente
D O p-valor alto indica que a tendência não é confiável
Correto! 0,008 bar/amostra × 60 amostras/hora × 24h = 11,52 bar/dia. p-valor = 0,001 < 0,05 → tendência estatisticamente significativa → deriva real → calibração urgente!
❌ Slope × 60 × 24 = +11,52 bar/dia. p-valor = 0,001 confirma significância estatística. Isso é deriva real do instrumento — não variação de processo.
⚡ Questão Rápida — 2
Em um gráfico de controle I-MR, um ponto cai acima do UCL (Upper Control Limit). Isso indica:
A Variação aleatória normal do processo — nenhuma ação necessária
B O processo atingiu sua capacidade máxima
C Causa especial (assinalável) presente — investigar e eliminar a causa-raiz
D O limite de controle está configurado incorretamente
Correto! Pontos fora dos limites de controle (UCL/LCL) indicam causas especiais (assinaláveis) — eventos não aleatórios que exigem investigação: troca de matéria-prima, falha de instrumento, mudança de operação, etc.
❌ Ponto acima do UCL em gráfico SPC indica causa especial (assinalável) — não é variação natural. Requer investigação e ação corretiva imediata.