Há um tipo de dado que quase todo pipeline de ML trata mal, e trata mal sempre da mesma forma. A curva de consumo de um cliente ao longo de 24 horas. O espectro de uma amostra em 500 comprimentos de onda. A trajetória de temperatura de um equipamento durante um ciclo de produção. O traçado de um sensor de movimento.
Nesses casos, a observação não é um vetor de números independentes — é uma função amostrada. Os pontos têm ordem, são altamente correlacionados, e a forma da curva carrega mais informação que qualquer valor isolado.
O tratamento usual é jogar as 24 horas como 24 colunas e seguir em frente. Funciona mal por três razões que valem entender antes de qualquer técnica.
Reconhecer variáveis funcionais e por que o tratamento vetorial as desperdiça, representar curvas por bases de funções, extrair características derivadas, e aplicar análise de componentes principais funcional (FPCA) para reduzir dimensionalidade preservando a forma.
24 colunas são um mau modelo de uma curva
O modelo ignora a ordem. Para um algoritmo, hora_07 e hora_08 são tão relacionadas quanto hora_07 e hora_22 — nenhuma. Embaralhar as colunas não mudaria nada no ajuste. Mas embaralhar os instantes de uma curva destrói exatamente a informação que interessa.
A correlação é altíssima. Consumo às 7h e às 8h são quase a mesma coisa. Em regressão linear isso é multicolinearidade severa: coeficientes instáveis, sinais que invertem com pequenas mudanças nos dados, interpretação impossível.
A dimensão explode sem ganho. Um espectro com 500 pontos vira 500 colunas — mas a curva inteira costuma ser descrita por três ou quatro parâmetros de forma. Você paga o custo estatístico de 500 dimensões para carregar a informação de quatro.
E há um quarto problema, que aparece em dados reais: as curvas nem sempre são amostradas nos mesmos pontos. Um sensor mediu de minuto em minuto, outro a cada cinco minutos, um terceiro falhou por meia hora. O modelo vetorial exige uma grade comum e obriga a preencher buracos; o modelo funcional trata a curva como um objeto contínuo e a irregularidade deixa de ser problema.
Representar por uma base de funções
A ideia central da análise de dados funcionais é deixar de representar a curva pelos valores observados e passar a representá-la pelos coeficientes de uma combinação de funções-base:
$$x(t) \approx \sum_{k=1}^{K} c_k \phi_k(t)$$
As funções $\phi_k$ são fixas e conhecidas; o que descreve a curva são os $K$ coeficientes $c_k$ — e $K$ é tipicamente muito menor que o número de pontos observados.
Duas famílias cobrem quase tudo:
- B-splines para curvas suaves e não periódicas (crescimento, trajetória de temperatura). São locais: mexer num coeficiente afeta só um trecho da curva.
- Fourier para curvas periódicas (consumo diário, sazonalidade, sinal). São globais: cada coeficiente descreve uma frequência presente na curva inteira.
Em R, o fda é a implementação de referência:
library(fda)
# curvas em uma matriz: linhas = instantes, colunas = indivíduos
instantes <- seq(0, 24, length.out = 144)
base <- create.bspline.basis(rangeval = c(0, 24), nbasis = 15, norder = 4)
# lambda penaliza a 2ª derivada: controla o quanto a curva pode "ondular"
par_suavizacao <- fdPar(base, Lfdobj = 2, lambda = 1e-2)
ajuste <- smooth.basis(instantes, curvas, par_suavizacao)
curvas_fd <- ajuste$fd
plot(curvas_fd, xlab = "hora", ylab = "consumo (kWh)")
O lambda é o parâmetro que mais importa e o mais fácil de errar. Ele regula o compromisso entre seguir os dados e manter a curva suave: baixo demais, a representação incorpora o ruído da medição; alto demais, achata características reais. A escolha se faz por validação cruzada generalizada:
lambdas <- 10^seq(-4, 2, by = 0.5)
gcv <- sapply(lambdas, function(l) {
sum(smooth.basis(instantes, curvas, fdPar(base, 2, l))$gcv)
})
melhor <- lambdas[which.min(gcv)]
Características derivadas: o caminho pragmático
Antes de FPCA, vale conhecer a abordagem que resolve a maior parte dos casos práticos com uma fração do esforço: extrair de cada curva um punhado de descritores interpretáveis.
import numpy as np
def descritores(curva, t):
"""Reduz uma curva a características com significado de domínio."""
d1 = np.gradient(curva, t)
return {
"media": curva.mean(),
"desvio": curva.std(),
"maximo": curva.max(),
"instante_max": t[curva.argmax()],
"minimo": curva.min(),
"amplitude": curva.max() - curva.min(),
"area": np.trapezoid(curva, t),
"inclin_max": d1.max(),
"inclin_min": d1.min(),
"cruzamentos": int((np.diff(np.sign(curva - curva.mean())) != 0).sum()),
}
A vantagem decisiva é a interpretabilidade. "O instante do pico de consumo" e "a área sob a curva" são grandezas que um especialista do domínio entende, questiona e valida. Um coeficiente de spline, não.
A regra prática: comece por aqui. Se os descritores explicam bem o fenômeno, o problema acabou. Passe a FPCA quando eles não bastarem, ou quando a forma da curva importar de um jeito que nenhum descritor captura.
Note que instante_max é uma característica do eixo horizontal, e ela costuma ser a mais informativa das dez. Quando duas curvas têm o mesmo pico mas em horários diferentes, nenhuma estatística de valor as distingue.
FPCA: componentes principais para curvas
A FPCA é a versão funcional do PCA. Em vez de direções no espaço de variáveis, ela encontra funções — modos de variação — que explicam o máximo da variabilidade entre as curvas.
fpca <- pca.fd(curvas_fd, nharm = 4)
fpca$varprop # proporção da variância por componente
plot(fpca$harmonics) # os modos de variação, como funções
escores <- fpca$scores # matriz indivíduos x componentes: entra no modelo
O que torna a FPCA valiosa é que os modos costumam ser interpretáveis como formas. Num conjunto de curvas de consumo diário, é comum encontrar:
- CP1 — nível geral (a curva inteira sobe ou desce);
- CP2 — contraste dia/noite (consumo diurno alto com noturno baixo, ou o inverso);
- CP3 — deslocamento do pico (o pico acontece mais cedo ou mais tarde);
- CP4 — quantidade de oscilação.
Quatro números por cliente, cada um com significado claro, substituindo 144 colunas. Esses escores entram em qualquer modelo comum:
dados_modelo <- data.frame(escores, classe = rotulos)
modelo <- ranger::ranger(classe ~ ., data = dados_modelo)
Para visualizar o que cada componente significa, o recurso mais útil é desenhar a média com e sem o efeito do componente:
plot.pca.fd(fpca) # média ± um múltiplo de cada harmônica
Alinhamento: o passo que quase todos pulam
Existe um problema anterior a qualquer análise, e ignorá-lo estraga tudo: variação de fase.
Duas curvas podem ter exatamente a mesma forma e picos em instantes diferentes — dois pacientes com a mesma resposta fisiológica, com cinco minutos de defasagem. Tratadas sem alinhamento, a análise vai gastar o primeiro componente descrevendo a defasagem e a interpretação fica poluída.
A decomposição correta separa duas fontes de variação:
- amplitude — o quanto a curva sobe e desce;
- fase — quando os eventos acontecem.
alinhado <- register.fd(yfd = curvas_fd, y0fd = mean.fd(curvas_fd))
curvas_alinhadas <- alinhado$regfd # variação de amplitude
deformacoes <- alinhado$warpfd # variação de fase
E aqui está o detalhe que gera decisão de modelagem: a fase frequentemente é a informação que interessa. Se você está classificando pacientes pela rapidez da resposta, jogar a deformação fora seria descartar o alvo. Alinhe, mas guarde as deformações como características — elas costumam ser tão preditivas quanto a forma alinhada.
Em Python
O ecossistema R é mais maduro nessa área, mas o scikit-fda cobre bem o essencial:
from skfda.representation.basis import BSplineBasis
from skfda.preprocessing.dim_reduction import FPCA
from skfda.representation.grid import FDataGrid
fd = FDataGrid(curvas, grid_points=instantes)
fd_suave = fd.to_basis(BSplineBasis(n_basis=15))
fpca = FPCA(n_components=4)
escores = fpca.fit_transform(fd_suave)
Quando não quiser uma dependência a mais, há um atalho honesto: aplicar PCA comum sobre as curvas suavizadas e reamostradas numa grade fixa. Não é FPCA — não há base funcional nem penalização de derivada —, mas captura boa parte dos modos de variação e frequentemente basta.
Os erros que custam caro
Padronizar coluna a coluna. Aplicar StandardScaler nos 144 instantes trata cada um como variável independente e destrói a forma da curva. Se for preciso normalizar, normalize a curva inteira (subtraindo sua média, dividindo por sua norma).
Imputar buracos com a média da coluna. Introduz descontinuidades artificiais no meio da curva. Em dado funcional, o buraco se resolve na suavização, que interpola respeitando a forma local.
Ignorar a irregularidade da amostragem. Curvas medidas em grades diferentes não podem ser empilhadas numa matriz. A representação por base resolve isso naturalmente: cada curva é ajustada em seus próprios instantes e passa a ser descrita pelos mesmos $K$ coeficientes.
Uma variável funcional é uma função amostrada, não um vetor de valores independentes. Tratá-la como colunas ignora a ordem, gera multicolinearidade severa e infla a dimensão sem ganho nenhum. A representação por base de funções descreve a curva com poucos coeficientes: B-splines para curvas suaves, Fourier para periódicas. O lambda que penaliza a derivada é o parâmetro crítico — baixo demais incorpora ruído, alto demais achata o sinal —, e a escolha sai da validação cruzada generalizada. Antes de qualquer coisa sofisticada, vale começar pelos descritores derivados: pico, instante do pico, área, inclinações, cruzamentos. Resolvem a maior parte dos casos e o especialista consegue lê-los. A FPCA encontra modos de variação que costumam ter leitura clara — nível, contraste, deslocamento do pico, oscilação — e seus escores entram em qualquer modelo. O alinhamento separa amplitude de fase: alinhe, mas guarde as deformações, porque a fase costuma ser preditiva. Nunca padronize instante a instante nem impute com a média da coluna, que são duas formas de destruir a forma da curva. E amostragem irregular deixa de ser problema assim que a curva passa a ser representada por base.
Exercícios
-
Por que tratar 144 medições diárias como 144 colunas independentes é estatisticamente ruim?
✓ Resposta:Três problemas somados. (a) A ordem some: para o algoritmo, os instantes 07:00 e 07:10 não são mais próximos que 07:00 e 23:00 — embaralhar as colunas não mudaria o ajuste, embora destrua a curva. (b) Multicolinearidade severa: medições adjacentes são quase idênticas, o que torna os coeficientes de modelos lineares instáveis, com sinais que invertem diante de pequenas perturbações nos dados. (c) Dimensão inflada: paga-se o custo estatístico de 144 variáveis para carregar uma informação que costuma caber em três ou quatro parâmetros de forma, o que piora a razão entre número de observações e de parâmetros e aumenta o risco de sobreajuste.
-
O que o
lambdacontrola na suavização e o que acontece nos dois extremos?✓ Resposta:Ele pondera a penalização sobre uma derivada (tipicamente a segunda) no ajuste, regulando o compromisso entre fidelidade aos pontos observados e suavidade da curva resultante. Com
lambdabaixo demais, o ajuste persegue cada ponto e incorpora o ruído de medição como se fosse sinal — a curva fica cheia de ondulações espúrias que poluem derivadas e componentes principais. Comlambdaalto demais, a penalização domina e a curva tende a uma reta, achatando características reais como picos e vales. A escolha se faz por validação cruzada generalizada, minimizando o GCV sobre uma grade de valores. -
Você extraiu média, desvio e máximo de cada curva, e o modelo não separa as classes. O que provavelmente falta?
✓ Resposta:Características do eixo horizontal — o quando. Média, desvio e máximo são estatísticas dos valores e são completamente invariantes a qualquer reordenação dos instantes: duas curvas com o mesmo pico às 8h e às 20h produzem exatamente os mesmos três números. Se as classes se distinguem pelo momento em que os eventos ocorrem, essa informação simplesmente não está nas variáveis. Acrescente
instante_max,instante_min, inclinações máxima e mínima (que capturam a velocidade das transições), número de cruzamentos pela média e, se houver periodicidade, os coeficientes de Fourier das primeiras frequências. -
O que a FPCA oferece além do PCA aplicado à matriz de instantes?
✓ Resposta:Três coisas. (a) Os componentes são funções suaves, e não vetores de pesos por instante — o que os torna interpretáveis como formas ("o pico acontece mais cedo") em vez de listas de coeficientes. (b) A suavização por base filtra o ruído antes da decomposição, de modo que os componentes descrevem variação real e não flutuação de medição. (c) A representação funcional acomoda amostragem irregular: curvas medidas em instantes diferentes são ajustadas cada uma em sua própria grade e passam a ser descritas pelos mesmos coeficientes, algo que o PCA matricial não permite sem interpolar antes. Na prática, quando as curvas são suaves e a grade é comum, PCA sobre curvas suavizadas é uma aproximação razoável.
-
Por que descartar as deformações do alinhamento pode ser um erro grave?
✓ Resposta:Porque o alinhamento separa a variação em amplitude (a forma da curva) e fase (quando os eventos acontecem), e em muitos problemas a fase é o fenômeno de interesse. Classificar pacientes pela rapidez da resposta a um medicamento, detectar equipamentos cujo ciclo está atrasando, distinguir clientes que consomem cedo dos que consomem tarde — em todos esses casos, alinhar as curvas e ficar só com a parte alinhada joga fora exatamente o sinal preditivo, deixando um conjunto de curvas quase idênticas. O procedimento correto é alinhar e extrair características das funções de deformação (deslocamento médio, sua variação ao longo do domínio), usando ambas como entrada do modelo.