Taller 1 - Análisis de datos funcionales.

Autores/as

Carol Sofia Calderon

Camilo Gómez

Ana Maria Valencia

Código
library(R.matlab)
library(fda)
library(plotly)
library(dplyr)
library(depthTools)
library(mrfDepth)
library(ggplot2)
library(dplyr)
library(tidyr)
library(robustbase)
library(Matrix)
library(kableExtra)
library(fdaoutlier)
library(matrixcalc)
library(ccaPP)

1 Descripción de datos

Fluorescencia

La fluorescencia es la propiedad de algunos átomos y moléculas de absorber luz a una longitud de onda determinada (excitación) seguido por la emisión de luz de corta duración a una longitud de onda más larga.

La fluorescencia implica una fuente de luz externa para excitar la muestra a una longitud de onda en particular. Cuando se excita a la longitud de onda adecuada, la molécula pasa de un estado básico a otro excitado. A medida que la molécula vuelve al estado fundamental, se libera energía en forma de calor (pérdida de energía) y luz a una longitud de onda diferente de menor energía.

Recolección de los datos

Se recolectaron muestras de azúcar durante tres meses de operación en una planta azucarera ubicada en Escandinavia. Las muestras se tomaron cada ocho horas, lo que corresponde a tres mediciones diarias, para un total de 268 muestras a lo largo del periodo de estudio.

El azúcar se disolvió en agua en una proporción de 2.25 g por 15 ml, y la solución resultante se analizó utilizando un espectrofluorómetro PE LS50B.

Cada muestra fue excitada a siete longitudes de onda (230, 240, 255, 290, 305, 325 y 340 nm). Para cada una de estas excitaciones, el equipo registró la intensidad de emisión en 571 longitudes de onda. Los espectros de emisión se midieron en el rango de 275 a 560 nm, con intervalos de 0,5 nm.

Almacenamiento de los datos

Los datos de fluorescencia se almacenan en una matriz de 268 x 3997. Las primeras 571 columnas corresponden a las mediciones de emisión para la primera longitud de onda de excitación (340 nm). Las últimas 571 columnas corresponden a las mediciones de emisión para la última longitud de onda de excitación (230 nm)

Asi se tienen las curvas espectrométricas asociadas a la producción de azúcar corresponden a 7 longitudes de onda de excitación \(Y_1 = 230\,\text{nm},\; Y_2 = 240\,\text{nm},\; Y_3 = 255\,\text{nm},\; Y_4 = 290 \text{nm},\; Y_5 = 305\,\text{nm},\)

\(Y_6 = 325\,\text{nm},\; Y_7 = 340\,\text{nm}\).

Las observaciones correspondientes a cada longitud de excitación representan un proceso funcional continuo \(Y_i\) , así que el proceso de producción de azúcar se puede considerar como un proceso funcional multivariado \(\mathbf{Y}\) está conformado por \(\mathbf{Y}=(Y_1, Y_2, ..., Y_7)\).

Código
sugar <- readMat("C:\\Users\\sofia\\OneDrive\\Documentos\\2026-1\\FDA\\data.mat")
sugar_fluo <- sugar$X
dim(sugar_fluo) 
[1]  268 3997
Código
t <-sugar$EmAx
wvl <- array(NA, dim = c(571, 268, 7))
for (k in 1:7) {
start <- 571 * (k- 1) + 1
end <- 571 * k
wvl[, , 8- k] <- t(sugar_fluo[,start:end])
}
nms <- c("340nm", "325nm", "305nm", "290nm", "255nm", "240nm", "230nm")
nms <- rev(nms)
dimnames(wvl) <- list(NULL, NULL, nms)

2 Visualización de los espectros de emisión

Figura 1: Espectros de emisión para los siete procesos.

3 Suavizado de las curva

El objetivo es ajustar una función a un conjunto de observaciones discretizadas \(y_j\), con \(j = 1, \ldots, n\), bajo \[ y_j = x(t_j) + \varepsilon_j \]

Donde \(x\) es una función desconocida y \(\varepsilon_j\) es el término de error.

La función \(x\) para cada tiempo, se aproxima mediante:

\[ \hat{x}(t) \approx \sum_{k=1}^{K} c_k \phi_k(t) \]

Las funciones \(\phi_k\) constituyen la base funcional utilizada en la aproximación y su forma específica depende del tipo de base seleccionada (por ejemplo, Fourier o B-splines).

Los coeficientes \(c_k\) representan los pesos asociados a cada función base dentro de la combinación lineal y se estiman mediante mínimos cuadrados o mínimos cuadrados penalizados. Finalmente, \(K\) es el número total de funciones base empleadas en la aproximación y determina, en parte, la flexibilidad del modelo.

Suavizado splines

La metodología de suavizamiento mediante splines también se basa en la representación de la función como una combinación lineal de funciones base. Sin embargo, en este caso, los coeficientes de dicha combinación se estiman minimizando una suma de cuadrados del error a la que se le incorpora un término de penalización asociado a la curvatura de la función, controlado por el parámetro \(\lambda\).

\(\mathrm{PENSSE}_m(y \mid c) = \mathrm{SSE}(y \mid c) + \lambda \mathrm{PEN}_m(x)\)

Este parámetro regula el equilibrio entre ajuste y suavidad. Generalmente, \(\lambda\) se selecciona mediante validación cruzada generalizada (GCV).

En esta metodología, a diferencia de la regresión splines, el número de funciones base no es el elemento más determinante; el papel central lo desempeña el valor de \(\lambda\), ya que es el que define principalmente el grado de suavidad de la función.

Justificación del suavizado

Para el suavizado de las curvas se empleará suaviado splines, utilizando bases B-splines. Esta elección se justifica a partir de la visualización de los datos, donde se puede observar en Figura 1 que los espectros de emisión no presentan un comportamiento periódico. En este contexto, los B-splines son una base adecuada.

En cuanto al número de funciones base, se realizaron pruebas con distintos valores de \(K\) (por ejemplo, valores cercanos a 70). No obstante, se observó que algunos de estos ajustes producían valores negativos en los espectros de emisión, lo cual no es consistente con la naturaleza de los datos, Por esta razón, se utilizo la función smooth.pos del paquete fda para garantizar que el ajuste no de valores negativos y \(K=40\).

Finalmente, la selección del parámetro de suavizado \(\lambda\) se llevó a cabo mediante la exploración de una grilla de valores candidatos, con el objetivo de identificar aquel que minimiza la suma de cuadrados penalizada

Código
x <- seq(275, 560, by = 0.5)
onda_340 <- as.matrix(t(sugar_fluo[1:268,1:571]))
onda_325 <- as.matrix(t(sugar_fluo[1:268,572:1142]))
onda_305 <- as.matrix(t(sugar_fluo[1:268,1143:1713]))
onda_290 <- as.matrix(t(sugar_fluo[1:268,1714:2284]))
onda_255 <- as.matrix(t(sugar_fluo[1:268,2285:2855]))
onda_240 <- as.matrix(t(sugar_fluo[1:268,2856:3426]))
onda_230  <- as.matrix(t(sugar_fluo[1:268,3427:3997]))
Código
ondas_lista <- list(
  "340" = onda_340, "325" = onda_325, "305" = onda_305, 
  "290" = onda_290, "255" = onda_255, "240" = onda_240, 
  "230" = onda_230
)

lambdas <- 10^seq(-3, 4, 0.25)
BSpl <- create.bspline.basis(rangeval = c(275, 560), nbasis = 40) 

ncurve <- dim(wvl)[2]

for (nombre in names(ondas_lista)) {
  
  data_actual <- ondas_lista[[nombre]]
  gcv_values <- numeric(length(lambdas))
  
  for(i in 1:length(lambdas)){
    Wfd0     <- fd(matrix(0, BSpl$nbasis, ncurve), BSpl)
    WfdPar   <- fdPar(Wfd0, lambda = lambdas[i])
    Fsugar_tmp <- smooth.pos(x, data_actual, WfdPar)
      
    fs <- NULL
    for (n in seq_len(ncurve)) {
      fs <- c(fs, Fsugar_tmp$Flist[[n]]$f)
    }
      
    fsave[i] <- sum(fs)
  }
  
  mejor_lambda[nombre] <- lambdas[which.min(fsave)]
  
  cat("Mejor lambda:", mejor_lambda, "\n")
}
Tabla 1: Valores seleccionados del parámetro de suavizado
Parámetro Valor Seleccionado
\(\lambda_{1}\) 0.0056
\(\lambda_{2}\) 0.0010
\(\lambda_{3}\) 0.0010
\(\lambda_{4}\) 0.0010
\(\lambda_{5}\) 1.0000
\(\lambda_{6}\) 0.0018
\(\lambda_{7}\) 0.0018
Código
Wfd0 <- fd(matrix(0, BSpl$nbasis, ncurve), BSpl)
grid <- seq(min(x), max(x), length = 1000)
fit <- list()
fits <- array(NA, dim = c(length(grid), ncurve, 7))

for (i in 1:7) {

  WfdPar <- fdPar(Wfd0, lambda = Lambdas[i])
  
  fit[[i]] <- smooth.pos(x, wvl[, , i], WfdPar, dbglev = 1)
  
  Wfd <- fit[[i]]$Wfdobj
  
  fits[ , , i] <- exp(eval.fd(grid, Wfd))
}

4 Usando el data set, seleccione un proceso y encuentre:

Se considerará para los puntos a continuación el proceso \(Y_2\) que corresponde a las curvas espectrométricas asociadas a la producción de azúcar de la segunda longitud de onda de excitación (240nm).

4.1 Función media

Sea \(x_i\), \(i=1, \ldots, n\), una muestra de curvas o funciones ajustadas a los datos. La función media muestral en \(t\), se definen como:

\[ \bar{x}(t) = \frac{\sum_{i=1}^n x_i(t)}{n} \]

Código
grid <- seq(min(t), max(t), length = 1000)
fd.240 <- t(fits[ , , 2])

Ybar <- colMeans(fd.240)

matplot(grid, fits[ , , 2], 
        type = "l",
        col = "#CCCCFF", 
        lty = 1,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")
lines(grid, Ybar, col = "#000033", lwd = 1.5)
legend("topright", 
       legend = bquote("Función Media " ~ bar(x)(t)), 
       col = "#000033", 
       lwd = 1.5,
       bty = "n",
       cex = 0.9)
Figura 2: Curvas espectrométricas de fluorescencia y función media asociadas a la segunda longitud de onda de excitación (240nm).

En Figura 2 se presentan las curvas espectrométricas asociadas a la segunda longitud de onda de excitación. Las líneas azules representan las curvas individuales, mientras que la línea azul oscuro corresponde a la función media muestral. Se observa un aumento de la intensidad hasta aproximadamente 350 nm, seguido de un comportamiento casi constante hasta alrededor de 450 nm. A partir de este punto, la intensidad comienza a descender, lo que indica que para longitudes de onda menores y mayores la intensidad es menor.

4.2 Función media recortada al 10%

La media recortada al \(100\alpha \%\) se calcula de manera análoga a la media funcional, con la diferencia de que se excluyen las \(100\alpha \%\) funciones menos centrales.

Functional band depth

Siguiendo el enfoque propuesto por López-Pintado y Romo (2009). Sea \(x_1, \dots, x_n\) una colección de funciones reales, en el espacio \(C(I)\) de funciones reales continuas sobre el intervalo compacto \(I\). El gráfico de una función \(x\) es el subconjunto del plano \(G(x) = \{(t, x(t)) : t \in I\}\). La banda en \(\mathbb{R}^2\) delimitada por las curvas \(x_{i_1}, \dots, x_{i_k}\) es:

\[ \begin{equation}B(x_{i_1}, x_{i_2}, \dots, x_{i_k}) = \left\{ (t, y) : t \in I, \min_{r=1, \dots, k} x_{i_r}(t) \leq y \leq \max_{r=1, \dots, k} x_{i_r}(t) \right\}\end{equation} \]

Para cualquier función \(x\) en \(x_1, \dots, x_n\), la cantidad

\[ \begin{equation}S_n^{(j)}(x) = \binom{n}{j}^{-1} \sum_{1 \leq i_1 < i_2 < \dots < i_j \leq n} I\{G(x) \subset B(x_{i_1}, x_{i_2}, \dots, x_{i_j})\}, \quad j \geq 2,\end{equation} \]

expresa la proporción de bandas \(B(x_{i_1}, x_{i_2}, \dots, x_{i_j})\) determinadas por \(j\) curvas diferentes \(x_{i_1}, x_{i_2}, \dots, x_{i_j}\) que contienen el gráfico de \(x\).

Para las funciones \(x_1, \dots, x_n\), la profundidad de banda (BD) de cualquiera de estas curvas \(x\) es

\[ \begin{equation}S_{n,J}(x) = \sum_{j=2}^{J} S_n^{(j)}(x), \quad J \geq 2.\end{equation} \]

De esta manera, es posible ordenar las curvas según su nivel de centralidad, de modo que aquellas con mayores valores de profundidad de banda (BD) se consideran las más centrales dentro del conjunto de datos.

Tambien se puede introducir una definición más flexible donde \(GS_n^{(j)}(x)\) es la proporción de coordenadas de \(x\) dentro del intervalo establecido por \(j\) puntos diferentes de la muestra:

\[ \begin{equation}GS_n^{(j)}(x) =\binom{n}{j}^{-1}\sum_{1 \leq i_1 < \dots < i_j \leq n}\frac{1}{d}\sum_{k=1}^{d}I \left\{\min \{ x_{i_1}(k), \dots, x_{i_j}(k) \}\leq x(k) \leq\max \{ x_{i_1}(k), \dots, x_{i_j}(k) \}\right\}.\end{equation} \]

Definiendo este orden funcional, sean \(x_{[1]}, \dots, x_{[n]}\) los estadísticos de orden, siendo \(x_{[1]}\) la curva más profunda (la más central) y \(x_{[n]}\) la curva más alejada.

La media recortada al \(100\alpha\%\), se calcula

\[ \overline{x}_{\alpha}(t) = \sum_{i=1}^{n - \lfloor n\alpha \rfloor} \frac{x_{[i]}(t)}{n - \lfloor n\alpha \rfloor} \]

De esta forma, si se quiere encontrar la media recortada al 10%, entonces con \(\alpha = 0.1\) y \(N=268\), \(N - \lfloor N\alpha \rfloor = 242\).

Usando la función MBD del paquete depthTools se calcula esta última propuesta de profundidad de banda. Posteriormente, se presenta la visualización de las curvas junto con la media recortada.

Código
fd.240 <- t(fits[ , , 2])

grid <- seq(min(t), max(t), length = 1000)

n <- nrow(fd.240)
alpha <- 0.1
upto <- n - floor(n * alpha)

mbd <- MBD(fd.240, plotting = FALSE)
depths <- data.frame(idx = seq_along(mbd$MBD),
                     MBD = as.numeric(mbd$MBD))

depths <- depths[order(depths$MBD, decreasing = TRUE), ]
ind <- depths$idx[1:upto]

Ytrimmed <- fd.240[depths$idx,][1:upto,]

Ytrimmed_mean <- colMeans(Ytrimmed)
Tabla 2
Índice Curva MBD
79 0.4964
97 0.4922
44 0.4918
175 0.4898
103 0.4880
53 0.4876
139 0.4862
23 0.4858
181 0.4855
25 0.4853

Orden de las curvas basadas en la MBD

Con base en estos resultados, se presenta la media recortada al 10% en la Figura 3.

Código
matplot(grid, fits[ , , 2], 
        type = "l",
        col = "#CCCCFF", 
        lwd = 1.5,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")
lines(grid, Ytrimmed_mean, col = "seagreen3", lwd = 2)
lines(grid, Ybar, col = "#000033", lwd = 2, lty = "solid")
legend("topright", 
       legend = c(bquote("Función Media " ~ bar(x)(t)),
                  bquote("Función Media Recortada " ~ bar(x)[0.1](t))), 
       col = c("#000033",
               "seagreen3"), 
       lty = "solid",
       lwd = 2,
       bty = "n",
       cex = 0.9)
Figura 3: Curvas espectrométricas de fluorescencia y función media recortada asociadas a la segunda longitud de onda de excitación (240nm).

4.3 Función de varianza

Sea \(x_1, ..., x_n\) una muestra de curvas sobre un intervalo \([a,b]\). Luego, la función de varianza en \(t\) está dada por:

\[s(t) = \frac{1}{n-1}\sum_{i=1}^n (x_i (t) - \bar{x}(t))^2\]

Esta función se ilustra en Figura 4.

Código
Ybar <- colMeans(fd.240)
centered <- sweep(fd.240, 2, Ybar, "-")
centered2 <- centered^2
var_f <- colSums(centered2) / (nrow(fd.240) - 1)


plot(grid, var_f, type="l", lwd=1,
     xlab="Longitud de Onda de Excitación (nm)", ylab="Varianza", col="red", main = "Función Varianza") 
Figura 4: Función varianza para las curvas fluorescencia asociadas a la segunda longitud de onda de excitación (240nm)

4.4 Función de covarianza

La función de covarianza bivariada \(\sigma(s,t)\) especifica la covarianza entre los valores de las curvas \(x_i(s)\) y \(x_i(t)\) en los tiempos \(s\) y \(t\), respectivamente. Esta se estima mediante

\(v(s,t) = (n-1)^{-1} \sum_{i=1}^{n} [x_i(s) - \bar{x}(s)][x_i(t) - \bar{x}(t)]\)

Código
scov_mat <- t(centered) %*% centered / (nrow(fd.240) - 1)

escala_colores <- list(
  list(0, "#000033"),      
  list(0.02, "#0000FF"),   
  list(0.05, "#FFFFFF"),   
  list(0.1, "#FFFF00"),    
  list(0.3, "#FF7F00"),    
  list(0.6, "#FF0000"),    
  list(1, "#4B0082")       
)

p <- plot_ly(x = ~grid, y = ~grid, z = ~scov_mat) %>% 
  add_surface(
    colorscale = escala_colores,
    colorbar = list(title = "Covarianza/"),
    contours = list(
      z = list(
        show = TRUE, 
        usecolormap = TRUE, 
        highlightcolor = "#ff0000", 
        project = list(z = TRUE) 
      )
    )
  ) %>%
  layout(
    title = list(text = "Superficie de Covarianza (Onda 240)"),
    scene = list(
      xaxis = list(title = "Longitud de onda (nm)"),
      yaxis = list(title = "Longitud de onda (nm)"),
      zaxis = list(
        title = "Valor", 
        range = c(0,334),
        tickformat = ".0f"
      )
    )
  )
p
Figura 5: Superficie de covarianza asociada a la segunda longitud de onda de excitación (240nm).

En Figura 5 se observa que para longitudes de onda pequeñas y grandes los valores son cercanos a cero, lo que indica una menor variabilidad conjunta de las intensidades en esas regiones del espectro. Esto es consistente con el comportamiento observado en las curvas, donde las funciones tienden a concentrarse alrededor de la media en los extremos del dominio.

En contraste, en la región central del espectro, aproximadamente entre 350 y 500 nm, la covarianza aumenta considerablemente y alcanza su valor máximo cerca de 400 nm. Esto sugiere que las intensidades en esas longitudes de onda presentan una mayor variabilidad entre las muestras y además tienden a variar conjuntamente.

En consecuencia, esta región concentra la mayor parte de la variabilidad del proceso de fluorescencia, lo cual también se refleja en la mayor dispersión de las curvas alrededor de la función media.

4.5 Función mediana

La funcion mediana muestral es la curva con la mayor profundidad. Es decir, \[ \hat{m}_{n,J} = \underset{x \in \{x_1, \ldots, x_n\}}{\arg \min} MBD_n(x) \]

Esta función se ilustra en la Figura 6.

Código
mediana <- depths$idx[1]

Ymedian <- fd.240[depths$idx[1],]

matplot(grid, fits[ , , 2], 
        type = "l",
        col = "#CCCCFF", 
        lty = 1,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")
lines(grid, Ymedian, col = "tomato", lwd = 3)
lines(grid, Ybar, col = "#000033", lwd = 3)
legend("topright", 
       legend = c(bquote("Función Mediana "),
                  bquote("Función Media ")), 
       col = c("tomato",
               "#000033"), 
       lwd = 3,
       bty = "n")
Figura 6: Función mediana para las curvas espectrométricas asociadas a la segunda longitud de onda de excitación (240nm)$

4.6 Funciones cuantiles 90 y 95

Para hallar las funciones se utiliza la relación \(p=\mid 2q-1 \mid\), donde \(p\) es el porcentaje de curvas más profundas y \(q\) representa el cuantil.

4.6.1 Función cuantil 90

En este caso, se sabe que \(q=0.9\), luego \(p=0.8\). Así, la función cuantil 90 se ilustra en la Figura 7.

Código
mbd_values <- modified_band_depth(dt = fd.240)

# Con la relación p=|2q-1|
p = abs(2*0.9-1)
n = 268
k <- ceiling(p * n)   # cantidad que corresponde al 80% de curvas más profundas
orden <- order(mbd_values, decreasing = TRUE)
indices80 <- orden[1:k]
curvas80 <- fd.240[indices80,]

curva_min <- apply(curvas80, 2, min)

#Función cuantil 90
curva_max <- apply(curvas80, 2, max)

# Graficando todas las curvas
matplot(grid, fits[ , , 2], 
        type = "l",
        col = "#CCCCFF", 
        lty = 1,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")

# Graficando q1 = 0.9  
lines(grid, curva_max, col="tomato", lwd=2)
legend("topright", 
       legend = bquote("Función Cuantil 90"), 
       col = "tomato", 
       lwd = 3,
       bty = "n",
       cex = 0.9)
Figura 7: Función cuantil 90 para las curvas espectrométricas asociadas a la segunda longitud de onda de excitación (240nm)

4.6.2 Función cuantil 95

En este caso, se sabe que \(q=0.95\), luego \(p=0.9\). Así, la función cuantil 90 se ilustra en la Figura 8.

Código
# Con la relación p=|2q-1|
# Con la relación p=|2q-1|
p = abs(2*0.95-1)
k <- ceiling(p * n)   # cantidad que corresponde al 90%
indices90 <- orden[1:k]
curvas90 <- fd.240[indices90,]

curva_min <- apply(curvas90, 2, min)

#Función cuantil 95
curva_max <- apply(curvas90, 2, max)

# Graficando todas las curvas
matplot(grid, fits[ , , 2], 
        type = "l",
        col = "#CCCCFF", 
        lty = 1,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")

# Graficando q1 = 0.95 y q2 = 0.1 
lines(grid, curva_max, col="tomato", lwd=2)
legend("topright", 
       legend = bquote("Función Cuantil 95"), 
       col = "tomato", 
       lwd = 3,
       bty = "n",
       cex = 0.9)
Figura 8: Función cuantil 90 para las curvas espectrométricas asociadas a la segunda longitud de onda de excitación (240nm)$

4.7 Región central del 0.75

Sea \(y_{[i]}\) la curva de la muestra asociada con el \(i\)-ésimo valor más grande de profundidad de banda. Considerando a \(y_{[1]}, \dots, y_{[n]}\) como estadísticos de orden, siendo \(y_{[1]}\) la curva más profunda (la más central) y \(y_{[n]}\) la curva más alejada.

La región central del \(0.75\%\) de la muestra se expresa por

\[C_{0.75} = \left\{ (t,y(t)): \min_{r=1,\dots, \lceil 0.75n \rceil } y_{[r]}(t) \leq y(t) \leq \max_{r=1, \dots, \lceil 0.75n \rceil } y_{[r]}(t) \right\}\]

Partiendo de la relación \(p=|2q-1|\)

\[ \begin{equation} \begin{aligned} p &= 0.75 \end{aligned} \end{equation} \] \[ \begin{equation} \begin{aligned} 2q - 1 &= 0.75 \\ 2q &= 1.75 \\ q &= \frac{1.75}{2} \\ q &= 0.875 \end{aligned} \end{equation} \] \[ \begin{equation} \begin{aligned} -2q + 1 &= 0.75 \\ 2q &= 1 - 0.75 \\ q &= \frac{0.25}{2} \\ q &= 0.125 \end{aligned} \end{equation} \]

Usando la función MBD del paquete depthTools

Código
mbd <- MBD(fd.240, plotting = FALSE)
valores_mbd <- mbd$MBD
orden <- order(valores_mbd, decreasing = TRUE)

k <- ceiling(0.75 * length(valores_mbd))
central <- orden[1:k]

curvas_centrales <- fd.240[central, ]

lim_inf <- apply(curvas_centrales, 2, min)
lim_sup <- apply(curvas_centrales, 2, max)

matplot(grid, fits[ , , 2], type="l", col="#CCCCFF", lty=1,
        xlab="Longitud de onda (nm)",
        ylab="Intensidad")

polygon(c(grid, rev(grid)),
        c(lim_inf, rev(lim_sup)),
        col=rgb(0,0,1,0.2), border=NA)

lines(grid, lim_inf, lwd=2, col="#000033")
lines(grid, lim_sup, lwd=2, col="#000033")
Figura 9: Región central del 0.75 asociada a la segunda longitud de onda de excitación (240nm).

En la Figura 9 se observa que la región sombreada en azul representa la región central del \(75\%\), construida a partir de las curvas con mayor profundidad de banda. Las líneas negras gruesas que delimitan esta zona corresponden a la envolvente superior e inferior, definida matemáticamente como el valor mínimo y el valor máximo en cada punto \(t\) entre las \(0.75n\) curvas más profundas, excluyendo así el \(25\%\) de las curvas más periféricas

4.8 Outliers usando el boxplot functional

Para poder construir el boxplot funcional, primero se debe hallar el 50% de curvas más profundas (Rango Intercuartílico) y tener en cuenta la función mediana. Luego, para hallar los bigotes primero se encuentran los outliers, es decir, las curvas que se encuentran más allá de \(1.5\) veces el RIQ. Excluyendo estas observaciones, se encuentran los bigotes tomando las líneas verticales de los inliers, como se muestra a continuación en la Figura 10.

Código
p <- 0.5
n <- 268
region <- ceiling(n / 2)
Y <- fd.240
depths <- depths[order(depths$MBD, decreasing = T), ]
BD50 <- Y[depths$idx,][1:region, ]

Min <- apply(BD50, 2, min)
Max <- apply(BD50, 2, max)

RIQ <- Max - Min

lower <- Min - 1.5 * RIQ
upper <- Max + 1.5 * RIQ

isOut <- apply(Y, 1, function(f) any(f < lower | f > upper))

Ygood <- Y[!isOut,]

lowerWhisker <- apply(Ygood, 2, min)
upperWhisker <- apply(Ygood, 2, max)



matplot(grid, fits[ , , 2], 
        type = "l",
        col = "lightgrey", 
        lty = 1,
        xlab = "Longitud de Onda de Excitación (nm)",
        ylab = "Intensidad de Fluorescencia")

for (k in 1:sum(isOut)) {
  lines(grid, Y[which(isOut)[k],], col = "red", lty = "dashed")
}

lines(grid, Ymedian, col = "black", lwd = 3)
lines(grid, Min, col = "springgreen3", lwd = 3)
lines(grid, Max, col = "springgreen3", lwd = 3)

polygon(x = c(grid, rev(grid)),
        y = c(Max, rev(Min)),
        col = adjustcolor("springgreen", alpha.f = 0.25),
        border = NA)

lines(grid, lowerWhisker, col = "springgreen4", lwd = 3)
lines(grid, upperWhisker, col = "springgreen4", lwd = 3)
Figura 10: Boxplot funcional para las curvas espectrométricas asociadas a la segunda longitud de onda de excitación (240nm)$

Las observaciones atípicas (outliers) están marcadas de color rojo. Estas corresponden a las curvas que se muestran en la Tabla 3.

Tabla 3: Curvas detectadas como outliers usando el Boxplot funcional junto con sus profundidades
Curva MBD
131 0.0973
10 0.0677
16 0.0362
17 0.0331
14 0.0307
71 0.0296

Para una mejor visualización gráfica, se muestran únicamente las curvas outliers:

Figura 11: Observaciones outliers de las curvas espectrométricas ajustadas (color rojo) asociadas a la segunda longitud de onda de excitación (240nm)
Código
fbox <- fbplot(fits[ , , 2])

Código
fbox$outpoint #outliers
[1]  10  14  16  17  71 131

4.9 Outliers usando el boxplot funcional ajustado

En primer lugar, para hallar el valor de F adecuado, se debe estimar la estructura de covarianza de forma robusta. Para esto, se utiliza el siguiente estimador para la matriz de varianza covarianza:

\[ \operatorname{Cov}(X,Y) = \frac{\sigma_X \sigma_Y}{4} \left[ \operatorname{Var}\!\left(\frac{X}{\sigma_X} + \frac{Y}{\sigma_Y}\right) - \operatorname{Var}\!\left(\frac{X}{\sigma_X} - \frac{Y}{\sigma_Y}\right) \right] \]

donde \(\sigma\) se estima de la siguiente forma:

\[ \hat{\sigma}_Z = Q_n(Z) = d \left\{\, |z_i - z_j| \; ; \; i < j,\; i,j = 1,2,\ldots,n \right\}_{(k)} \] con \(k = \left\lfloor \frac{\binom{n}{2} + 2}{4} \right\rfloor + 1\).

Código
# Estimación de la varianza robusta

var_robusta <- function(z){
  n = length(z)
  d = 2.2191
  k = floor((choose(n,2)+2)/4) + 1
  distancia <- c()
  for (i in 1:(n-1)){
    for (j in (i+1):n){
      distancia <- c(distancia, abs(z[i] - z[j]))
    }
  }
  distancia <- sort(distancia, decreasing = FALSE)
  var_est <- d*distancia[k]
  return(var_est)
}

# Estimación de la matriz de covarianza

p <- 571
var_rob <- numeric(p)
k <- floor((choose(n,2)+2)/4) + 1

X_240 <- wvl[ , , 2]
X_240 <- t(X_240)

for(i in 1:p){
  var_rob[i] <- Qn(X_240[, i], k=k)
}

matriz_cov <- matrix(NA, nrow = p, ncol = p)
for (i in 1:p){
  matriz_cov[i,i] <- (var_rob[i])^2
  for (j in i:p){
    if(i != j){
      factor1 <- (var_rob[i] * var_rob[j]) / 4
      factor2 <- (Qn((X_240[,i] / var_rob[i]) + 
                      (X_240[,j] / var_rob[j]), k = k))^2 -
        (Qn((X_240[,i] / var_rob[i]) - 
             (X_240[,j] / var_rob[j]), k = k))^2
      matriz_cov[i,j] <- factor1 * factor2
      matriz_cov[j,i] <- matriz_cov[i,j]  
    }
  }
}

Sin embargo, la matriz estimada no es definida positiva

Código
sum(eigen(matriz_cov)$values < 0)
[1] 275

Por lo tanto, se busca una matriz definida positiva cercana a la estimación de la matriz de covarianza

Código
matriz_cov_pd <- as.matrix(nearPD(matriz_cov)$mat)

A partir de la matriz definida positiva más cercana a la estimación de la matriz de covarianza, se determina mediante simulación el valor de \(F\) tal que la proporción estimada de observaciones clasificadas como inliers por el boxplot sea aproximadamente del \(99.3\%\).

Código
F_grid <- seq(1.2, 2, by = 0.05)
v_exp <- seq(275, 560, by = 0.5)
n_obs <- 100
n_sim <- 1000
cobertura <- matrix(NA, nrow = n_sim, ncol = length(F_grid))

for (i in 1:n_sim){
  
  sim <- mvrnorm(n_obs, rep(0, 571), matriz_cov_pd)
  perfil_sim <- smooth.basis(argvals = v_exp,
                             y = t(sim),
                             fdParobj = fd_240)
  
  curvas_eval_sim <- eval.fd(v_exp, perfil_sim$fd)
  depth <- MBD(t(curvas_eval_sim))
  index <- order(depth, decreasing = TRUE)
  m <- ceiling(n_obs * 0.5)
  center <- curvas_eval_sim[, index[1:m]]
  inf <- apply(center, 1, min)
  sup <- apply(center, 1, max)
  for (j in seq_along(F_grid)){
    f <- F_grid[j]
    dist  <- f * (sup - inf)
    upper <- sup + dist
    lower <- inf - dist
    outly <- (curvas_eval_sim <= lower) | (curvas_eval_sim >= upper)
    outcol <- colSums(outly)
    cobertura[i,j] <- (n_obs - sum(outcol > 0)) / n_obs
  }
}

cobertura_media <- colMeans(cobertura)

df_cobertura <- data.frame(
  F = F_grid,
  cobertura = cobertura_media
)

Con estos resultados, se toma \(F=1.45\), pues la proporción estimada de observaciones clasificadas como inliers por el boxplot funcional es de \(0.99339\), el cual es el valor más cercano a \(0.993\). El boxplot funcional ajustado se ilustra en la Figura 12.

Código
fda::fbplot(fits[ , , 2], method ="MBD", factor = 1.45, xlab="Longitud de Onda de Excitación (nm)", ylab = "Intensidad de Fluorescencia", main = "Boxplot Funcional Ajustado")
$depth
  [1] 0.40403846 0.26147895 0.18132707 0.32547929 0.32988887 0.35538840
  [7] 0.30979904 0.26038862 0.23074979 0.06770211 0.24787272 0.33624876
 [13] 0.13697373 0.03072531 0.08466007 0.03619973 0.03313763 0.11766723
 [19] 0.07940818 0.26809168 0.17131969 0.37257577 0.48581732 0.42635961
 [25] 0.48525736 0.48083912 0.43222830 0.48477992 0.45299748 0.39081995
 [31] 0.34307329 0.38822578 0.45384521 0.44378747 0.40363274 0.43028777
 [37] 0.31075516 0.06113880 0.14992722 0.42913131 0.22135832 0.43151741
 [43] 0.43314355 0.49177701 0.17783672 0.23606893 0.32334054 0.16585444
 [49] 0.24890475 0.42290911 0.45487680 0.43930427 0.48764805 0.47946325
 [55] 0.46087361 0.27617346 0.22312790 0.39831880 0.39363665 0.36367757
 [61] 0.27616927 0.31006904 0.15099480 0.12563178 0.31951523 0.47464095
 [67] 0.43009129 0.17562245 0.33238554 0.36632355 0.02961731 0.41127089
 [73] 0.45912606 0.45123132 0.39952379 0.41487160 0.39638968 0.45368472
 [79] 0.49638146 0.34505769 0.24688596 0.46684029 0.41357024 0.38964369
 [85] 0.28363290 0.28117771 0.18493186 0.15498273 0.16348834 0.32910017
 [91] 0.17100749 0.13043865 0.41404159 0.41793326 0.36725094 0.33060942
 [97] 0.49217368 0.20633959 0.46590631 0.44254039 0.48133563 0.42540522
[103] 0.48801029 0.41395740 0.25264408 0.42733031 0.45981491 0.37096439
[109] 0.35274034 0.35932808 0.45626105 0.12758774 0.42463369 0.32318358
[115] 0.43915591 0.43205618 0.34259215 0.30826849 0.26574610 0.36850785
[121] 0.41161898 0.37466113 0.17708094 0.33681793 0.41352563 0.44310437
[127] 0.34588563 0.39135122 0.05030779 0.18332573 0.09732087 0.27578277
[133] 0.24774012 0.37748521 0.33955654 0.18898563 0.07636167 0.47399257
[139] 0.48619481 0.45924881 0.47420672 0.46093180 0.45947946 0.46638493
[145] 0.46443127 0.42631349 0.45934804 0.35655705 0.41252999 0.33308659
[151] 0.35540891 0.43169367 0.38029057 0.37240818 0.43991240 0.45498821
[157] 0.40833015 0.44166130 0.45905797 0.45215842 0.45060411 0.23827358
[163] 0.27454179 0.22469003 0.37060294 0.16995740 0.31014528 0.43206568
[169] 0.46717072 0.36213975 0.45052133 0.14385119 0.35249377 0.23987523
[175] 0.48981022 0.46984426 0.32869601 0.38190268 0.47180116 0.24929219
[181] 0.48551691 0.48036050 0.45919280 0.48441288 0.33474582 0.21848041
[187] 0.15832377 0.12109095 0.26800648 0.33060255 0.27694896 0.36284370
[193] 0.39335251 0.38938130 0.46009866 0.39866907 0.44369462 0.40695209
[199] 0.35450584 0.41197216 0.40481184 0.41407642 0.42965258 0.37828615
[205] 0.22288932 0.38400794 0.36613986 0.25385891 0.31751238 0.37376404
[211] 0.38532176 0.16802918 0.40623299 0.25351149 0.35751255 0.38207295
[217] 0.38164542 0.17060221 0.42041607 0.46104841 0.46954984 0.25363827
[223] 0.32341590 0.14913578 0.14783912 0.27661624 0.30153351 0.43406004
[229] 0.43778534 0.43570255 0.44291486 0.39813684 0.24463615 0.23840125
[235] 0.41003069 0.16725938 0.20430292 0.31900470 0.21663956 0.37876578
[241] 0.26196361 0.29923271 0.39969137 0.45075538 0.42434971 0.37940360
[247] 0.32960506 0.20622914 0.23003650 0.33952250 0.34844972 0.36165051
[253] 0.37652133 0.16126430 0.24615892 0.34008888 0.37772100 0.39357907
[259] 0.37627883 0.34947370 0.32148275 0.28038007 0.32909827 0.28325569
[265] 0.42840567 0.41974420 0.35121237 0.12893879

$outpoint
[1]  10  14  16  17  71 131

$medcurve
[1] 79
Figura 12: Boxplot funcional ajustado

4.10 Outliers usando el FOM y el mapa de calor

De acuerdo a lo propuesto por Rousseeuw, Raymaekers, y Hubert (2018) se presenta el concepto de atipicidad.

Stahel–Donoho Outlyingness (SDO)

En un caso univariado, el SDO de un punto \(y\) respecto a una muestra \(Y = \{y_1, y_2, \dots, y_n\}\) se define como:

\[ \begin{equation}\text{SDO}(y; Y) = \frac{|y - \text{med}(Y)|}{\text{MAD}(Y)}\end{equation} \]

Donde el denominador es la Desviación Absoluta de la Mediana (MAD):

\[ \begin{equation}\text{MAD}(Y) = \frac{\text{med}_i \left( |y_i - \text{med}_j(y_j)| \right)}{\Phi^{-1}(0.75)}\end{equation} \]

Una limitación del SDO es que asume implícitamente que los datos no atípicos (inliers) siguen una distribución aproximadamente simétrica. Sin embargo, cuando la distribución de los inliers es asimétrica, el uso del MAD como única medida de escala no logra capturar dicha asimetría. Por ejemplo, si los datos provienen de una distribución sesgada hacia la derecha, algunos inliers ubicados en la cola derecha pueden presentar valores elevados de outlyingness y ser considerados atípicos, mientras que verdaderos outliers ubicados en la cola izquierda podrían no ser detectados adecuadamente.

Directional outlyingness (DO)

Sea \(y_1 \leq y_2 \leq \dots \leq y_n\) una muestra univariada, se contruye dos muestra de tamaño \(h = \left\lfloor \frac{n+1}{2} \right\rfloor\)

  • Si \(h\) es par entonces se toma \(Y_a = \{y_{h+1}, \dots ,y_n \}\) y \(Y_b = \{y_1, \dots y_h\}\)

  • Si \(h\) es impar entonces se toma \(Y_a = \{y_{h}, \dots ,y_n \}\) y \(Y_b = \{y_1, \dots y_h\}\)

Inicialmente, se calculan los siguientes estimadores de dispersión.

\[ \begin{equation} s_{o,a}(Y) = \frac{\text{med}(Z_a)}{\Phi^{-1}(0.75)} \quad \text{y} \quad s_{o,b}(Y) = \frac{\text{med}(Z_b)}{\Phi^{-1}(0.75)} \end{equation} \] Donde \(Z_a = Y_a - \text{med}(Y)\) y \(Z_b = Y_b - \text{med}(Y)\).

Luego se calculan las escalas finales \(s_a\) y \(s_b\) utilizando la función \(\rho_c\) de Huber

\[ \begin{equation}s_a(Y) = s_{o,a}(Y) \sqrt{ \frac{1}{2\alpha h} \sum_{z_i \in Z_a} \rho_c \left( \frac{z_i}{s_{o,a}(Y)} \right) }\end{equation} \]

\[ \begin{equation}s_b(Y) = s_{o,b}(Y) \sqrt{ \frac{1}{2\alpha h} \sum_{z_i \in Z_b} \rho_c \left( \frac{z_i}{s_{o,b}(Y)} \right) }\end{equation} \]

\[ \alpha = \int_{0}^{\infty} \rho_c(x) d\Phi(x) \qquad \text{y} \qquad \rho_c(t) = \left( \frac{t}{c} \right)^2 \mathbb{I}_{[-c,c]} + \mathbb{I}_{(-\infty, -c) \cup (c, \infty)} \]

Asi, el DO de un punto \(y\) respecto a una muestra \(Y = \{y_1, y_2, \dots, y_n\}\) se define como:

\[ \begin{equation}\text{DO}(y; Y) = \begin{cases} \frac{y - \text{med}(Y)}{s_a(Y)} & \text{si } y \geq \text{med}(Y) \\\frac{\text{med}(Y) - y}{s_b(Y)} & \text{si } y \leq \text{med}(Y)\end{cases}\end{equation} \]

El MDO se define mediante proyecciones

\[ \text{MDO}(\mathbf{y}; \mathbf{Y}) = \sup_{\mathbf{v} \in \mathbb{R}^d} \text{DO}(\mathbf{y}^T \mathbf{v}; \mathbf{Y}^T \mathbf{v}) \]

Functional Directional Outlyingness

Dado un conjunto de datos funcionales como \(\mathbf{Y} = \{Y_1, Y_2, \dots, Y_n\}\), donde cada \(Y_i\) es una función. Tal que estas funciones se observan en un conjunto discreto de puntos de su dominio \(\{t_1, \dots, t_T\}\).

Para define la medida de alejamiento funcional direccional (fDO) de \(X\) respecto a la muestra \(\mathbf{Y}\) como un promedio ponderado de los alejamientos en cada punto.

\[ \text{fDO}(X; \mathbf{Y}) = \sum_{j=1}^{T} \text{DO}(X(t_j); \mathbf{Y}(t_j)) W(t_j) \]

Donde \(W(\cdot)\) es una función de pesos para la cual:

\[ \sum_{j=1}^{T} W(t_j) = 1 \]

The Functional Outlier Map

El FOM se define como el diagrama de dispersión de que permite identificar de forma simultánea tanto la atipicidad de cada función como la variabilidad de dicho atipicidad a lo largo del dominio: \[(\text{fDO}(Y_i; \mathbf{Y}), \text{vDO}(Y_i; \mathbf{Y}))\]

La ecuación que mide la variabilidad de la atipicidad funcional es:

\[ \begin{equation}\text{vDO}(Y_i; \mathbf{Y}) = \frac{\text{stdev}_j (\text{DO}(Y_i(t_j); \mathbf{Y}(t_j)))}{1 + \text{fDO}(Y_i; \mathbf{Y})}\end{equation} \]

Para señalar valores atípicos se define combined functional outlyingness (CFO de una función Yi como

\[ \begin{equation}\begin{array}{ll}\text{CFO}_i & = \text{CFO}(Y_i; \mathbf{Y}) \\\\& = \sqrt{(\text{fDO}_i / \text{med}(\text{fDO}))^2 + (\text{vDO}_i / \text{med}(\text{vDO}))^2}\end{array}\end{equation} \]

Para determinar un cutoff para \(\text{CFO}\), para cada \(i = 1, \dots, n\), se calcula: \[ LCFO_i = \log(0.1 + \text{CFO}_i) \]Se considera la función \(Y_i\) como atípica si se cumple que:

\[ \begin{equation}\frac{LCFO_i - \text{med}(LCFO)}{\text{MAD}(LCFO)} > \Phi^{-1}(0.995)\end{equation} \]

Usando la funcion fOutl del paquete mrfDepth se calculan las atipicidades direccionales univariadas y con la funcion fom el Functional Outlier Map.

Código
array_240 <- array(fd.240, dim = c(268, 571,1))
DO_240 <-  fOutl(x = aperm(array_240, c(2, 1, 3)), alpha = 0,type = "fDO", distOptions = list(rmZeroes = TRUE,
                                                      maxRatio = 3),
                             diagnostic = TRUE)

fom(DO_240, cutoff = TRUE)
Figura 13: FOM asociado a la segunda longitud de onda de excitación (240nm).

Como se puede observar en Figura 13, seis puntos son identificados como atípicos, cuando el valor del fDO es alto y la medida de variación de la atipicidad (vFO) es pequeña, esto sugiere que la función presenta un nivel elevado de atipicidad que se mantiene de manera relativamente constante a lo largo del dominio.

Por otro lado, los puntos que se encuentran dentro de la región representada por una elipse, corresponden a funciones que, si bien pueden presentar valores ligeramente mayores o menores de atipicidad, no muestran una variabilidad elevada en esta medida. Esto sugiere que dichas funciones no se apartan significativamente del comportamiento general del conjunto, o bien que las posibles desviaciones corresponden a atipicidades de carácter más local.

Finalmente, dado que la implementación del FOM proporcionada por el paquete utilizado no permite identificar directamente los índices de las observaciones atípicas, se procederá a construir el FOM desde cero para identificar las observaciones que pueden considerarse atípicas dentro del conjunto de datos.

Código
fDO = function(DO,weights=NULL){
  if(is.null(weights)){
    weights=apply(DO,MARGIN = 2:(length(dim(DO))),FUN = function(y) 
      (prod(is.na(y))==0)) 
  }
  weights = weights/sum(weights,na.rm = TRUE) 
  fDOvalues = apply(DO,1,FUN= function(y) sum(weights*y,na.rm=TRUE))
  return(fDOvalues)
}

fdo240 <- fDO(DO_240$crossDistsX)
desv240 <- apply(DO_240$crossDistsX, 1, sd)

vDO240 <- desv240/(1+fdo240)
CFO240 <- sqrt(( fdo240/median(fdo240))^2 + (vDO240/median(vDO240))^2)

LCFO240 <- log(0.1 + CFO240)
z_LCFO240 <- (LCFO240 - median(LCFO240)) / mad(LCFO240)

umbral <- qnorm(0.995)
es_outlier_cfo <- z_LCFO240 > umbral
indices_a_marcar <- which(es_outlier_cfo)
x_grid <- seq(0, max(fdo240) * 1.2, length.out = 200)
y_grid <- seq(0, max(vDO240) * 1.2, length.out = 200)
grid <- expand.grid(x = x_grid, y = y_grid)

norm_grid_x <- grid$x / median(fdo240)
norm_grid_y <- grid$y / median(vDO240)
CFO_grid <- sqrt(norm_grid_x^2 + norm_grid_y^2)
LCFO_grid <- log(0.1 + CFO_grid)
z_LCFO_grid <- (LCFO_grid - median(LCFO240)) / mad(LCFO240)

z_matrix <- matrix(z_LCFO_grid, nrow = length(x_grid))
contorno <- contourLines(x_grid, y_grid, z_matrix, levels = umbral)

colores_puntos <- ifelse(es_outlier_cfo, "red", "black")

plot(fdo240, vDO240,
     col = colores_puntos,
     pch = 19,              
     cex = 0.8,           
     xlab = "fDO",
     ylab = "sd(fDO) / (1+fDO)", 
     xlim = c(0, max(fdo240) * 1.2), 
     ylim = c(0, max(vDO240) * 1.2),
     axes = TRUE
)

if(length(contorno) > 0) {
  lines(contorno[[1]]$x, contorno[[1]]$y, lty = 2, lwd = 2, col = "black")
}

text(fdo240[indices_a_marcar], vDO240[indices_a_marcar],
     labels = indices_a_marcar,
     cex = 0.5, col = "red", pos = 3, offset = 0.5)
Figura 14: FOM con atípicos asociado a la segunda longitud de onda de excitación (240nm).

Como se puede observar en Figura 14, se conserva la misma estructura obtenida previamente al utilizar el paquete, lo que confirma la consistencia de los resultados. A partir de esta construcción, es posible identificar explícitamente las observaciones atípicas, las cuales corresponden a las funciones 71, 10, 13, 14, 16, 129 y 131.

Finalmente, se construira el mapa de calor de los DO. Este análisis permitirá examinar cómo se distribuye la atipicidad a lo largo del dominio. En particular, dado que se cuenta con 571 mediciones de longitud de onda, el mapa de calor permitirá identificar las regiones del dominio donde los valores de DO son mayores, así como las observaciones que concentran estos niveles elevados de atipicidad direccional.

Código
DO <- DO_240$crossDistsX
rownames(DO) <- c(1:268)
colnames(DO) <- x
cap <- quantile(DO, 0.99)
DO[DO > cap] <- cap

df <- as.data.frame(as.table(DO))
colnames(df) <- c("Observacion", "Longitud", "DO")

df$Longitud <- as.numeric(as.character(df$Longitud))
df$Observacion <- as.numeric(as.character(df$Observacion))

ggplot(df, aes(x = Longitud, y = Observacion, fill = DO)) +
  geom_tile() +
  scale_fill_gradient(low = "white", high = "darkred") +
  scale_x_continuous(
    breaks = seq(min(df$Longitud), max(df$Longitud), by = 50)
  ) +
  scale_y_continuous(
    breaks = seq(min(df$Observacion), max(df$Observacion), by = 50)
  ) +
  theme_classic()
Figura 15: Mapa de calor para DO asociado a la segunda longitud de onda de excitación (240nm).

En Figura 15 se observan varias lineas horizontales con valores elevados, lo que indica la presencia de observaciones que mantienen niveles altos de atipicidad direccional a lo largo de gran parte del dominio de longitudes de onda. Este patrón sugiere que la atipicidad identificada no se concentra únicamente en regiones específicas, sino que en algunos casos corresponde a atipicidades de carácter más global, donde ciertas funciones se mantienen alejadas del comportamiento central en múltiples longitudes de onda. Asimismo, se identifican zonas con valores de DO relativamente altos en distintos intervalos de observaciones, lo cual podría corresponder a algunas de las funciones previamente detectadas como atípicas mediante el FOM, tales como las observaciones 14, 16, 71, 129 y 131.

4.11 Comparación de boxplots para la curva 230

Código
# Estimación de la varianza robusta

var_robusta <- function(z){
  n = length(z)
  d = 2.2191
  k = floor((choose(n,2)+2)/4) + 1
  distancia <- c()
  for (i in 1:(n-1)){
    for (j in (i+1):n){
      distancia <- c(distancia, abs(z[i] - z[j]))
    }
  }
  distancia <- sort(distancia, decreasing = FALSE)
  var_est <- d*distancia[k]
  return(var_est)
}

# Estimación de la matriz de covarianza
p <- 571
var_rob <- numeric(p)
k <- floor((choose(n,2)+2)/4) + 1

X_230 <- wvl[ , , 1]
X_230 <- t(X_230)

for(i in 1:p){
  var_rob[i] <- Qn(X_230[, i], k=k)
}

matriz_cov <- matrix(NA, nrow = p, ncol = p)
for (i in 1:p){
  matriz_cov[i,i] <- (var_rob[i])^2
  for (j in i:p){
    if(i != j){
      factor1 <- (var_rob[i] * var_rob[j]) / 4
      factor2 <- (Qn((X_230[,i] / var_rob[i]) + 
                       (X_230[,j] / var_rob[j]), k = k))^2 -
        (Qn((X_230[,i] / var_rob[i]) - 
              (X_230[,j] / var_rob[j]), k = k))^2
      matriz_cov[i,j] <- factor1 * factor2
      matriz_cov[j,i] <- matriz_cov[i,j]  
    }
  }
}

# La matriz estimada no es definida positiva
eigen(matriz_cov)$values
  [1]  2.691763e+05  3.178212e+04  1.328831e+04  1.900832e+03  9.618884e+02
  [6]  4.434061e+02  2.881716e+02  2.492422e+02  2.393163e+02  2.117750e+02
 [11]  1.974263e+02  1.669260e+02  1.592655e+02  1.565061e+02  1.385989e+02
 [16]  1.348812e+02  1.259126e+02  1.236031e+02  1.198630e+02  1.135839e+02
 [21]  1.075079e+02  1.067906e+02  1.042876e+02  1.000651e+02  9.563649e+01
 [26]  9.257126e+01  9.137729e+01  8.747672e+01  8.324460e+01  8.168292e+01
 [31]  7.993105e+01  7.734975e+01  7.473811e+01  7.363400e+01  7.193905e+01
 [36]  7.003616e+01  6.935645e+01  6.785276e+01  6.535716e+01  6.269481e+01
 [41]  6.131975e+01  5.950942e+01  5.849225e+01  5.788403e+01  5.666884e+01
 [46]  5.511871e+01  5.458509e+01  5.288996e+01  5.198297e+01  5.107804e+01
 [51]  5.097813e+01  4.973675e+01  4.924128e+01  4.863291e+01  4.758138e+01
 [56]  4.678439e+01  4.549961e+01  4.469908e+01  4.376240e+01  4.345885e+01
 [61]  4.261051e+01  4.226401e+01  4.127019e+01  4.112387e+01  4.004671e+01
 [66]  3.933417e+01  3.823345e+01  3.784252e+01  3.722668e+01  3.622782e+01
 [71]  3.598546e+01  3.517104e+01  3.458432e+01  3.406366e+01  3.319940e+01
 [76]  3.294045e+01  3.237177e+01  3.171712e+01  3.145120e+01  3.062992e+01
 [81]  3.041863e+01  3.003268e+01  2.980909e+01  2.911828e+01  2.871039e+01
 [86]  2.841513e+01  2.770936e+01  2.732221e+01  2.706098e+01  2.684146e+01
 [91]  2.654336e+01  2.603046e+01  2.591327e+01  2.523731e+01  2.476806e+01
 [96]  2.436387e+01  2.395912e+01  2.390004e+01  2.343593e+01  2.298711e+01
[101]  2.270721e+01  2.251590e+01  2.206790e+01  2.181383e+01  2.138128e+01
[106]  2.129640e+01  2.050623e+01  2.034580e+01  2.006134e+01  2.003363e+01
[111]  1.982135e+01  1.950519e+01  1.891757e+01  1.867298e+01  1.861027e+01
[116]  1.797032e+01  1.790200e+01  1.763471e+01  1.736363e+01  1.723312e+01
[121]  1.687810e+01  1.677073e+01  1.659865e+01  1.620535e+01  1.616834e+01
[126]  1.588070e+01  1.571643e+01  1.562235e+01  1.527401e+01  1.508269e+01
[131]  1.471933e+01  1.461537e+01  1.454755e+01  1.418389e+01  1.395498e+01
[136]  1.385304e+01  1.361426e+01  1.326190e+01  1.320120e+01  1.291211e+01
[141]  1.289208e+01  1.271202e+01  1.255449e+01  1.248505e+01  1.239363e+01
[146]  1.209079e+01  1.172368e+01  1.150670e+01  1.148231e+01  1.141468e+01
[151]  1.123947e+01  1.099461e+01  1.084275e+01  1.067858e+01  1.046632e+01
[156]  1.026364e+01  1.013432e+01  9.955350e+00  9.741515e+00  9.566334e+00
[161]  9.446725e+00  9.287305e+00  9.008792e+00  8.974547e+00  8.805842e+00
[166]  8.686658e+00  8.567023e+00  8.469058e+00  8.309836e+00  8.273778e+00
[171]  8.137901e+00  8.000433e+00  7.768217e+00  7.709512e+00  7.534796e+00
[176]  7.479943e+00  7.402258e+00  7.311605e+00  7.180540e+00  7.047102e+00
[181]  6.957925e+00  6.809109e+00  6.714732e+00  6.493630e+00  6.427531e+00
[186]  6.251713e+00  6.222304e+00  6.133952e+00  6.069726e+00  5.943905e+00
[191]  5.898982e+00  5.787327e+00  5.611380e+00  5.478191e+00  5.316196e+00
[196]  5.183032e+00  5.076116e+00  5.000715e+00  4.885177e+00  4.816976e+00
[201]  4.700272e+00  4.644666e+00  4.483984e+00  4.463172e+00  4.337679e+00
[206]  4.226514e+00  4.046841e+00  4.022293e+00  3.932393e+00  3.854187e+00
[211]  3.799504e+00  3.673279e+00  3.602629e+00  3.482078e+00  3.338582e+00
[216]  3.254150e+00  3.242209e+00  3.154545e+00  3.112619e+00  3.022013e+00
[221]  2.887482e+00  2.870210e+00  2.723815e+00  2.691166e+00  2.585005e+00
[226]  2.561995e+00  2.475062e+00  2.430867e+00  2.380637e+00  2.297642e+00
[231]  2.182860e+00  2.145094e+00  2.123404e+00  2.087416e+00  2.006714e+00
[236]  1.973100e+00  1.946731e+00  1.858158e+00  1.843454e+00  1.797342e+00
[241]  1.748750e+00  1.681023e+00  1.578041e+00  1.547968e+00  1.476297e+00
[246]  1.450337e+00  1.362610e+00  1.342533e+00  1.252457e+00  1.228907e+00
[251]  1.200563e+00  1.179580e+00  1.127522e+00  1.061226e+00  1.011474e+00
[256]  1.000769e+00  9.706859e-01  8.855378e-01  8.589316e-01  8.059626e-01
[261]  7.991636e-01  7.819150e-01  7.447901e-01  7.104080e-01  6.680900e-01
[266]  6.447309e-01  6.155321e-01  5.987616e-01  5.892258e-01  5.317617e-01
[271]  5.128790e-01  4.946674e-01  4.792879e-01  4.463662e-01  3.935738e-01
[276]  3.809109e-01  3.628768e-01  3.280595e-01  3.214276e-01  2.747575e-01
[281]  2.632817e-01  2.466401e-01  2.102043e-01  1.930176e-01  1.899555e-01
[286]  1.587470e-01  1.404369e-01  1.137895e-01  9.773830e-02  8.535388e-02
[291]  6.243711e-02  4.483098e-02  2.908405e-02  6.622554e-03  1.353634e-03
[296] -4.803220e-03 -2.114651e-02 -2.407069e-02 -4.141734e-02 -5.397553e-02
[301] -7.596760e-02 -8.650688e-02 -1.116202e-01 -1.258272e-01 -1.460953e-01
[306] -1.594849e-01 -2.107192e-01 -2.188330e-01 -2.240982e-01 -2.399991e-01
[311] -2.708478e-01 -2.962336e-01 -3.131039e-01 -3.326547e-01 -3.658935e-01
[316] -3.870067e-01 -4.226265e-01 -4.495042e-01 -4.616535e-01 -4.878890e-01
[321] -5.339424e-01 -5.807252e-01 -5.901739e-01 -6.281815e-01 -6.859208e-01
[326] -6.963902e-01 -7.275800e-01 -7.724008e-01 -7.906397e-01 -8.109869e-01
[331] -8.786244e-01 -9.056402e-01 -9.318300e-01 -9.621008e-01 -1.021107e+00
[336] -1.059520e+00 -1.103274e+00 -1.135861e+00 -1.199941e+00 -1.271837e+00
[341] -1.325068e+00 -1.391700e+00 -1.474556e+00 -1.495298e+00 -1.522289e+00
[346] -1.589391e+00 -1.596606e+00 -1.668335e+00 -1.728630e+00 -1.820260e+00
[351] -1.832574e+00 -1.879278e+00 -1.965649e+00 -2.012194e+00 -2.034429e+00
[356] -2.161621e+00 -2.260454e+00 -2.277546e+00 -2.302846e+00 -2.405006e+00
[361] -2.496173e+00 -2.565166e+00 -2.606620e+00 -2.649158e+00 -2.725686e+00
[366] -2.773117e+00 -2.851430e+00 -2.983463e+00 -3.082891e+00 -3.158936e+00
[371] -3.208929e+00 -3.273256e+00 -3.357602e+00 -3.390404e+00 -3.551490e+00
[376] -3.610558e+00 -3.762284e+00 -3.811264e+00 -3.928626e+00 -4.134819e+00
[381] -4.153735e+00 -4.244433e+00 -4.309696e+00 -4.399161e+00 -4.487826e+00
[386] -4.680218e+00 -4.724972e+00 -4.795551e+00 -4.922860e+00 -5.047367e+00
[391] -5.153972e+00 -5.207204e+00 -5.326704e+00 -5.405288e+00 -5.523220e+00
[396] -5.577012e+00 -5.686285e+00 -5.748685e+00 -5.931178e+00 -6.020957e+00
[401] -6.254688e+00 -6.376336e+00 -6.451286e+00 -6.515687e+00 -6.601908e+00
[406] -6.774492e+00 -6.948131e+00 -7.038715e+00 -7.081564e+00 -7.184160e+00
[411] -7.266335e+00 -7.356043e+00 -7.511351e+00 -7.566414e+00 -7.656294e+00
[416] -7.844929e+00 -7.929482e+00 -8.202434e+00 -8.285340e+00 -8.392939e+00
[421] -8.691679e+00 -8.795475e+00 -9.003836e+00 -9.065952e+00 -9.193695e+00
[426] -9.269169e+00 -9.508461e+00 -9.643071e+00 -9.753972e+00 -9.929242e+00
[431] -1.010394e+01 -1.037691e+01 -1.040368e+01 -1.047795e+01 -1.077467e+01
[436] -1.094161e+01 -1.109947e+01 -1.120073e+01 -1.133519e+01 -1.146656e+01
[441] -1.168182e+01 -1.183403e+01 -1.200403e+01 -1.206419e+01 -1.233308e+01
[446] -1.244283e+01 -1.254320e+01 -1.265838e+01 -1.297905e+01 -1.322575e+01
[451] -1.330699e+01 -1.344493e+01 -1.387412e+01 -1.394626e+01 -1.405804e+01
[456] -1.430614e+01 -1.458773e+01 -1.476945e+01 -1.492099e+01 -1.533212e+01
[461] -1.538428e+01 -1.555342e+01 -1.583018e+01 -1.604718e+01 -1.637102e+01
[466] -1.659068e+01 -1.666955e+01 -1.687982e+01 -1.720108e+01 -1.751809e+01
[471] -1.769386e+01 -1.813987e+01 -1.837349e+01 -1.852919e+01 -1.856441e+01
[476] -1.882633e+01 -1.921317e+01 -1.928580e+01 -1.995929e+01 -2.022657e+01
[481] -2.092253e+01 -2.116837e+01 -2.125763e+01 -2.144522e+01 -2.172819e+01
[486] -2.221101e+01 -2.268607e+01 -2.290209e+01 -2.316125e+01 -2.387140e+01
[491] -2.413617e+01 -2.444669e+01 -2.466299e+01 -2.469200e+01 -2.559667e+01
[496] -2.609279e+01 -2.643327e+01 -2.688067e+01 -2.793743e+01 -2.807088e+01
[501] -2.830901e+01 -2.885198e+01 -2.929562e+01 -2.980239e+01 -3.030854e+01
[506] -3.089425e+01 -3.143463e+01 -3.176963e+01 -3.226013e+01 -3.260728e+01
[511] -3.297913e+01 -3.353634e+01 -3.456968e+01 -3.477149e+01 -3.579585e+01
[516] -3.620030e+01 -3.690034e+01 -3.710920e+01 -3.845041e+01 -3.978827e+01
[521] -4.014853e+01 -4.068049e+01 -4.236712e+01 -4.276616e+01 -4.340488e+01
[526] -4.431904e+01 -4.448413e+01 -4.482665e+01 -4.562301e+01 -4.693644e+01
[531] -4.960389e+01 -5.049542e+01 -5.147170e+01 -5.382519e+01 -5.449160e+01
[536] -5.616637e+01 -5.721844e+01 -5.782153e+01 -5.982454e+01 -6.247773e+01
[541] -6.315249e+01 -6.442398e+01 -6.490389e+01 -6.967355e+01 -7.091508e+01
[546] -7.388693e+01 -7.589939e+01 -7.901443e+01 -7.970555e+01 -8.309792e+01
[551] -8.496801e+01 -8.564098e+01 -9.086040e+01 -9.091003e+01 -9.896233e+01
[556] -1.034372e+02 -1.048119e+02 -1.084592e+02 -1.094552e+02 -1.135267e+02
[561] -1.239286e+02 -1.270349e+02 -1.448599e+02 -1.588658e+02 -1.659944e+02
[566] -1.757041e+02 -1.932240e+02 -2.171638e+02 -2.669563e+02 -4.644865e+02
[571] -9.327630e+02
Código
# Matriz definida positiva
matriz_cov_pd <- as.matrix(nearPD(matriz_cov)$mat)


F_grid <- seq(1.2, 2, by = 0.05)
v_exp <- seq(275, 560, by = 0.5)
n_obs <- 100
n_sim <- 1000
cobertura <- matrix(NA, nrow = n_sim, ncol = length(F_grid))

for (i in 1:n_sim){
  
  sim <- mvrnorm(n_obs, rep(0, 571), matriz_cov_pd)
  perfil_sim <- smooth.basis(argvals = v_exp,
                             y = t(sim),
                             fdParobj = fdParobj)
  
  curvas_eval_sim <- eval.fd(v_exp, perfil_sim$fd)
  depth <- MBD(t(curvas_eval_sim))
  index <- order(depth, decreasing = TRUE)
  m <- ceiling(n_obs * 0.5)
  center <- curvas_eval_sim[, index[1:m]]
  inf <- apply(center, 1, min)
  sup <- apply(center, 1, max)
  for (j in seq_along(F_grid)){
    f <- F_grid[j]
    dist  <- f * (sup - inf)
    upper <- sup + dist
    lower <- inf - dist
    outly <- (curvas_eval_sim <= lower) | (curvas_eval_sim >= upper)
    outcol <- colSums(outly)
    cobertura[i,j] <- (n_obs - sum(outcol > 0)) / n_obs
  }
}

cobertura_media <- colMeans(cobertura)

df_cobertura_230 <- data.frame(
  F = F_grid,
  cobertura = cobertura_media
)
Código
df_cobertura_230 <- read.csv("C:\\Users\\sofia\\OneDrive\\Documentos\\2026-1\\FDA\\df_cobertura_230.csv")
df_cobertura_230
      F cobertura
1  1.20   0.98563
2  1.25   0.98836
3  1.30   0.99060
4  1.35   0.99258
5  1.40   0.99414
6  1.45   0.99522
7  1.50   0.99622
8  1.55   0.99710
9  1.60   0.99760
10 1.65   0.99810
11 1.70   0.99848
12 1.75   0.99879
13 1.80   0.99904
14 1.85   0.99927
15 1.90   0.99944
16 1.95   0.99959
17 2.00   0.99965
Código
# Se selecciona F = 1.35
Código
par(mfrow = c(1,2))

# Boxplot funcional
fda::fbplot(fits[ , , 2],
            method = "MBD",
            main = "Boxplot funcional")
$depth
  [1] 0.40403846 0.26147895 0.18132707 0.32547929 0.32988887 0.35538840
  [7] 0.30979904 0.26038862 0.23074979 0.06770211 0.24787272 0.33624876
 [13] 0.13697373 0.03072531 0.08466007 0.03619973 0.03313763 0.11766723
 [19] 0.07940818 0.26809168 0.17131969 0.37257577 0.48581732 0.42635961
 [25] 0.48525736 0.48083912 0.43222830 0.48477992 0.45299748 0.39081995
 [31] 0.34307329 0.38822578 0.45384521 0.44378747 0.40363274 0.43028777
 [37] 0.31075516 0.06113880 0.14992722 0.42913131 0.22135832 0.43151741
 [43] 0.43314355 0.49177701 0.17783672 0.23606893 0.32334054 0.16585444
 [49] 0.24890475 0.42290911 0.45487680 0.43930427 0.48764805 0.47946325
 [55] 0.46087361 0.27617346 0.22312790 0.39831880 0.39363665 0.36367757
 [61] 0.27616927 0.31006904 0.15099480 0.12563178 0.31951523 0.47464095
 [67] 0.43009129 0.17562245 0.33238554 0.36632355 0.02961731 0.41127089
 [73] 0.45912606 0.45123132 0.39952379 0.41487160 0.39638968 0.45368472
 [79] 0.49638146 0.34505769 0.24688596 0.46684029 0.41357024 0.38964369
 [85] 0.28363290 0.28117771 0.18493186 0.15498273 0.16348834 0.32910017
 [91] 0.17100749 0.13043865 0.41404159 0.41793326 0.36725094 0.33060942
 [97] 0.49217368 0.20633959 0.46590631 0.44254039 0.48133563 0.42540522
[103] 0.48801029 0.41395740 0.25264408 0.42733031 0.45981491 0.37096439
[109] 0.35274034 0.35932808 0.45626105 0.12758774 0.42463369 0.32318358
[115] 0.43915591 0.43205618 0.34259215 0.30826849 0.26574610 0.36850785
[121] 0.41161898 0.37466113 0.17708094 0.33681793 0.41352563 0.44310437
[127] 0.34588563 0.39135122 0.05030779 0.18332573 0.09732087 0.27578277
[133] 0.24774012 0.37748521 0.33955654 0.18898563 0.07636167 0.47399257
[139] 0.48619481 0.45924881 0.47420672 0.46093180 0.45947946 0.46638493
[145] 0.46443127 0.42631349 0.45934804 0.35655705 0.41252999 0.33308659
[151] 0.35540891 0.43169367 0.38029057 0.37240818 0.43991240 0.45498821
[157] 0.40833015 0.44166130 0.45905797 0.45215842 0.45060411 0.23827358
[163] 0.27454179 0.22469003 0.37060294 0.16995740 0.31014528 0.43206568
[169] 0.46717072 0.36213975 0.45052133 0.14385119 0.35249377 0.23987523
[175] 0.48981022 0.46984426 0.32869601 0.38190268 0.47180116 0.24929219
[181] 0.48551691 0.48036050 0.45919280 0.48441288 0.33474582 0.21848041
[187] 0.15832377 0.12109095 0.26800648 0.33060255 0.27694896 0.36284370
[193] 0.39335251 0.38938130 0.46009866 0.39866907 0.44369462 0.40695209
[199] 0.35450584 0.41197216 0.40481184 0.41407642 0.42965258 0.37828615
[205] 0.22288932 0.38400794 0.36613986 0.25385891 0.31751238 0.37376404
[211] 0.38532176 0.16802918 0.40623299 0.25351149 0.35751255 0.38207295
[217] 0.38164542 0.17060221 0.42041607 0.46104841 0.46954984 0.25363827
[223] 0.32341590 0.14913578 0.14783912 0.27661624 0.30153351 0.43406004
[229] 0.43778534 0.43570255 0.44291486 0.39813684 0.24463615 0.23840125
[235] 0.41003069 0.16725938 0.20430292 0.31900470 0.21663956 0.37876578
[241] 0.26196361 0.29923271 0.39969137 0.45075538 0.42434971 0.37940360
[247] 0.32960506 0.20622914 0.23003650 0.33952250 0.34844972 0.36165051
[253] 0.37652133 0.16126430 0.24615892 0.34008888 0.37772100 0.39357907
[259] 0.37627883 0.34947370 0.32148275 0.28038007 0.32909827 0.28325569
[265] 0.42840567 0.41974420 0.35121237 0.12893879

$outpoint
[1]  10  14  16  17  71 131

$medcurve
[1] 79
Código
# Boxplot funcional ajustado
fda::fbplot(fits[ , , 2],
            method = "MBD",
            factor = 1.35,
            main = "Boxplot funcional ajustado")

$depth
  [1] 0.40403846 0.26147895 0.18132707 0.32547929 0.32988887 0.35538840
  [7] 0.30979904 0.26038862 0.23074979 0.06770211 0.24787272 0.33624876
 [13] 0.13697373 0.03072531 0.08466007 0.03619973 0.03313763 0.11766723
 [19] 0.07940818 0.26809168 0.17131969 0.37257577 0.48581732 0.42635961
 [25] 0.48525736 0.48083912 0.43222830 0.48477992 0.45299748 0.39081995
 [31] 0.34307329 0.38822578 0.45384521 0.44378747 0.40363274 0.43028777
 [37] 0.31075516 0.06113880 0.14992722 0.42913131 0.22135832 0.43151741
 [43] 0.43314355 0.49177701 0.17783672 0.23606893 0.32334054 0.16585444
 [49] 0.24890475 0.42290911 0.45487680 0.43930427 0.48764805 0.47946325
 [55] 0.46087361 0.27617346 0.22312790 0.39831880 0.39363665 0.36367757
 [61] 0.27616927 0.31006904 0.15099480 0.12563178 0.31951523 0.47464095
 [67] 0.43009129 0.17562245 0.33238554 0.36632355 0.02961731 0.41127089
 [73] 0.45912606 0.45123132 0.39952379 0.41487160 0.39638968 0.45368472
 [79] 0.49638146 0.34505769 0.24688596 0.46684029 0.41357024 0.38964369
 [85] 0.28363290 0.28117771 0.18493186 0.15498273 0.16348834 0.32910017
 [91] 0.17100749 0.13043865 0.41404159 0.41793326 0.36725094 0.33060942
 [97] 0.49217368 0.20633959 0.46590631 0.44254039 0.48133563 0.42540522
[103] 0.48801029 0.41395740 0.25264408 0.42733031 0.45981491 0.37096439
[109] 0.35274034 0.35932808 0.45626105 0.12758774 0.42463369 0.32318358
[115] 0.43915591 0.43205618 0.34259215 0.30826849 0.26574610 0.36850785
[121] 0.41161898 0.37466113 0.17708094 0.33681793 0.41352563 0.44310437
[127] 0.34588563 0.39135122 0.05030779 0.18332573 0.09732087 0.27578277
[133] 0.24774012 0.37748521 0.33955654 0.18898563 0.07636167 0.47399257
[139] 0.48619481 0.45924881 0.47420672 0.46093180 0.45947946 0.46638493
[145] 0.46443127 0.42631349 0.45934804 0.35655705 0.41252999 0.33308659
[151] 0.35540891 0.43169367 0.38029057 0.37240818 0.43991240 0.45498821
[157] 0.40833015 0.44166130 0.45905797 0.45215842 0.45060411 0.23827358
[163] 0.27454179 0.22469003 0.37060294 0.16995740 0.31014528 0.43206568
[169] 0.46717072 0.36213975 0.45052133 0.14385119 0.35249377 0.23987523
[175] 0.48981022 0.46984426 0.32869601 0.38190268 0.47180116 0.24929219
[181] 0.48551691 0.48036050 0.45919280 0.48441288 0.33474582 0.21848041
[187] 0.15832377 0.12109095 0.26800648 0.33060255 0.27694896 0.36284370
[193] 0.39335251 0.38938130 0.46009866 0.39866907 0.44369462 0.40695209
[199] 0.35450584 0.41197216 0.40481184 0.41407642 0.42965258 0.37828615
[205] 0.22288932 0.38400794 0.36613986 0.25385891 0.31751238 0.37376404
[211] 0.38532176 0.16802918 0.40623299 0.25351149 0.35751255 0.38207295
[217] 0.38164542 0.17060221 0.42041607 0.46104841 0.46954984 0.25363827
[223] 0.32341590 0.14913578 0.14783912 0.27661624 0.30153351 0.43406004
[229] 0.43778534 0.43570255 0.44291486 0.39813684 0.24463615 0.23840125
[235] 0.41003069 0.16725938 0.20430292 0.31900470 0.21663956 0.37876578
[241] 0.26196361 0.29923271 0.39969137 0.45075538 0.42434971 0.37940360
[247] 0.32960506 0.20622914 0.23003650 0.33952250 0.34844972 0.36165051
[253] 0.37652133 0.16126430 0.24615892 0.34008888 0.37772100 0.39357907
[259] 0.37627883 0.34947370 0.32148275 0.28038007 0.32909827 0.28325569
[265] 0.42840567 0.41974420 0.35121237 0.12893879

$outpoint
[1]  10  13  14  16  17  38  71 129 131

$medcurve
[1] 79

5 Profundidad Multivariada

Para el cálculo de la profundidad multivariada para los siete procesos funcionales, se debe tener en cuenta que se tiene una muestra de tamaño \(n=268\), \(\{\mathbf{Y}_1, \mathbf{Y}_2, \ldots, \mathbf{Y}_{268} \}\) donde \(U=\mathcal{T}=[275, 560]\) y \(\boldsymbol{{Y}}_i \in \mathcal{C}(U)^7\) con \(i=1,\ldots, 268\). El cálculo de la profundidad se realiza mediante la MFHD (multivariate functional halfspace depth).

MFHD corresponde a la Multivariate Functional Halfspace Depth. La cual está dada por:

\[MFHD_N(\boldsymbol{\chi})=\sum_{j=1}^T HD(\boldsymbol{\chi}(t_j); F_{\boldsymbol{\mathcal{Y}}(t_j), N})W_j\]

donde la profundidad \(HD\) muestral está dada por:

\[HD(X(t); F_{\mathcal{Y}(t),N}) =\frac{1}{N}\min_{u \in\mathbb{R}^K,\ \|u\|=1}\#\left\{ Y_n(t),\ n = 1,\ldots,N :u'Y_n(t) \ge u'X(t) \right\}\]

y, con \(t_0 = t_1\), \(t_{T+1} = t_T\), \(W_j\) está dado por \(W_j = \int_{(t_{j-1}+t_j)/2}^{(t_j+t_{j+1})/2} w(t)\,dt\) donde \(w(t)\) es una función de pesos.

La medida de profundidad Halfspace Depth consiste en dividir el espacio en dos partes mediante un hiperplano que pasa por la observación \(\boldsymbol{X}(t)\). Para cada dirección \(u\), los datos se proyectan sobre dicha dirección y se calcula la proporción de observaciones \(Y_n(t)\) que satisfacen \(u'Y_n(t) \ge u'X(t)\). La profundidad se define como la mínima proporción obtenida al considerar todas las direcciones posibles. La idea intuitiva de esta medida de profundidad es medir cuántos datos se encuentran alrededor del valor \(\boldsymbol{X}(t)\), lo que permite determinar qué tan central es esta observación respecto al conjunto de datos.

En conclusión, la MFHD consiste en calcular la HD de un vector de funciones \(\boldsymbol{\chi}\) en la muestra punto a punto a lo largo de su dominio, con respecto a los demás vectores de curvas, y luego obtener un promedio ponderado de estas profundidades.

5.0.1 Pesos uniformes

En el caso de de pesos uniformes se toma \(w(t)=w\) para todo \(t \in U\), donde \(U\) hace referencia al intervalo donde están definidas las funciones. Como se tienen puntos \(t\) equiespaciados en el dominio, entonces los pesos están dados por \(w(t)=\frac{1}{\mid U \mid}\), donde \(\mid U \mid\) denota la longitud del intervalo \(U\).

Código
fitted.curves <- function(fd, t){
  
  fd_obj <- fd
  
  grid <- t
  
  Yt <- eval.fd(grid, fd_obj)

  Y <- t(Yt)
  
  return(list(Y = Y, Yt = Yt))
  
}
Código
YY <- fits

MFHD <- mfd(YY, type = "hdepth")

Mdepths <- data.frame(idx = 1:length(MFHD$MFDdepthX),
                      depth = MFHD$MFDdepthX)

Mdepths <- Mdepths[order(Mdepths$depth, decreasing = TRUE),]
Tabla 4
Curva MBD
184 0.0669
111 0.0621
53 0.0614
195 0.0611
34 0.0602
204 0.0568
83 0.0553
33 0.0546
192 0.0544
101 0.0537

Orden de los datos funcionales multivariados con la MFHD

5.0.2 Función de pesos sugerida por los autores

Por otro lado, los autores sugieren utilizar una función de pesos proporcional al volumen de la región de profundidad en cada instante \((t)\), Los pesos están dados por:

\[W_j = \frac{\mathrm{vol}\{D_{\alpha}(F_{\mathcal{Y}(t_j),N})\}(t_{j+1}-t_{j-1})}{\sum_{j=1}^{T} \mathrm{vol}\{D_{\alpha}(F_{\mathcal{Y}(t_j),N})\}(t_{j+1}-t_{j-1})} \]

Esta función de pesos tiene en cuenta los cambios locales en la variabilidad de la amplitud de las curvas, por lo que en los valores de \(t\) donde las curvas son muy similares, el volumen de la región de profundidad es pequeña, por lo tanto, los pesos asignados en esos puntos \(t\) también son pequeños. Por otro lado, en regiones donde la variabilidad en la amplitud es mayor, el volumen de la región de profundidad aumenta y se asignan mayores pesos.

Para este ejercicio, se tomó \(\alpha = 0.1\).

Código
pesos_mfd <- function(x, type = "hdepth", alpha = 0.1, time = NULL){
  t1 <- dim(x)[1]
  p1 <- dim(x)[3]
  if (is.null(time)) {
    time <- 1:t1
  }
    if (length(time) != 1) {
    dTime <- diff(c(time[1], time, time[t1]), lag = 2)
  } else {
    dTime <- 1
  }
  weights <- rep(0, t1)
  for (j in 1:t1) {
    if (p1 == 1) {
      xTimePoint <- matrix(x[j, , 1])
    } else {
      xTimePoint <- x[j, , , drop = TRUE]
    }
    if (alpha != 0) {
      temp <- depthContour(x = xTimePoint, alpha, type = type)
      Vert <- temp[[1]]$vertices
      if (sum(is.nan(Vert)) == 0 && nrow(Vert) == nrow(unique(Vert))) {
        if (p1 == 1) {
          vol <- max(Vert) - min(Vert)
        } else {
          vol <- try(convhulln(matrix(Vert, ncol = p1), "FA")$vol,
                     silent = TRUE)
        }
        if (is.numeric(vol)) {
          weights[j] <- vol
        }
      }
    }
  }
  weights <- weights * dTime
  weights <- weights / sum(weights)
  return(weights)
}
Código
valores_x <- seq(275, 560, by = 0.5)
BSpl <- create.bspline.basis(rangeval = c(275, 560), nbasis = 40) 
ncurve <- dim(wvl)[2]
fits2 <- array(NA, dim = c(length(valores_x), ncurve, 7))
Wfd0     <- fd(matrix(0, BSpl$nbasis, ncurve), BSpl)
for (i in 1:7) {

  WfdPar <- fdPar(Wfd0, lambda = Lambdas[i])
  
  fit[[i]] <- smooth.pos(t, wvl[, , i], WfdPar, dbglev = 1)
  
  Wfd <- fit[[i]]$Wfdobj
  
  fits2[ , , i] <- exp(eval.fd(valores_x, Wfd))
}

YY2 <- fits2
pesos2 <- pesos_mfd(YY2, time=wavelengths)
Código
# Se demora en correr, se debe verificar que los pesos son iguales a los de la función anterior
MFHD_w <- mfd(YY2, type = "hdepth", alpha = 0.1)
#save(MFHD_w, file = "C:/Users/EQUIPO/Documents/2026-1/Análisis de datos funcionales/MFHD_w2.RData")
Código
load("MFHD_w2.RData")

5.1 Función Mediana Multivariada

Para el caso multivariado, la función mediana corresponde al vector \(\mathbf{Y}_i\) que maximiza la MFHD. En este caso, dicho vector es \(\mathbf{Y}_{34}\), y se muestra en la ?@fig-funcion_mediana_mv.

5.1.1 Pesos uniformes y no uniformes

Figura 16: Función mediana multivariada obtenida mediante la profundidad con pesos uniformes (rojo) y pesos no uniformes (azul) para las curvas espectrométricas ajustadas de la intensidad de fluorescencia.
Figura 17: Función mediana multivariada obtenida mediante la profundidad con pesos uniformes (rojo) y pesos no uniformes (azul) para las curvas espectrométricas ajustadas de la intensidad de fluorescencia.
Figura 18: Función mediana multivariada obtenida mediante la profundidad con pesos uniformes (rojo) y pesos no uniformes (azul) para las curvas espectrométricas ajustadas de la intensidad de fluorescencia.
Figura 19: Función mediana multivariada obtenida mediante la profundidad con pesos uniformes (rojo) y pesos no uniformes (azul) para las curvas espectrométricas ajustadas de la intensidad de fluorescencia.
Código
YYmedian_idx
[1] 184
Código
YYmedian_idx2
[1] 238

5.2 Observaciones Outliers

Sea \(\Psi=\{\psi_1,\ldots,\psi_n\}\) un conjunto de datos funcionales multivariados, donde \(\psi_i = (\psi_{i1},\ldots,\psi_{ip}), \quad i=1,\ldots,n\) y \(\psi_{ij}\) está definida en \([a,b]\) para \(j=1,\ldots,p\).

Sea \(\chi = (\chi_1, \ldots ,\chi_p)'\) donde \(\chi_j \subset [a,b]\), \(j=1,\ldots,p\).

Sea \(\{t_1,\ldots,t_N\}\) un conjunto discreto de puntos tales que \(t_j \subset [a,b]\).

La atipicidad direccional (DO) de la función multivariada \(\chi\) con respecto a \(\Psi\) está dada por

\[ \begin{equation} \text{mFDO}(\chi,\Psi) = \sum_{j=1}^{N} \text{MDO}\big(\chi(t_j);\Psi(t_j)\big)\,\omega(t_j) \end{equation} \]

De esta forma, igual que en el caso univariado, se puede graficar el FOM y establecer un punto de corte para la detección de valores atípicos.

Código
# Directional Outlyiness

f_out <- fOutl(x = YY, type = "fDO", diagnostic = TRUE)
fom_X <- fom(f_out, cutoff = TRUE)
Código
indices_outliers_fom <- function(fOutlResult){
  AOValues <- fOutlResult$crossDistsX
  weights <- fOutlResult$weights
  fAO <- AOValues %*% weights
  sdAO <- apply(AOValues, 1, function(y){
    sqrt(sum(weights * (y - sum(weights * y, na.rm=TRUE))^2, na.rm=TRUE) /
           (1 - 1/length(weights)))
  })
  DispMeasure <- sdAO / (1 + fAO)
  CAO <- log(0.1 + sqrt((fAO/median(fAO))^2 +
                          (DispMeasure/median(DispMeasure))^2))
  
  
  Fence <- qnorm(0.995) * mad(CAO) + median(CAO)
  theta <- seq(0, pi/2, length = (100))
  FenceData <- matrix(0, nrow = length(theta), ncol = 2)
  colnames(FenceData) <- c("x", "y")
  FenceData <- data.frame(FenceData)
  FenceData$x <- median(fAO) * (exp(Fence) - 0.1) * cos(theta)
  FenceData$y <- median(DispMeasure) * (exp(Fence) - 0.1) * sin(theta)
  PlotData <- ifelse(CAO > Fence, "red", "black")
  outliers <- which(CAO > Fence)
  
  plot(fAO, DispMeasure, 
       col = PlotData,
       pch = 16,
       xlab = "MfDO", ylab = "MvDO")
  lines(FenceData$x, FenceData$y, lty = 2, lwd = 2)
  text(fAO[outliers], DispMeasure[outliers], 
       labels = outliers, 
       col = "red",
       pos = 3, cex = 0.5)
}

indices_outliers_fom(f_out)

6 Replique la sección 4. Application to Image Data del artículo de Rousseeuw, P. J.,Raymaekers, J., & Hubert, M. (2018).

Se replicará la sección 4 del artículo Rousseeuw, Raymaekers, y Hubert (2018), donde se trabaja una aplicación de la metodología de Directional Outlyingness con imágenes.

Las imágenes son funciones sobre un dominio bivariado. En la práctica, el dominio es una cuadrícula de puntos discretos, por ejemplo, los píxeles horizontales y verticales de una imagen. Por lo que es conveniente usar dos índices \(j = 1, \dots, J\) y \(k = 1, \dots, K\), uno para cada dimensión de la cuadrícula. Los valores de la función pueden ser univariados, como intensidades de gris, pero también pueden ser multivariados, por ejemplo, las intensidades de rojo, verde y azul (RGB).

En general, se escribirá un conjunto de datos de imágenes como una muestra \(\boldsymbol{Y} = \{Y_1, Y_2, \dots, Y_n\}\) donde cada \(Y_i\) es una función de \(\{(j, k); j = 1, \dots, J \text{ y } k = 1, \dots, K\}\) a \(\mathbb{R}^d\).

Las nociones de \(\text{fDO}\) y \(\text{vDO}\) desarrolladas anteriormente seran extendidas, así;

\[\begin{equation} \text{fDO}(Y_i; \boldsymbol{Y}) = \sum_{j=1}^{J} \sum_{k=1}^{K} \text{DO}(Y_i(j, k); \boldsymbol{Y}(j, k))W_{jk}, \end{equation}\]

donde los pesos \(W_{jk}\) deben satisfacer \(\sum_{j=1}^{J} \sum_{k=1}^{K} W_{jk} = 1\), y

\[\begin{equation} \text{vDO}(Y_i; \boldsymbol{Y}) = \frac{\text{stdev}_{j,k}(\text{DO}(Y_i(j, k); \boldsymbol{Y}(j, k)))}{1 + \text{fDO}(Y_i; \boldsymbol{Y})}, \end{equation}\]

Se estudiará un dataset que contiene imágenes de resonancia magnética cerebral de 416 sujetos entre 18 y 96 años. Las imágenes tienen \(176\) por \(208\) píxeles, con valores de escala de grises entre \(0\) y \(255\). En conjunto, tenemos por tanto 416 imágenes observadas \(Y_i\) que contienen valores de intensidad univariados \(Y_i(j, k)\), donde \(j = 1, \dots, J = 176\) y \(k = 1, \dots, K = 208\).

Código
load("C:\\Users\\sofia\\OneDrive\\Documentos\\2026-1\\FDA\\DO_MRI_data.RData")

Usando la función fOutl del paquete mrfDepth se calculara el DO punto por punto.

Código
dims <- dim(MRI) 
dim(MRI) <- c(dims[1], prod(dims[2:3]), dims[4])

DO_MRI_compwise <- mrfDepth::fOutl(x = aperm(MRI, c(2, 1, 3)), type = "fDO", distOptions = list(maxRatio = 2, type = "compWise"), diagnostic = TRUE)

También se grafica el FOM en este caso con el fin de identificar los vectores de imágenes que presentan un comportamiento atípico.

Código
indices_outliers_fom <- function(fOutlResult){
  AOValues <- fOutlResult$crossDistsX
  weights <- fOutlResult$weights
  fAO <- AOValues %*% weights
  sdAO <- apply(AOValues, 1, function(y){
    sqrt(sum(weights * (y - sum(weights * y, na.rm=TRUE))^2, na.rm=TRUE) /
           (1 - 1/length(weights)))
  })
  DispMeasure <- sdAO / (1 + fAO)
  CAO <- log(0.1 + sqrt((fAO/median(fAO))^2 +
                          (DispMeasure/median(DispMeasure))^2))
  
  
  Fence <- qnorm(0.995) * mad(CAO) + median(CAO)
  theta <- seq(0, pi/2, length = (100))
  FenceData <- matrix(0, nrow = length(theta), ncol = 2)
  colnames(FenceData) <- c("x", "y")
  FenceData <- data.frame(FenceData)
  FenceData$x <- median(fAO) * (exp(Fence) - 0.1) * cos(theta)
  FenceData$y <- median(DispMeasure) * (exp(Fence) - 0.1) * sin(theta)
  PlotData <- ifelse(CAO > Fence, "red", "black")
  outliers <- which(CAO > Fence)
  
  plot(fAO, DispMeasure, 
       col = PlotData,
       pch = 16,
       xlab = "MfDO", ylab = "MvDO")
  lines(FenceData$x, FenceData$y, lty = 2, lwd = 2)
  text(fAO[outliers], DispMeasure[outliers], 
       labels = outliers, 
       col = "red",
       pos = 3, cex = 0.5)
}

indices_outliers_fom(DO_MRI_compwise)
Figura 20: FOM de la imágenes de resonancia magnética cerebral
Código
source("C:\\Users\\sofia\\OneDrive\\Documentos\\2026-1\\FDA\\DO_code.R")

DO_MRI_compwise <- DO_MRI_compwise$crossDistsX
dim(DO_MRI_compwise) <- dims[1:3]
DO_heatmap_image(t(apply(DO_MRI_compwise[387, , ], 2, rev)), cap = 15)
Warning: The `guide` argument in `scale_*()` cannot be `FALSE`. This was deprecated in
ggplot2 3.3.4.
ℹ Please use "none" instead.

Código
DO_heatmap_image(t(apply(DO_MRI_compwise[92, , ], 2, rev)), cap = 15)

Código
DO_heatmap_image(t(apply(DO_MRI_compwise[126, , ], 2, rev)), cap = 15)

7 Referencias

López-Pintado, Sara, y Juan Romo. 2009. «On the Concept of Depth for Functional Data». Journal of the American Statistical Association 104 (486): 718-34. https://doi.org/10.1198/jasa.2009.0108.
Rousseeuw, Peter J., Jakob Raymaekers, y Mia Hubert. 2018. «A Measure of Directional Outlyingness With Applications to Image Data and Video». Journal of Computational and Graphical Statistics 27 (2): 345-59. https://doi.org/10.1080/10618600.2017.1366912.