Explorando los tipos de cáncer con neo4j | por David Wells | agosto, 2024

Cómo identificar y visualizar clústeres en gráficos de conocimiento

En esta publicación, identificaremos y visualizaremos diferentes grupos de tipos de cáncer mediante el análisis ontología de la enfermedad como un gráfico de conocimiento. En concreto, configuraremos neo4j en un contenedor Docker, importaremos la ontología, generaremos clústeres de gráficos e incrustaciones, antes de utilizar la reducción de dimensión para trazar estos clústeres y obtener algunos conocimientos. Aunque estamos utilizando `disease_ontology` como ejemplo, se pueden utilizar los mismos pasos para explorar cualquier ontología o base de datos de gráficos.

Tipos de cáncer vistos como incrustaciones y coloreados por grupo, imagen del autor

En una base de datos gráfica, en lugar de almacenar datos como filas (como una hoja de cálculo o una base de datos relacional), los datos se almacenan como nodos y relaciones entre nodos. Por ejemplo, en la figura siguiente, vemos que el melanoma y el carcinoma son subcategorías del tipo de célula cáncer tumor (mostrado por la relación SCO). Con este tipo de datos, podemos ver claramente que el melanoma y el carcinoma están relacionados, aunque esto no se indique explícitamente en los datos.

Ejemplo de base de datos gráfica, imagen del autor

Las ontologías son un conjunto formalizado de conceptos y relaciones entre esos conceptos. Son mucho más fáciles de analizar para las computadoras que el texto libre y, por lo tanto, es más fácil extraer significado de ellas. Las ontologías se utilizan ampliamente en las ciencias biológicas y es posible que encuentre una ontología que le interese en https://obofoundry.org/Aquí nos centraremos en la ontología de la enfermedad, que muestra cómo los diferentes tipos de enfermedades se relacionan entre sí.

Neo4j es una herramienta para gestionar, consultar y analizar bases de datos gráficas. Para facilitar su configuración, utilizaremos un contenedor Docker.

docker run \
-it - rm \
- publish=7474:7474 - publish=7687:7687 \
- env NEO4J_AUTH=neo4j/123456789 \
- env NEO4J_PLUGINS='["graph-data-science","apoc","n10s"]' \
neo4j:5.17.0

En el comando anterior, los indicadores `-publish` establecen puertos para permitir que Python consulte la base de datos directamente y nos permita acceder a ella a través de un navegador. El argumento `NEO4J_PLUGINS` especifica qué complementos instalar. Desafortunadamente, la imagen de Docker de Windows no parece poder manejar la instalación, por lo que para continuar, deberá instalar Neo4j Desktop manualmente. Sin embargo, no se preocupe, los demás pasos deberían funcionar para usted.

Mientras neo4j se está ejecutando, puedes acceder a tu base de datos yendo a http://localhost:7474/ en tu navegador, o puedes usar el controlador de Python para conectarte como se muestra a continuación. Ten en cuenta que estamos usando el puerto que publicamos con nuestro comando docker anterior y nos estamos autenticando con el nombre de usuario y la contraseña que también definimos anteriormente.

URI = "bolt://localhost:7687"
AUTH = ("neo4j", "123456789")
driver = GraphDatabase.driver(URI, auth=AUTH)
driver.verify_connectivity()

Una vez que haya configurado su base de datos neo4j, es hora de obtener algunos datos. El complemento neo4j n10s está diseñado para importar y manejar ontologías; puede usarlo para integrar sus datos en una ontología existente o para explorar la ontología en sí. Con los comandos cypher a continuación, primero establecemos algunas configuraciones para que los resultados sean más claros, luego establecemos una restricción de unicidad y, finalmente, importamos la ontología de la enfermedad.

CALL n10s.graphconfig.init({ handleVocabUris: "IGNORE" });
CREATE CONSTRAINT n10s_unique_uri FOR (r:Resource) REQUIRE r.uri IS UNIQUE;
CALL n10s.onto.import.fetch(http://purl.obolibrary.org/obo/doid.owl, RDF/XML);

Para ver cómo se puede hacer esto con el controlador de Python, consulte el código completo aquí https://github.com/DAWells/do_onto/blob/main/import_ontology.py

Ahora que hemos importado la ontología, puedes explorarla abriendo http://localhost:7474/ en tu navegador web. Esto te permite explorar un poco de tu ontología de forma manual, pero nos interesa el panorama general, así que hagamos un análisis. En concreto, haremos agrupamiento de Louvain y generaremos incrustaciones de proyección aleatorias rápidas.

El agrupamiento de Louvain es un algoritmo de agrupamiento para redes como esta. En resumen, identifica conjuntos de nodos que están más conectados entre sí que con el conjunto más amplio de nodos; este conjunto se define entonces como un clúster. Cuando se aplica a una ontología, es una forma rápida de identificar un conjunto de conceptos relacionados. La proyección aleatoria rápida, por otro lado, produce una incrustación para cada nodo, es decir, un vector numérico donde más nodos similares tienen más vectores similares. Con estas herramientas podemos identificar qué enfermedades son similares y cuantificar esa similitud.

Para generar incrustaciones y clústeres, tenemos que “proyectar” las partes de nuestro gráfico que nos interesan. Debido a que las ontologías suelen ser muy grandes, esta subdivisión es una forma sencilla de acelerar el cálculo y evitar errores de memoria. En este ejemplo, solo nos interesan los cánceres y ningún otro tipo de enfermedad. Lo hacemos con la consulta de cifrado que aparece a continuación; hacemos coincidir el nodo con la etiqueta “cáncer” y cualquier nodo que esté relacionado con este mediante una o más relaciones SCO o SCO_RESTRICTION. Debido a que queremos incluir las relaciones entre los tipos de cáncer, tenemos una segunda consulta MATCH que devuelve los nodos de cáncer conectados y sus relaciones.

MATCH (cancer:Class {label:"cancer"})<-[:SCO|SCO_RESTRICTION *1..]-(n:Class)
WITH n
MATCH (n)-[:SCO|SCO_RESTRICTION]->(m:Class)
WITH gds.graph.project(
"proj", n, m, {}, {undirectedRelationshipTypes: ['*']}
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels

Una vez que tenemos la proyección (a la que hemos llamado “proj”) podemos calcular los clústeres y las incrustaciones y volver a escribirlas en el gráfico original. Finalmente, al consultar el gráfico, podemos obtener las nuevas incrustaciones y clústeres para cada tipo de cáncer, que podemos exportar a un archivo csv.

CALL gds.fastRP.write(
'proj',
{embeddingDimension: 128, randomSeed: 42, writeProperty: 'embedding'}
) YIELD nodePropertiesWritten

CALL gds.louvain.write(
"proj",
{writeProperty: "louvain"}
) YIELD communityCount

MATCH (cancer:Class {label:"cancer"})<-[:SCO|SCO_RESTRICTION *0..]-(n)
RETURN DISTINCT
n.label as label,
n.embedding as embedding,
n.louvain as louvain

Echemos un vistazo a algunos de estos grupos para ver qué tipo de cánceres están agrupados. Después de haber cargado los datos exportados en un marco de datos de Pandas en Python, podemos inspeccionar los grupos individuales.

El grupo 2168 es un conjunto de cánceres de páncreas.

nodes[nodes.louvain == 2168]["label"].tolist()
#array(['"islet cell tumor"',
# '"non-functioning pancreatic endocrine tumor"',
# '"pancreatic ACTH hormone producing tumor"',
# '"pancreatic somatostatinoma"',
# '"pancreatic vasoactive intestinal peptide producing tumor"',
# '"pancreatic gastrinoma"', '"pancreatic delta cell neoplasm"',
# '"pancreatic endocrine carcinoma"',
# '"pancreatic non-functioning delta cell tumor"'], dtype=object)

El grupo 174 es un grupo más grande de cánceres, pero principalmente carcinomas.

nodes[nodes.louvain == 174]["label"]
#array(['"head and neck cancer"', '"glottis carcinoma"',
# '"head and neck carcinoma"', '"squamous cell carcinoma"',
#...
# '"pancreatic squamous cell carcinoma"',
# '"pancreatic adenosquamous carcinoma"',
#...
# '"mixed epithelial/mesenchymal metaplastic breast carcinoma"',
# '"breast mucoepidermoid carcinoma"'], dtype=object)p

Se trata de agrupaciones sensatas, basadas en el tipo de órgano o de cáncer, que resultarán útiles para la visualización. Por otra parte, las incrustaciones siguen teniendo unas dimensiones demasiado elevadas como para poder visualizarlas de forma significativa. Afortunadamente, la TSNE es un método muy útil para la reducción de dimensiones. En este caso, utilizamos la TSNE para reducir la incrustación de 128 dimensiones a 2, manteniendo al mismo tiempo los nodos estrechamente relacionados muy juntos. Podemos comprobar que esto ha funcionado trazando estas dos dimensiones como un diagrama de dispersión y coloreándolas según los grupos de Louvain. Si estos dos métodos coinciden, deberíamos ver los nodos agrupados por color.

from sklearn.manifold import TSNE

nodes = pd.read_csv("export.csv")
nodes['louvain'] = pd.Categorical(nodes.louvain)

embedding = nodes.embedding.apply(lambda x: ast.literal_eval(x))
embedding = embedding.tolist()
embedding = pd.DataFrame(embedding)

tsne = TSNE()
X = tsne.fit_transform(embedding)

fig, axes = plt.subplots()
axes.scatter(
X[:,0],
X[:,1],
c = cm.tab20(Normalize()(nodes['louvain'].cat.codes))
)
plt.show()

Proyección TSNE de inclusiones de cáncer coloreadas por grupo, imagen del autor

Esto es exactamente lo que vemos: los tipos de cáncer similares se agrupan y se hacen visibles como grupos de un solo color. Observe que algunos nodos de un solo color están muy separados, esto se debe a que tenemos que reutilizar algunos colores, ya que hay 29 grupos y solo 20 colores. Esto nos brinda una excelente descripción general de la estructura de nuestro gráfico de conocimiento, pero también podemos agregar nuestros propios datos.

A continuación, graficamos la frecuencia del tipo de cáncer como tamaño del ganglio y la tasa de mortalidad como opacidad (Bray y otros 2024). Solo tuve acceso a estos datos para algunos de los tipos de cáncer, por lo que solo he representado esos nodos. A continuación, podemos ver que el cáncer de hígado no tiene una incidencia especialmente alta en general. Sin embargo, las tasas de incidencia del cáncer de hígado son mucho más altas que las de otros cánceres dentro de su grupo (mostrados en violeta), como el de orofaringe, laringe y nasofaringe.

Frecuencia y mortalidad de los cánceres coloreados por grupo, imagen del autor

Aquí hemos utilizado la ontología de enfermedades para agrupar distintos tipos de cáncer en grupos, lo que nos proporciona el contexto para comparar estas enfermedades. Esperamos que este pequeño proyecto le haya mostrado cómo explorar visualmente una ontología y agregar esa información a sus propios datos.

Puedes consultar el código completo de este proyecto en https://github.com/DAWells/do_onto.

Bray, F., Laversanne, M., Sung, H., Ferlay, J., Siegel, RL, Soerjomataram, I. y Jemal, A. (2024). Estadísticas mundiales de cáncer 2022: estimaciones de GLOBOCAN sobre la incidencia y la mortalidad en todo el mundo para 36 tipos de cáncer en 185 países. CA: una revista sobre cáncer para médicos, 74(3), 229–263.