El propósito de este tutorial es demostrar las diversas funcionalidades y métodos estadísticos disponibles en la paquetería de R structToolbox. A través de un caso práctico, se busca ilustrar cómo estas herramientas pueden ser aplicadas eficazmente en un flujo de trabajo de análisis metabolómico, desde el pre-procesamiento de los datos hasta la identificación de metabolitos significativos.
Para ello, se reproducirá el análisis de datos reportado en los estudios de Mendez et al. (2020) y Chan et al. (2016), proporcionando un ejemplo tangible y validado del poder de estas funciones.
El conjunto de datos utilizado en este ejemplo proviene de un estudio de metabolómica clínica basado en Resonancia Magnética Nuclear de protón (1H-RMN). Específicamente, contiene los espectros procesados de muestras de orina obtenidas de pacientes con cáncer gástrico y de controles sanos (Chan et al., 2016). El objetivo fundamental de este tipo de análisis es identificar perfiles metabólicos o biomarcadores que puedan diferenciar entre los dos grupos.
Disponibilidad de los datos:
Los datos brutos originales están disponibles públicamente a
través del repositorio Metabolomics Workbench (ID del
proyecto: PR000699).
La versión procesada, que utilizaremos en este tutorial, está disponible para su descarga como un archivo de Excel desde: https://github.com/CIMCB/MetabWorkflowTutorial/raw/master/GastricCancer_NMR.xlsx.
Como primer paso crucial en cualquier análisis metabolómico, es
necesario importar y estructurar los datos en un formato adecuado para
el análisis en R. En este caso, convertiremos el archivo de Excel en un
objeto de la clase DatasetExperiment. Para leer el archivo,
utilizaremos la función del paquete openxlsx, que lo
cargará directamente en un data.frame de R, listo para su
manipulación.
## install additional bioc packages for vignette if needed
#BiocManager::install(c('pmp', 'ropls', 'BiocFileCache'))
## install additional CRAN packages if needed
#install.packages(c('cowplot', 'openxlsx'))
suppressPackageStartupMessages({
# Bioconductor packages
library(structToolbox)
library(pmp)
library(ropls)
library(BiocFileCache)
# CRAN libraries
library(ggplot2)
library(gridExtra)
library(cowplot)
library(openxlsx)
})
# use the BiocFileCache
bfc <- BiocFileCache(ask = FALSE)url = 'https://github.com/CIMCB/MetabWorkflowTutorial/raw/master/GastricCancer_NMR.xlsx'
# read in file directly from github...
# X=read.xlsx(url)
# if (!requireNamespace("BiocManager", quietly = TRUE))
# install.packages("BiocManager")
# BiocManager::install("BiocFileCache")
# ...or use BiocFileCache
path = bfcrpath(bfc,url)
X = read.xlsx(path)
# sample meta data
SM=X[,1:4]
rownames(SM)=SM$SampleID
# convert to factors
SM$SampleType=factor(SM$SampleType)
SM$Class=factor(SM$Class)
# keep a numeric version of class for regression
SM$Class_num = as.numeric(SM$Class)
## data matrix
# remove meta data
X[,1:4]=NULL
rownames(X)=SM$SampleID
# feature meta data
VM=data.frame(idx=1:ncol(X))
rownames(VM)=colnames(X)
# prepare DatasetExperiment
DE = DatasetExperiment(
data=X,
sample_meta=SM,
variable_meta=VM,
description='1H-NMR urinary metabolomic profiling for diagnosis of gastric cancer',
name='Gastric cancer (NMR)')
DE## A "DatasetExperiment" object
## ----------------------------
## name: Gastric cancer (NMR)
## description: 1H-NMR urinary metabolomic profiling for diagnosis of gastric cancer
## data: 140 rows x 149 columns
## sample_meta: 140 rows x 5 columns
## variable_meta: 149 rows x 1 columns
Un paso fundamental en cualquier análisis metabolómico es la evaluación de la calidad de los datos y la eliminación de aquellas características (en este caso, variables o picos del espectro de RMN) que puedan ser de baja calidad. Este filtro es crucial para asegurar que el análisis estadístico posterior se base en señales biológicas robustas y reproducibles, en lugar de ruido técnico o artefactos.
Como parte de este control de calidad, en el tutorial se aplicará un filtro para eliminar aquellas características que no cumplan con ciertos criterios. Específicamente, se descartarán las señales que presenten:
Un Coeficiente de Variación (QC-RSD) superior al 20%. El RSD (Desviación Estándar Relativa) se calcula utilizando únicamente las muestras de control de calidad (QC) y sirve como medida de la reproducibilidad analítica. Un valor alto indica que la señal es inestable a lo largo del experimento.
Más de un 10% de valores ausentes (missing values) en el conjunto total de muestras. Una alta proporción de valores ausentes puede indicar que la señal es inconsistente o se encuentra por debajo del límite de detección del instrumento.
Retener únicamente las características que pasan estos filtros de calidad aumenta la robustez y la fiabilidad de los resultados biológicos obtenidos.
# prepare model sequence
M = rsd_filter(rsd_threshold=20,qc_label='QC',factor_name='Class') +
mv_feature_filter(threshold = 10,method='across',factor_name='Class')
# apply model
M = model_apply(M,DE)
# get the model output
filtered = predicted(M)
# summary of filtered data
filtered## A "DatasetExperiment" object
## ----------------------------
## name: Gastric cancer (NMR)
## description: 1H-NMR urinary metabolomic profiling for diagnosis of gastric cancer
## data: 140 rows x 53 columns
## sample_meta: 140 rows x 5 columns
## variable_meta: 53 rows x 1 columns
Antes de realizar cualquier modelo estadístico, es crucial pre-procesar los datos para que cumplan con los supuestos de los algoritmos y para que las variables sean comparables entre sí.
Nota sobre la reproducibilidad: Es importante señalar que en este tutorial podría haber una ligera diferencia en el número final de características en comparación con el procesamiento reportado por Mendez et al. Esto se debe a que los filtros aquí implementados utilizan los operadores
>=o<=en lugar de>y<.
Una vez filtrados los datos, se realiza el Análisis de Componentes Principales (PCA), una técnica no supervisada fundamental en metabolómica para una primera evaluación de la calidad de los datos. El PCA nos permite visualizar la estructura general de los datos, identificar muestras atípicas (outliers) y, lo más importante, evaluar la estabilidad del sistema analítico.
En un experimento de metabolómica robusto, se espera que la varianza biológica (la dispersión entre las muestras de los pacientes) sea notablemente mayor que la varianza técnica. Visualmente, en un gráfico de PCA, esto se traduce en:
Un agrupamiento muy compacto (tight clustering) de las muestras de control de calidad (QCs).
Una dispersión mayor de las muestras biológicas, reflejando las diferencias metabólicas reales entre individuos.
Para lograr esto, en el flujo de trabajo que estamos reproduciendo se aplicaron los siguientes pasos secuenciales:
Transformación Logarítmica (log10):
Ayuda a normalizar la distribución de los datos, reduciendo el peso de
los metabolitos de muy alta concentración y haciendo que las variaciones
sean más comparables.
Autoescalado (Autoscaling): Consiste en centrar cada variable a una media de cero y escalarla a una varianza unitaria. Este paso es esencial para que todos los metabolitos contribuyan por igual al modelo, independientemente de su abundancia absoluta.
Imputación de valores ausentes (k-NN, con 3 vecinos): Se utiliza para estimar y rellenar los missing values basándose en los valores de las 3 muestras más similares.
La matriz transformada, escalada e imputada se utiliza entonces como
entrada para el PCA. Una gran ventaja del paquete
structToolbox es que permite “encadenar”
(chain) todos estos pasos en una única secuencia de
modelado, lo que simplifica el código y asegura la reproducibilidad del
análisis.
# prepare the model sequence
M = log_transform(base = 10) +
autoscale() +
knn_impute(neighbours = 3) +
PCA(number_components = 10)
# apply model sequence to data
M = model_apply(M,filtered)
# get the transformed, scaled and imputed matrix
TSI = predicted(M[3])
# scores plot
C = pca_scores_plot(factor_name = 'SampleType')
g1 = chart_plot(C,M[4])
# loadings plot
C = pca_loadings_plot()
g2 = chart_plot(C,M[4])
plot_grid(g1,g2,align='hv',nrow=1,axis='tblr')Una vez que hemos evaluado la calidad y la estructura general de los datos mediante PCA, el siguiente paso es identificar qué características (metabolitos) específicas muestran una diferencia estadísticamente significativa entre los grupos de estudio. Para esto se utiliza la estadística univariada, que evalúa cada metabolito de forma individual.
La paquetería structToolbox ofrece varias herramientas
para este propósito, incluyendo la popular prueba t de
Student (t-test), análisis de varianza (ANOVA), y
otras funciones para el análisis de biomarcadores. Por brevedad y para
mantener la consistencia con el flujo de trabajo que estamos
reproduciendo, en este tutorial nos centraremos únicamente en la
aplicación de la prueba t.
Para realizar una comparación estadística válida entre las condiciones biológicas, es un requisito indispensable filtrar el conjunto de datos. Específicamente, debemos:
Excluir las muestras de control de calidad (QC), ya que su propósito es monitorear la estabilidad analítica y no representan un grupo biológico de interés.
Reducir el conjunto de datos para incluir únicamente las muestras de los dos grupos que deseamos comparar: Cáncer Gástrico (GC) y controles Sanos (HE, del inglés Healthy).
# prepare model
TT = filter_smeta(mode='include',factor_name='Class',levels=c('GC','HE')) +
ttest(alpha=0.05,mtc='fdr',factor_names='Class')
# apply model
TT = model_apply(TT,filtered)
# keep the data filtered by group for later
filtered = predicted(TT[1])
# convert to data frame
out=as_data_frame(TT[2])
# show first few features
head(out)Mientras que el análisis univariado evalúa cada metabolito de forma aislada, la estadística multivariada y el aprendizaje automático (machine learning) nos permiten analizar todas las variables simultáneamente. El objetivo es construir modelos predictivos capaces de clasificar nuevas muestras basándose en su perfil metabólico completo. Por ejemplo, ¿podemos predecir si un paciente tiene cáncer gástrico a partir de los metabolitos de su orina?
Un paso absolutamente crítico en el aprendizaje automático es la división del conjunto de datos en dos subconjuntos independientes:
Conjunto de Entrenamiento (Training Set): Se utiliza para construir o “entrenar” el modelo estadístico. El algoritmo aprende los patrones que diferencian a los grupos (ej. Cáncer vs. Sano) a partir de estos datos.
Conjunto de Prueba (Test Set): Se mantiene “oculto” durante el entrenamiento. Se utiliza una sola vez al final para evaluar el rendimiento del modelo en datos nuevos y no vistos, proporcionando una medida honesta de su capacidad predictiva.
Este proceso es esencial para evitar el sobreajuste (overfitting), un fenómeno donde el modelo memoriza el ruido específico del conjunto de entrenamiento en lugar de aprender el patrón biológico subyacente, lo que le haría fallar al clasificar muestras futuras.
La paquetería structToolbox facilita este paso con
funciones específicas:
split_data: Realiza una división aleatoria simple de
todo el conjunto de datos.
stratified_split: Realiza una división
estratificada. Este método es crucial en estudios clínicos, ya
que asegura que la proporción de muestras de cada grupo (ej. 60% sanos,
40% enfermos) se mantenga igual tanto en el conjunto de entrenamiento
como en el de prueba.
En el flujo de trabajo de Mendez et al., se utilizó el enfoque de división estratificada, que es el que seguiremos aquí para garantizar una validación robusta del modelo.
# prepare model
M = stratified_split(p_train=0.75,factor_name='Class')
# apply to filtered data
M = model_apply(M,filtered)
# get data from object
train = M$training
train## A "DatasetExperiment" object
## ----------------------------
## name: Gastric cancer (NMR)
## (Training set)
## description: • 1H-NMR urinary metabolomic profiling for diagnosis of gastric cancer
## • A subset of the data has been selected as a training set
## data: 62 rows x 53 columns
## sample_meta: 62 rows x 5 columns
## variable_meta: 53 rows x 1 columns
## A "DatasetExperiment" object
## ----------------------------
## name: Gastric cancer (NMR)
## (Testing set)
## description: • 1H-NMR urinary metabolomic profiling for diagnosis of gastric cancer
## • A subset of the data has been selected as a test set
## data: 21 rows x 53 columns
## sample_meta: 21 rows x 5 columns
## variable_meta: 53 rows x 1 columns
Una vez divididos los datos, procedemos a construir un modelo de Análisis Discriminante por Mínimos Cuadrados Parciales (PLS-DA). PLS-DA es un método multivariado supervisado muy popular en metabolómica porque es eficaz para identificar las variables (metabolitos) que mejor explican la separación entre los grupos predefinidos.
Un parámetro crucial en cualquier modelo PLS-DA es el número de componentes a utilizar. Un componente es una “variable latente” que captura una parte de la información de los datos.
Muy pocos componentes: El modelo es demasiado simple y no captura la complejidad biológica (subajuste o underfitting).
Demasiados componentes: El modelo comienza a ajustarse al ruido específico de los datos de entrenamiento en lugar de a la señal biológica, perdiendo su capacidad de predecir nuevas muestras (sobreajuste u overfitting).
Para encontrar el número óptimo de componentes, en el estudio de Mendez et al. se utiliza una técnica robusta llamada validación cruzada de k-pliegues (k-fold cross-validation). Este método divide repetidamente el conjunto de entrenamiento en subconjuntos más pequeños para entrenar y validar el modelo internamente, proporcionando una estimación fiable de su rendimiento.
Adicionalmente, se usan 100 iteraciones de bootstrap para generar intervalos de confianza alrededor de las métricas de rendimiento, lo que nos da una idea de la estabilidad y fiabilidad del modelo.
En structToolbox, estos procedimientos complejos se
implementan de manera elegante utilizando objetos
“iteradores”, que se pueden combinar directamente con
los objetos del modelo. Para la optimización, se utilizará la métrica
R² (el coeficiente de determinación) y el modelo
PLSR del paquete.
Nota: Para acelerar el cálculo en este tutorial, solo se utilizarán 10 iteraciones de bootstrap en lugar de las 100 empleadas en el análisis original.
# scale/transform training data
M = log_transform(base = 10) +
autoscale() +
knn_impute(neighbours = 3,by='samples')
# apply model
M = model_apply(M,train)
# get scaled/transformed training data
train_st = predicted(M)
# prepare model sequence
MS = grid_search_1d(
param_to_optimise = 'number_components',
search_values = as.numeric(c(1:6)),
model_index = 2,
factor_name = 'Class_num',
max_min = 'max') *
permute_sample_order(
number_of_permutations = 10) *
kfold_xval(
folds = 5,
factor_name = 'Class_num') *
(mean_centre(mode='sample_meta')+
PLSR(factor_name='Class_num'))
# run the validation
MS = struct::run(MS,train_st,r_squared())
#
C = gs_line()
chart_plot(C,MS)La gráfica resultante de la validación cruzada muestra el valor de Q² en función del número de componentes utilizados en el modelo.
En metabolómica, Q² es una de las métricas más importantes, ya que representa el poder predictivo del modelo. Mientras que R² nos dice qué tan bien el modelo se ajusta a los datos de entrenamiento, Q² nos indica qué tan bien se espera que el modelo prediga muestras nuevas y no vistas. Un valor alto de Q² (generalmente > 0.5) sugiere un modelo robusto y con buena capacidad de generalización.
Este gráfico es comparable a la Figura del artículo de Mendez et al. En su análisis, los autores observaron que el rendimiento predictivo del modelo se optimizaba con dos componentes. Por lo tanto, para mantener la coherencia con su flujo de trabajo, nosotros también seleccionaremos dos componentes para construir nuestro modelo PLS-DA final.
Una vez que hemos determinado el número óptimo de componentes (dos, en nuestro caso), es el momento de construir el modelo final y evaluar su rendimiento en el conjunto de prueba que habíamos reservado. Este es el paso definitivo para validar si nuestro modelo es realmente capaz de discriminar entre los grupos biológicos en muestras que nunca ha visto.
Para esta evaluación final, enfocada específicamente en el
análisis discriminante, la paquetería
structToolbox proporciona el objeto de modelo
PLSDA. A diferencia del objeto PLSR que se usó
para la optimización, el modelo PLSDA está diseñado
explícitamente para tareas de clasificación y nos proporcionará métricas
de rendimiento relevantes como la exactitud (accuracy), la
sensibilidad y la especificidad.
# prepare the discriminant model
P = PLSDA(number_components = 2, factor_name='Class')
# apply the model
P = model_apply(P,train_st)
# charts
C = plsda_predicted_plot(factor_name='Class',style='boxplot')
g1 = chart_plot(C,P)
C = plsda_predicted_plot(factor_name='Class',style='density')
g2 = chart_plot(C,P)+xlim(c(-2,2))
C = plsda_roc_plot(factor_name='Class')
g3 = chart_plot(C,P)
plot_grid(g1,g2,g3,align='vh',axis='tblr',nrow=1, labels=c('A','B','C'))## A "AUC" object
## --------------
## name: Area under ROC curve
## description: The area under the ROC curve of a classifier is estimated using the trapezoid method.
## value: 0.9739583
Nota sobre el Umbral de Decisión
Al interpretar los gráficos de clasificación del modelo (como los de
la figura anterior), es importante saber que el umbral de
decisión (cutoff) por defecto en los modelos PLS de
structToolbox es 0.
Esto se debe a que, internamente, el software codifica los dos grupos
de estudio con valores numéricos de +1 y
-1 (por ejemplo, Sano = -1 y
Cáncer = +1). Por lo tanto, el valor 0
representa el punto medio exacto entre ambas clases.
Esta elección del umbral para la visualización no tiene ningún impacto en el rendimiento o la calidad general del modelo. Las métricas de rendimiento (como la exactitud o el área bajo la curva) evalúan la capacidad de separación del modelo independientemente de este punto de corte específico.
Incluso después de una validación con un conjunto de prueba, en metabolómica siempre existe el riesgo de que nuestro modelo haya encontrado una correlación casual en los datos en lugar de un patrón biológico real. Para descartar esta posibilidad y evaluar rigurosamente la significancia estadística de nuestro modelo, se realiza una prueba de permutación (permutation test).
Este procedimiento responde a la pregunta: “¿Qué tan probable es que nuestro resultado se deba puramente al azar?”
El proceso funciona de la siguiente manera:
Se calcula el rendimiento del modelo (ej. Q²) con los datos originales y las etiquetas de clase correctas.
A continuación, las etiquetas de clase de las muestras (ej. “Cáncer” y “Sano”) se barajan aleatoriamente (permutan) y se vuelve a construir y validar todo el modelo. Este paso se repite cientos o miles de veces.
Esto crea una distribución de resultados de rendimiento que se obtendrían si no existiera una relación real entre los perfiles metabólicos y la condición biológica.
Finalmente, se compara el rendimiento del modelo original con esta distribución “del azar”. Si el rendimiento original es significativamente superior a los obtenidos con las etiquetas permutadas, podemos concluir que nuestro modelo es estadísticamente robusto y no es producto del azar.
En structToolbox, la prueba de permutación se implementa
de manera muy conveniente a través del objeto iterador
permutation_test, el cual puede ser combinado con otros
iteradores y modelos para integrar esta validación crucial directamente
en el flujo de trabajo.
# model sequence
MS = permutation_test(number_of_permutations = 20,factor_name = 'Class_num') *
kfold_xval(folds = 5,factor_name = 'Class_num') *
(mean_centre(mode='sample_meta') + PLSR(factor_name='Class_num', number_components = 2))
# run iterator
MS = struct::run(MS,train_st,r_squared())
# chart
C = permutation_test_plot(style = 'density')
chart_plot(C,MS) + xlim(c(-1,1)) + xlab('R Squared')El gráfico resultante, comparable a la mitad inferior de la Figura en el artículo de Mendez et al., nos permite validar el modelo de forma definitiva.
Se observa claramente que el valor de Q² del modelo real (no permutado) es consistentemente superior a la distribución de los valores Q² obtenidos de los modelos nulos (con etiquetas permutadas). Esta es una evidencia sólida de que el modelo ha capturado un patrón biológico genuino y no es simplemente el resultado de una correlación aleatoria. En resumen, podemos concluir que nuestro modelo es fiable y estadísticamente significativo.
Más allá de construir un modelo predictivo, un objetivo fundamental en metabolómica es entender qué metabolitos específicos son los responsables de la separación entre los grupos. Los modelos PLS-DA son excelentes para esto, ya que nos permiten visualizar e interpretar sus variables latentes (los componentes).
Mediante los gráficos de proyección, como los scores plots y los loadings plots, podemos:
Visualizar cómo se distribuyen las muestras en el espacio del modelo (scores).
Identificar qué variables (metabolitos) tienen mayor influencia en la separación de los grupos (loadings).
# prepare the discriminant model
P = PLSDA(number_components = 2, factor_name='Class')
# apply the model
P = model_apply(P,train_st)
C = pls_scores_plot(components=c(1,2),factor_name = 'Class')
chart_plot(C,P)Una vez validado el modelo, el paso final y más importante desde el punto de vista biológico es identificar qué características (metabolitos) específicas son las que más contribuyen a la separación de los grupos. Estos metabolitos son nuestros principales candidatos a biomarcadores.
Para estimar la importancia de cada variable en el modelo PLS, se utilizan comúnmente dos métricas:
Coeficientes de Regresión: Indican la fuerza y la dirección de la relación de cada metabolito con la clase.
Puntajes VIP (Variable Importance in Projection): Esta es la métrica más utilizada. El puntaje VIP resume la influencia de un metabolito en todo el modelo. Por regla general, las variables con un puntaje VIP > 1.0 se consideran importantes para la discriminación entre los grupos.
En el estudio de Mendez et al., se utilizó la técnica de bootstrapping para calcular los intervalos de confianza de estas métricas, lo que proporciona mayor robustez estadística a los hallazgos. Sin embargo, por razones de brevedad, en este tutorial omitiremos ese paso y nos centraremos en el cálculo directo de la importancia de las variables.
# prepare chart
C = pls_vip_plot(ycol = 'HE')
g1 = chart_plot(C,P)
C = pls_regcoeff_plot(ycol='HE')
g2 = chart_plot(C,P)
plot_grid(g1,g2,align='hv',axis='tblr',nrow=2)Hemos recorrido un flujo de trabajo completo y robusto para el
análisis de datos de metabolómica utilizando la paquetería
structToolbox en R.
Partiendo de un conjunto de datos crudos, hemos realizado pasos esenciales que van desde la importación y el control de calidad, pasando por la exploración visual con PCA, hasta la construcción de un modelo predictivo supervisado (PLS-DA). Se ha puesto especial énfasis en los procedimientos críticos de validación rigurosa, como el uso de conjuntos de prueba y la prueba de permutación, para asegurar que nuestros resultados sean estadísticamente significativos y no producto del azar.
Finalmente, demostramos cómo traducir un modelo estadístico en conocimiento biológico tangible, utilizando los puntajes VIP para identificar los metabolitos más influyentes que podrían servir como potenciales biomarcadores. Este tutorial no solo reproduce un análisis publicado, sino que también proporciona una plantilla metodológica sólida que puedes adaptar para explorar tus propios conjuntos de datos metabolómicos y extraer de ellos conclusiones científicas valiosas.
## R version 4.5.0 (2025-04-11)
## Platform: x86_64-pc-linux-gnu
## Running under: Debian GNU/Linux 13 (trixie)
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.1
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.1; LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
## [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
## [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
## [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] openxlsx_4.2.8.1 cowplot_1.2.0 gridExtra_2.3.1
## [4] ggplot2_4.0.3 BiocFileCache_3.0.0 dbplyr_2.6.0
## [7] ropls_1.42.0 pmp_1.22.1 structToolbox_1.22.0
## [10] struct_1.22.1
##
## loaded via a namespace (and not attached):
## [1] Rdpack_2.6.6 DBI_1.3.0
## [3] MultiDataSet_1.38.0 httr2_1.3.0
## [5] rlang_1.3.0 magrittr_2.0.5
## [7] otel_0.2.0 matrixStats_1.5.0
## [9] compiler_4.5.0 RSQLite_3.53.3
## [11] vctrs_0.7.3 reshape2_1.4.5
## [13] stringr_1.6.0 pkgconfig_2.0.3
## [15] fastmap_1.2.0 XVector_0.50.0
## [17] labeling_0.4.3 rmarkdown_2.31
## [19] itertools_0.1-3 purrr_1.2.2
## [21] bit_4.6.0 xfun_0.60
## [23] MultiAssayExperiment_1.36.2 randomForest_4.7-1.2
## [25] cachem_1.1.0 jsonlite_2.0.0
## [27] blob_1.3.0 DelayedArray_0.36.1
## [29] parallel_4.5.0 R6_2.6.1
## [31] bslib_0.12.0 stringi_1.8.9
## [33] RColorBrewer_1.1-3 ranger_0.18.0
## [35] limma_3.66.0 GenomicRanges_1.62.1
## [37] jquerylib_0.1.4 Rcpp_1.1.2
## [39] Seqinfo_1.0.0 SummarizedExperiment_1.40.0
## [41] iterators_1.0.14 knitr_1.51
## [43] IRanges_2.44.0 BiocBaseUtils_1.12.0
## [45] Matrix_1.7-6 tidyselect_1.2.1
## [47] rstudioapi_0.19.0 dichromat_2.0-1
## [49] abind_1.4-8 yaml_2.3.12
## [51] codetools_0.2-20 curl_7.1.0
## [53] doRNG_1.8.6.3 lattice_0.22-9
## [55] tibble_3.3.1 plyr_1.8.9
## [57] Biobase_2.70.0 withr_3.0.3
## [59] S7_0.2.2 evaluate_1.0.5
## [61] ontologyIndex_2.12 zip_3.0.2
## [63] pillar_1.11.1 filelock_1.0.3
## [65] MatrixGenerics_1.22.0 rngtools_1.5.2
## [67] foreach_1.5.2 stats4_4.5.0
## [69] generics_0.1.4 sp_2.2-3
## [71] S4Vectors_0.48.1 scales_1.4.0
## [73] calibrate_1.7.7 glue_1.8.1
## [75] tools_4.5.0 grid_4.5.0
## [77] impute_1.84.0 missForest_1.6.1
## [79] rbibutils_2.4.1 cli_3.6.6
## [81] ggthemes_5.2.0 S4Arrays_1.10.1
## [83] dplyr_1.2.1 pls_2.9-0
## [85] pcaMethods_2.2.0 gtable_0.3.6
## [87] sass_0.4.10 digest_0.6.39
## [89] BiocGenerics_0.56.0 SparseArray_1.10.10
## [91] farver_2.1.2 memoise_2.0.1
## [93] htmltools_0.5.9 lifecycle_1.0.5
## [95] statmod_1.5.2 qqman_0.1.9
## [97] bit64_4.8.2 MASS_7.3-66