⚛️6.2 Espacio químico

Introducción

El espacio químico, a veces referido en la literatura como "universo químico", se refiere a todas las moléculas posibles, así como a los espacios conceptuales multidimensionales que representan sus propiedades estructurales y funcionales (Virshup, et al., 2013, Maggiora, 2014). En otras palabras, el espacio químico es un espacio multidimensional donde cada eje representa un descriptor molecular (Figura 1) (Medina-Franco, 2022).

Algunas aplicaciones de este concepto en el diseño de fármacos se encuentran en la evaluación de la diversidad de diferentes conjuntos de datos, para explorar relaciones estructura-propiedad (SPR, por la siglas del inglés structure-property relationships) ó relaciones estructura-actividad (SAR, por la siglas del inglés structure-activity relationships), y en el diseño de bibliotecas moleculares (Saldívar-González y Medina-Franco, 2022).

Para construir el espacio químico, se necesitan descriptores moleculares (véase la sección de Descriptores moleculares) que capturen estas propiedades químicas. La Figura 2 muestra un ejemplo del espacio químico para una base de datos. La base de datos está descrita por n moléculas (filas) y por M descriptores moleculares (columnas). La tabla con el conjunto de moléculas y descriptores moleculares es el espacio químico, y el espacio químico puede ser representado con diferentes métodos de visualización o reducción de variables (Medina-Franco, 2022).

1. Reducción de variables

La reducción de variables es necesaria para interpretar la visualización del espacio químico porque, en química, las moléculas se describen en términos de un gran número de variables que pueden incluir propiedades físicas, químicas y termodinámicas. Estas variables pueden ser muy complejas y difíciles de visualizar en su totalidad. Además, cuando se trabaja con grandes conjuntos de datos moleculares, es muy común que haya una gran cantidad de variables que no son relevantes para el análisis, lo que puede dificultar el trabajo con los datos.

Una forma muy útil de visualizar datos con múltiples variables son las técnicas de reducción de variables. Estas técnicas permiten reducir el número de variables en un conjunto de datos moleculares, de manera que se puedan visualizar en gráficos de dos o tres dimensiones. Esto facilita la interpretación de los datos y permite obtener una comprensión más profunda de la distribución global de las variables.

Para este propósito se emplean técnicas de reducción de variables, como el análisis de componentes principales (PCA), t-distributed stochastic neighbor embedding (t-SNE) y uniform manifold approximation and projection (UMAP) (Liu, 2020). El PCA usa un método lineal para la reducción, mientras que t-SNE y UMAP emplean métodos no lineales. Esta diferencia hace que las representaciones visuales obtenidas agrupen los datos de manera diferente e incluso se lleguen a distintas conclusiones.

En el caso de las técnicas lineales de reducción de variables, como PCA, se emplea un método lineal para reducir las dimensiones del problema. Esto significa que se busca una combinación lineal de las variables originales que explique la mayor cantidad posible de la variabilidad en los datos. Esta combinación lineal se llama componente principal, y cada componente principal subsiguiente se define como una combinación lineal adicional de las variables originales que es ortogonal a las combinaciones lineales anteriores. A continuación, se presenta un ejemplo básico de cómo puede ser implementado el método PCA en Python.

from sklearn.decomposition import PCA

#Crear dataframe con datos
data_initial = pd.DataFrame({'Variable 1': [1, 10, 99, 5], 'Variable 2': [2, 1, 2, 4], 
                             'Variable 3': [7, 13, 12, 15] })

# PCA  
reduction_pca = PCA(n_components = 2 )
reduction_pca.fit(data_initial)

## Reducción de dimensionalidad con PCA

data_final = reduction_pca.transform(data_initial)

print(type(data_final))
data_final

#Visualización 
import matplotlib.pyplot as plt

plt.scatter(data_final[:, 0], data_final[:, 1])
plt.show

Técnicas no lineales de reducción de variables, como t-SNE y UMAP, son capaces de encontrar estructuras más complejas en los datos. Estas técnicas pueden encontrar patrones no lineales en los datos que no se pueden detectar con técnicas lineales. Esto puede ser especialmente útil en el caso de conjuntos de datos moleculares complejos, donde las propiedades moleculares pueden ser altamente no lineales y difíciles de visualizar.

import numpy as np
import pandas as pd
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# Cargar datos
df = pd.read_csv('molecule_features.csv')

# Extraer características y etiquetas
X = df.drop('label', axis=1).values
y = df['label'].values

# Reducción de dimensionalidad con t-SNE
tsne = TSNE(n_components=2, perplexity=30, learning_rate=200)
X_tsne = tsne.fit_transform(X)

# Visualización
fig, ax = plt.subplots(figsize=(10,10))
colors = ['red', 'blue', 'green', 'purple', 'orange', 'yellow', 'pink', 'brown', 'gray', 'black']
for i in range(10):
    ax.scatter(X_tsne[y==i,0], X_tsne[y==i,1], color=colors[i], label=str(i), alpha=0.5)
ax.legend()
plt.show()

En resumen, la reducción de variables es una técnica útil para visualizar el espacio químico de compuestos que están descritos por más de tres descriptores. Las técnicas de reducción de variables lineales y no lineales, como PCA, t-SNE y UMAP, son herramientas poderosas para el análisis de datos y pueden ser utilizadas en conjunto con librerías como RDKit, Pandas y Scikit-learn para obtener una comprensión más profunda del espacio químico de un grupo de moléculas.

2. Caso de estudio

Nota: para la revisión de temas relacionados al "Espacio Químico" seguiremos utilizando la base de datos vista en la sección de Descriptores moleculares; es decir, aquella compuesta por proteínas esenciales para la estabilidad del ADN (DNA), con 5528 moléculas; la segunda enfocada en análogos de fármacos aprobados por la FDA, EMA y PMDA (Approved_AnalogDB) con 1403 moléculas y la última, es una base enfocada en ingredientes herbales (HerbalDB) con 485 moléculas.

Reordenamiento de la base de datos.

Con el objetivo de ejemplificar el caso de reducción de variables, se van a seleccionar los seis primeros descriptores moleculares y reordenar las columnas de la base de datos.

Esto puede llevarse a cabo con el siguiente código.

df_DB=df[[ "SlogP", "TPSA", "ExactMW", "NumRotatableBonds", "NumHBD", "NumHBA", "NumHeteroAtoms","DataBase"]]
df_DB

Con estas líneas de código, se modifica el DataFrame donde se encontraba almacenada la base de datos orignal. Es decir, ahora indicamos que ese DataFrame cambiará a solo las columnas que hemos escrito: "SlogP", "TPSA", "ExactMW", "NumRotatableBonds", "NumHBD", "NumHBA", "NumHeteroAtoms", "DataBase".

2.1 PCA (principal component analysis)

El análisis de componentes principales (PCA) es un método lineal de reducción de dimensionalidad que se utiliza para analizar conjuntos de datos con muchas variables correlacionadas. Este método busca las direcciones en las cuales los datos tienen la mayor variabilidad y luego proyecta los datos en un espacio de dimensiones reducidas definido por estas direcciones.

A continuación, se muestra el código que permite realizar la reducción de variables mediante el método PCA.

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

data_pca = df_DB.copy()
data_pca = data_pca.drop(labels = ['DataBase'],axis = 1)
#What StandardScaler() does is scaling the data. The fit.transform() module fits these new values to the data, 
#and stores them, replacing the old values.
data_pca = StandardScaler().fit_transform(data_pca)
#Apply PCA on the transformed (scaled and centered) data:
pca = PCA(n_components=3)
pca_results = pca.fit_transform(data_pca)
pca_results

En primer lugar, se seleccionan seis características de la base de datos original: "SlogP", "TPSA", "ExactMW", "NumRotatableBonds", "NumHBD" y "NumHBA". Estas características se consideran relevantes para el análisis que se desea realizar. Luego, se reordenan las columnas de la base de datos para que la columna "DataBase" quede al final.

Una vez hecho esto, se importan dos funciones de la librería Scikit-learn: StandardScaler y PCA. StandardScaler se utiliza para normalizar los datos, es decir, escalarlos a un rango común para que ninguna variable tenga más peso que las demás en el análisis. Por otro lado, PCA es un método de reducción de variables que busca combinar las características de la base de datos para crear nuevas variables que expliquen la mayor cantidad posible de la variabilidad de los datos originales.

Luego, se crea una copia de la base de datos sin la columna "DataBase", se normalizan los datos con StandardScaler y se aplica PCA con tres componentes (n_components=3), lo que significa que el resultado serán tres nuevas variables que explican la mayor cantidad posible de la variabilidad de los datos originales.

Por último, se guarda el resultado de aplicar PCA en la variable pca_results. Este resultado se puede utilizar para realizar visualizaciones de las moléculas en un espacio tridimensional, lo que facilita la interpretación de los datos. La librería Scikit-learn es muy útil para realizar análisis de datos en Python y cuenta con una documentación muy completa que puede resultar de gran ayuda.

Posteriormente, para la visualización de esta reducción podemos usar el siguiente código.

# Importamos el módulo StandardScaler de la librería sklearn.preprocessing
from sklearn.preprocessing import StandardScaler
# Creamos una instancia de StandardScaler
sc = StandardScaler()
# Ajustamos los datos de entrenamiento a la instancia creada
sc.fit(data_pca)
# Transformamos los datos de entrenamiento a la escala ajustada previamente
X_train_std = sc.transform(data_pca)

# Creamos una instancia del objeto PCA
pca = PCA()
# Determinamos las características transformadas utilizando el método fit_transform sobre los datos estandarizados
X_train_pca = pca.fit_transform(X_train_std)
# Determinamos la varianza explicada utilizando el atributo explained_variance_ratio_
exp_var_pca = pca.explained_variance_ratio_

# Obtenemos la suma acumulativa de los valores propios
cum_sum_eigenvalues = np.cumsum(exp_var_pca)
# Creamos una visualización para la varianza explicada utilizando una gráfica de barras y una gráfica escalonada
plt.bar(range(0,len(exp_var_pca)), exp_var_pca, alpha=0.5, align='center', label='Individual explained variance')
plt.step(range(0,len(cum_sum_eigenvalues)), cum_sum_eigenvalues, where='mid',label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

Primeramente, ocurre la normalización de los datos, lo que es importante ya que no todas las variables tienen el mismo rango. Esto se logra a través del método StandardScaler que se encarga de ajustar los datos a una escala común. La variable data_pca es la matriz de datos que se quiere normalizar. Una vez que el objeto sc se ajusta a los datos, se transforman los datos originales usando el método transform y se guardan en la variable X_train_std.

Después, se crea una instancia del objeto PCA utilizando la clase PCA y se aplica el método fit_transform a los datos estandarizados X_train_std. De esta forma, se determinan las características transformadas en función de las componentes principales. Posteriormente, se utiliza el atributo explained_variance_ratio_ para obtener la varianza explicada por cada componente principal en la transformación.

Finalmente, se pretende visualizar la varianza explicada por cada componente principal utilizando una gráfica de barras y una gráfica escalonada. La variable cum_sum_eigenvalues es un arreglo numpy que contiene la suma acumulativa de la varianza explicada por cada componente principal. Luego, se utiliza la función plt.bar() para crear la gráfica de barras de la varianza explicada para cada componente principal y la función plt.step() para crear la gráfica escalonada de la varianza explicada acumulada. Finalmente, se ajustan los ejes y se muestran ambas gráficas utilizando plt.tight_layout() y plt.show().

Ahora vamos a usar seaborn para hacer el gráfico de PCA. En este gráfico, los puntos de datos similares deberían estar más cerca, formando grupos (clusters). Para este conjunto de datos nos gustaría ver a las distintas bases de datos, formando grupos distintos (al hacer el gráfico de dispersión, el parámetro DataBase se corresponde con el color de los puntos).

import matplotlib.pyplot as plt
import seaborn as sns
pca_dataset = pd.DataFrame(data = pca_results, columns = ['component1', 'component2','component3'] )
pca_dataset['DataBase']=df_DB['DataBase']
plt.figure()
plt.figure(figsize=(10,10))
plt.xlabel('Component 1')
plt.ylabel('Component 2')
plt.title('2 Component PCA')
sns.scatterplot(x = pca_dataset['component1'], y = pca_dataset['component2'], hue=pca_dataset['DataBase'],
                alpha=0.3,palette=["red", "yellow","blue"])

En este bloque de código, se utiliza la biblioteca Seaborn para realizar el gráfico de dispersión en dos dimensiones de los datos después de la reducción de dimensionalidad por PCA.

En primer lugar, se crea un DataFrame llamado "pca_dataset" utilizando los resultados de la transformación PCA previamente realizada en los datos originales. Este DataFrame tiene tres columnas llamadas "component1", "component2" y "component3" correspondientes a las tres componentes principales seleccionadas en el paso anterior. Además, se agrega una cuarta columna llamada "DataBase" que corresponde a la base de datos a la que pertenece cada punto de datos.

Luego, se crea una figura vacía utilizando la función plt.figure(). Se especifica su tamaño mediante el argumento figsize que recibe una tupla con las dimensiones deseadas.

A continuación, se establecen las etiquetas de los ejes x e y utilizando las funciones plt.xlabel() y plt.ylabel(), respectivamente. Además, se agrega un título al gráfico utilizando plt.title().

Finalmente, se utiliza la función sns.scatterplot() de Seaborn para realizar el gráfico de dispersión. Los datos para los ejes x e y son las columnas "component1" y "component2" del DataFrame "pca_dataset", respectivamente. El parámetro hue se utiliza para asignar un color a cada punto de datos en función de la columna "DataBase" del DataFrame. El argumento alpha controla la transparencia de los puntos y el argumento palette establece una paleta de colores personalizada para los grupos de bases de datos (en este caso, "red", "yellow" y "blue").

El objetivo de este gráfico es visualizar cómo los datos se agrupan en función de las distintas bases de datos.

Google Colab

2.2 t-SNE (t-distributed stochastic neighbor embedding)

Es un algoritmo de reducción de dimensionalidad no lineal utilizado para visualizar datos en un espacio de baja dimensión (generalmente dos o tres dimensiones) a partir de datos de alta dimensión. t-SNE encuentra patrones en la distribución de los puntos en un espacio de alta dimensión y tratar de preservar estos patrones en un espacio de baja dimensión. Este algoritmo se utiliza con frecuencia para la visualización de datos, especialmente en campos como la bioinformática, la genómica y la ciencia de datos en general.

Para implementar el algoritmo t-SNE en Python, podemos utilizar el módulo Scikit-Learn. Primero, creamos una copia de la base de datos original y luego eliminamos las columnas "NumRotatableBonds" y "DataBase" utilizando el método .drop(). Luego, creamos un objeto t-SNE y lo ajustamos a nuestra copia de la base de datos utilizando la función tsne.fit_transform(). Por último, podemos visualizar los resultados utilizando Seaborn para crear un gráfico de dispersión.

from sklearn.manifold import TSNE
data_tsne = df_DB.copy()
data_tsne = data_tsne.drop(labels = ["DataBase"],axis = 1)
data_tsne = StandardScaler().fit_transform(data_tsne)
tsne = TSNE(n_components=2, verbose=1, perplexity=40, n_iter=300)
tsne_results = tsne.fit_transform(data_tsne)
tsne_resultspyth

tsne_dataset = pd.DataFrame(data = tsne_results, columns = ['component1', 'component2'] )
tsne_dataset['DataBase']=df_DB['DataBase']
plt.figure()
plt.figure(figsize=(10,10))
plt.xlabel('Component 1')
plt.ylabel('Component 2')
plt.title('2 Component TSNE')
sns.scatterplot(x = tsne_dataset['component1'], y = tsne_dataset['component2'], hue=tsne_dataset['DataBase'],
                alpha=0.3, palette=["red", "yellow", "blue"])

El código anterior arroja como resultado el siguiente gráfico (Figura 6):

Google Colab

2.3 UMAP (uniform manifold approximation and projection)

Es un método similar a t-SNE en cuanto a su capacidad para preservar las relaciones locales y globales de los datos, pero a menudo se considera más rápido y escalable en conjuntos de datos más grandes. A diferencia de PCA, UMAP no asume que los datos tienen una estructura lineal y puede ser más adecuado para datos no lineales y no gaussianos. UMAP también es capaz de manejar datos dispersos, como matrices de similitud, y puede proporcionar una visualización más clara de las estructuras en los datos.

import pandas as pd
import numpy as np
import umap.umap_ as umap
import seaborn as sns

# Carga de datos
df = pd.read_csv('datos_moleculares.csv')

# Eliminación de columnas innecesarias
X = df.drop(['Nombre', 'Clase'], axis=1)

# Reducción de dimensionalidad usando UMAP
umap_redux = umap.UMAP(n_components=2, random_state=42)
umap_results = umap_redux.fit_transform(X.values)

# Visualización de resultados
df['umap-2d-x'] = umap_results[:, 0]
df['umap-2d-y'] = umap_results[:, 1]
sns.scatterplot(x='umap-2d-x', y='umap-2d-y', hue='Clase', data=df)

UMAP ha sido utilizado en una variedad de disciplinas además de la quimioinformática, incluyendo la biología, la física y la informática. En biología, se ha utilizado para analizar la relación entre diferentes tipos de células y tejidos, mientras que en física, se ha utilizado para estudiar sistemas complejos y la estructura de materiales. En informática, se ha utilizado para analizar conjuntos de datos de alta dimensionalidad en aprendizaje automático y la minería de datos.

UMAP ha sido objeto de investigación activa en los últimos años, y ha habido varios estudios que han explorado su rendimiento y eficacia en comparación con otros métodos de reducción de dimensionalidad. Algunos de estos estudios han encontrado que UMAP puede superar a métodos como t-SNE en términos de precisión y velocidad en ciertos conjuntos de datos.

import pandas as pd
import seaborn as sns
from sklearn.preprocessing import StandardScaler
import umap
import matplotlib.pyplot as plt

# Copiamos el dataframe original para no modificarlo
data_umap = df_DB.copy()

# Eliminamos la columna "DataBase" ya que es categórica y no es numérica
data_umap = data_umap.drop(labels = ['DataBase'],axis = 1)

# Escalamos los datos para hacerlos comparables entre sí
scaled_data_umap = StandardScaler().fit_transform(data_umap)

# Instanciamos el objeto UMAP con sus hiperparámetros por defecto
umap = umap.UMAP()

# Aplicamos UMAP sobre los datos escalados para obtener las dos componentes principales
umap_results = umap.fit_transform(scaled_data_umap)

# Convertimos los resultados de UMAP en un nuevo dataframe
umap_dataset = pd.DataFrame(data = umap_results, columns = ['component1', 'component2'] )

# Agregamos la columna "DataBase" nuevamente al dataframe con los resultados de UMAP
umap_dataset['DataBase']=df_DB['DataBase']

# Creamos la figura para el scatterplot
plt.figure(figsize=(10,10))

# Definimos los ejes x e y y el título
plt.xlabel('Component 1')
plt.ylabel('Component 2')
plt.title('2 Component UMAP')

# Creamos el scatterplot usando Seaborn
sns.scatterplot(x = umap_dataset['component1'], y = umap_dataset['component2'], hue = umap_dataset['DataBase'],
                alpha=0.3,palette=["red", "yellow","blue"])

# Mostramos la figura
plt.show()

Con este código podemos obtener la siguiente visualización (Figura 7).

Para saber más:

Last updated