# Base de datos FooDrugs :pill: :pizza: ###### tags: `Marco`, `FNS-Cloud`, `FooDrugs`, `MySQL`, `R`, `database` [ToC] ## Preparando la base de datos El archivo está en la partición de Ubuntu de Marco. Lo he encontrado con estos comandos desde mi directorio home: ```bash ls -lht ../mgarranzo | grep 'sql' ``` ``` -rw-rw-r-- 1 1000 1000 1,3G ene 31 07:06 FinalFooDrugs_v2.sql ``` Vemos que el archivo es del **31 de enero de 2022**, por lo que sí parece ser la última versión de la base de datos. ### Configuración de MySQL Ahora quedaría instalarla para tenerla en MySQL. He intentado instalar Workbench siguiendo [este tutorial](https://platzi.com/tutoriales/1566-bd/8226-como-instalar-mysql-y-workbench-en-ubuntu-sin-morir-en-el-intento/), pero me da un error porque no encuentra el cliente de línea de comandos. Así pues, he seguido con la línea de comandos. Para **generar la base de datos:** ```bash mysql -u root -p FooDrugs_v2 < FinalFooDrugs_v2.sql ``` :::warning Es importante que pongamos `FooDrugs_v2` como nombre de la base de datos porque es ese el que viene en el script `FinalFooDrugs_v2.sql`: ```sql=! CREATE DATABASE /*!32312 IF NOT EXISTS*/ `FooDrugs_v2` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */; ``` ::: Esto para el portátil. En Totoro he tenido que crear manualmente la base de datos primero: ```bash blanca@totoro:~$ mysql -u root -p ``` ```sql mysql> CREATE DATABASE FooDrugs_v2; mysql> exit ``` ```bash blanca@totoro:~$ mysql -u root -p FooDrugs_v2 < FinalFooDrugs_v2.sql ``` ## Buscando la información relevante ### Número de textos de cada base de datos En la tabla `texts` tenmos la URL y el texto de cada texto. Podemos ver las URLs con: ```sql SELECT link FROM texts; ``` Así veremos todas las URLs. Al final vemos que la tabla tiene **2849 filas**. Observaremos algunos links repetidos. Por ejemplo: ```sql=! SELECT texts_ID FROM texts WHERE link = 'https://www.drugs.com/food-interactions/fentanyl,sublimaze.html?professional=1'; ``` ``` +----------+ | texts_ID | +----------+ | 2 | | 2931 | | 2934 | | 2937 | | 2940 | | 2943 | +----------+ ``` Si nos fijamos en la columna `document`, veremos que los textos también son idénticos: ```sql=! SELECT document FROM texts WHERE link = 'https://www.drugs.com/food-interactions/fentanyl,sublimaze.html?professional=1'; ``` Asumo que esto es porque en un mismo texto se puede mencionar varias veces cada interacción. Por ejemplo: ```sql=! SELECT * FROM TM_interactions WHERE texts_id = '2' OR texts_id = '2931' OR texts_ID = '2934' OR texts_id = '2937' OR texts_ID = '2940' OR texts_ID = '2943'; ``` ``` +--------------------+----------+-------------+-----------+------------+----------+ | TM_interactions_ID | texts_ID | start_index | end_index | food | drug | +--------------------+----------+-------------+-----------+------------+----------+ | 3 | 2 | 385 | 641 | grapefruit | fentanyl | | 4 | 2 | 643 | 846 | grapefruit | fentanyl | | 5 | 2 | 1953 | 2155 | grapefruit | fentanyl | | 6407 | 2931 | 385 | 641 | grapefruit | fentanyl | | 6408 | 2931 | 643 | 846 | grapefruit | fentanyl | | 6409 | 2931 | 1953 | 2155 | grapefruit | fentanyl | | 6413 | 2934 | 385 | 641 | grapefruit | fentanyl | | 6414 | 2934 | 643 | 846 | grapefruit | fentanyl | | 6415 | 2934 | 1953 | 2155 | grapefruit | fentanyl | | 6419 | 2943 | 385 | 641 | grapefruit | fentanyl | | 6420 | 2943 | 643 | 846 | grapefruit | fentanyl | | 6421 | 2943 | 1953 | 2155 | grapefruit | fentanyl | +--------------------+----------+-------------+-----------+------------+----------+ ``` Si nos fijamos en el texto 2, veremos que en efecto hay tres menciones de la interacción entre el zumo de uva y el fentanil: >| Major Food Interaction||GENERALLY AVOID: Alcohol may potentiate the central nervous system (CNS) depressant effects of opioid analgesics including fentanyl. Concomitant use may result in additive CNS depression and impairment of judgment, thinking, and psychomotor skills. In more severe cases, hypotension, respiratory depression, profound sedation, coma, or even death may occur. **GENERALLY AVOID: Consumption of grapefruit juice during treatment with oral transmucosal formulations of fentanyl may result in increased plasma concentrations of fentanyl, which is primarily metabolized by CYP450 3A4 isoenzyme in the liver and intestine.** ***Certain compounds present in grapefruit are known to inhibit CYP450 3A4 and may increase the bioavailability of swallowed fentanyl (reportedly up to 75% of a dose) and/or decrease its systemic clearance.*** The clinical significance is unknown. In 12 healthy volunteers, consumption of 250 mL regular-strength grapefruit juice the night before and 100 mL double-strength grapefruit juice one hour before administration of oral transmucosal fentanyl citrate (600 or 800 mcg lozenge) did not significantly affect fentanyl pharmacokinetics, overall extent of fentanyl-induced miosis (miosis AUC), or subjective self-assessment of various clinical effects compared to control. However, pharmacokinetic alterations associated with interactions involving grapefruit juice are often subject to a high degree of interpatient variability. The possibility of significant interaction in some patients should be considered. MANAGEMENT: Patients should not consume alcoholic beverages or use drug products that contain alcohol during treatment with fentanyl. Any history of alcohol or illicit drug use should be considered when prescribing fentanyl, and therapy initiated at a lower dosage if necessary. Patients should be closely monitored for signs and symptoms of sedation, respiratory depression, and hypotension. **Due to a high degree of interpatient variability with respect to grapefruit juice interactions, patients treated with fentanyl should preferably avoid the consumption of grapefruit and grapefruit juice.** In addition, patients receiving transdermal formulations of fentanyl should be cautioned that drug interactions and drug effects may be observed for a prolonged period beyond removal of the patch, as significant amounts of fentanyl are absorbed from the skin for 17 hours or more after the patch is removed. | Entonces, lo que tenemos que hacer es **quedarnos con los `texts_ID` únicos y ver su `link`**. El problema es que **todas las entradas** hacen referencia a las mismas frases del mismo texto, para una misma interacción... :::info Por ahora voy a quedarme con las **URL únicas** y a contar cuántas hay. Luego veremos. Miraré también las **combinaciones URL-texto únicas**. ::: :::warning **Guardar la salida en fichero de texto** Para manejarme mejor he intentado guardar la salida en un fichero de texto, y me he topado con un error: ```sql=! SELECT * FROM texts INTO OUTFILE '~/texts.txt'; ``` ```! ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement ``` Si miramos la variable `secure_file_priv`, veremos que los ficheros se guardarían en un directorio para el cual no tenemos acceso: ```sql=! SHOW VARIABLES LIKE "secure_file_priv"; ``` ``` +------------------+-----------------------+ | Variable_name | Value | +------------------+-----------------------+ | secure_file_priv | /var/lib/mysql-files/ | +------------------+-----------------------+ 1 row in set (0,01 sec) ``` Y para el cual no podemos cambiar los permisos: ```bash! chmod u+rwx /var/lib/mysql-files/ ``` ``` chmod: changing permissions of '/var/lib/mysql-files/': Operation not permitted ``` Vamos a intentar solucionalo con [este tutorial](https://computingforgeeks.com/how-to-solve-mysql-server-is-running-with-the-secure-file-priv-error/). Voy a seguir la segunda opción: ```bash=! sudo gedit /etc/mysql/my.cnf # no tengo vim instalado aun ``` >**Nota:** por si acaso he guardado una copia del fichero `my.cnf` en el home. Añadimos las líneas: ``` [mysqld] secure-file-priv = "" ``` Reiniciamos mysqld: ```bash! sudo systemctl restart mysql ``` *** Solucionado! Guardamos la salida: ```sql SELECT * FROM texts INTO OUTFILE '/tmp/texts.txt'; ``` Le cambiamos los permisos: ```bash=! sudo chmod ugo+rwx /tmp/texts.txt ``` ::: Y listo! Lo he copiado al home para manejarlo más fácilmente. s un fichero delimitado por tabuladores y con tres columnas: el ID del texto, el texto como tal y la URL. Vamos a quedarnos con las URL únicas: ```bash cut -f3 texts.txt | sort | uniq | wc -l ``` Vemos que tenemos 2839 líneas --> solo 10 se repiten, y son: ```bash cut -f3 texts.txt | sort | uniq -d ``` ``` https://www.drugs.com/food-interactions/abemaciclib,verzenio.html?professional=1 https://www.drugs.com/food-interactions/fentanyl,sublimaze.html?professional=1 ``` Y ya por fin: ```bash cut -f3 texts.txt | sort | uniq | cut -d '/' -f3 | uniq -c ``` ``` 285 go.drugbank.com 2312 pubmed.ncbi.nlm.nih.gov 242 www.drugs.com ``` Si contamos con los que se repiten varias veces: ```bash cut -f3 texts.txt | sort | cut -d '/' -f3 | uniq -c ``` ``` 285 go.drugbank.com 2312 pubmed.ncbi.nlm.nih.gov 252 www.drugs.com ``` ### Número de perfiles GEO Contando accession únicos: :::spoiler ``` mysql> SELECT COUNT(DISTINCT accession) FROM study; +---------------------------+ | COUNT(DISTINCT accession) | +---------------------------+ | 161 | +---------------------------+ 1 row in set (0,00 sec) ``` La tabla tiene 161 filas, así que no se repite ningún estudio. En cuanto al **"número total de experimentos"**, contamos con 2996 nodos. Cada nodo es una combinación **estudio-tratamiento-muestra-concentración**. Creo que Enrique quiere también ver cuántas muestras de distintos tratamientos tenemos. No sé si esto podría referirse a combinaciones **tratamiento-estudio** únicas. Mirando en la tabla **`sample`**, tenemos 4873 combinaciones únicas estudio-tratamiento-muestra-concentración, y 3376 si excluimos los controles: ```sql! SELECT COUNT(DISTINCT study_id,treatment,concentration,sample_id) FROM sample; SELECT COUNT(DISTINCT study_id,treatment,concentration,sample_id) FROM sample WHERE treatment != 'control'; ``` Si ahora nos vamos a la tabla **`node`**, veremos que tenemos 510 `node_id` distintos y 2996 `sample_id` distintos (la tabla tiene 2996 filas). ```sql SELECT COUNT(DISTINCT node_id) FROM nodes; SELECT COUNT(DISTINCT sample_id) FROM nodes; ``` ::: :::info El número que le he dado a Enrique (**426 estudios**) sale de haber replicado la consulta Food-drug interactions con "resveratrol" usando MySQL y después haciendo una "búsqueda vacía": ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration, origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes NATURAL JOIN cmap_foodrugs WHERE nodes.node_id = cmap_foodrugs.node_id; -- ambigüedad de atributos -- 426 rows in set (0,02 sec) ``` Código completo: :::spoiler ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration, origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes NATURAL JOIN cmap_foodrugs WHERE nodes.node_id = cmap_foodrugs.node_id INTO OUTFILE '/tmp/FinalFooDrugs_v2_experiments_no_header.txt'; -- Query OK, 426 rows affected (0,02 sec) ``` La movemos a un sitio donde pueda modificarla para incluir los headers: ```bash # ejecutados en home sudo cp /tmp/FinalFooDrugs_v2_experiments_no_header.txt ./FooDrugs/ sudo chmod ugo+rw FooDrugs/FinalFooDrugs_v2_experiments_no_header.txt ``` Y se los añado manualmente, separados por tabuladores ``` accession node_id treatment time_point concentration origin_name ``` ```bash # no me dejaba enviar el original por slack :/ cp experiments_FinalFooDrugs_v2.txt all_experiments_FinalFooDrugs_v2.txt rm experiments_FinalFooDrugs_v2.txt ``` ::: Me ha costado un poco llegar hasta aquí. Algunas **notas:** * El set de 401 entradas que obtuve al principio venía de que no incluía el `origin_name` en la selección, de forma que en estudios donde se ha usado el mismo tratamiento, durante el mismo tiempo y a la misma concentracón en líneas celulares diferentes solo veíamos una entrada. * Siguiendo ese razonamiento, tendremos números diferentes quitando cualquiera de las otras categorías del `SELECT`. * Si hacemos la query eliminando el `NATURAL JOIN cmap_foodrugs` salen 513 entradas, tenemos entonces 86 estudios para los que no hay nodos en CMAP: ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration FROM study NATURAL JOIN sample NATURAL JOIN nodes; -- 513 rows in set (0,02 sec) ``` #### Replicando una búsqueda en FooDrugs Cuando entras en FooDrugs y buscas "resveratrol", sale: ![](https://i.imgur.com/xHdafV3.png) La query que se hace por debajo es: ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration, origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes NATURAL JOIN cmap_foodrugs WHERE nodes.node_id = cmap_foodrugs.node_id -- resuelve ambigüedad de atributos AND treatment = 'resveratrol' AND (tau >= 90 OR tau <= -90); ``` ``` +-----------+---------+-------------+------------+---------------+ | accession | node_id | treatment | time_point | concentration | +-----------+---------+-------------+------------+---------------+ | 32357 | 350 | resveratrol | 30 days | 150 mg/day | | 25412 | 351 | resveratrol | 48 hours | 150 mM | | 25412 | 352 | resveratrol | 48 hours | 250 mM | | 85871 | 353 | resveratrol | 12 hours | 10 µM | | 42432 | 354 | resveratrol | 30 days | 150 mg/day | | 78188 | 355 | resveratrol | 24 hours | - | +-----------+---------+-------------+------------+---------------+ 6 rows in set (0,03 sec) ``` :::success Creo que lo que quiere Enrique es la salida de: ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration, origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes NATURAL JOIN cmap_foodrugs WHERE nodes.node_id = cmap_foodrugs.node_id; -- el WHERE es recomendable al tener ambigüedad de atributos ``` ``` 426 rows in set (0,02 sec) ``` Es decir, la misma query sin especificar ningún tratamiento ni restringir tau. ::: :::info Según el archivo de Marco: ```sql SELECT DISTINCT study.accession, nodes.node_id, sample.treatment, sample.time_point, sample.concentration, sample.origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes RIGHT JOIN cmap_foodrugs ON nodes.node_id = cmap_foodrugs.node_id WHERE sample.treatment = ?; -- AND cell_line = ? ``` _"Cuando el usuario busca un fármaco con interacciones con compuestos alimenticios basados en evidencia molecular. Puedes añadir “`AND cell_line = ?`” si el usuario añade cell line como párametro de búsqueda también."_ ::: :::spoiler Me queda la duda, sin embargo, porque si quitamos el join con `cmap_foodrugs` salen más entradas: ```sql=! SELECT DISTINCT accession, node_id, treatment, time_point, concentration FROM study NATURAL JOIN sample NATURAL JOIN nodes; ``` ``` 513 rows in set (0,02 sec) ``` Asumo que esto puede ser porque tengamos nodos para los cuales no haya información en el connectivity map. Me guardo ambas para trastear: ```sql SELECT DISTINCT accession, node_id, treatment, time_point, concentration FROM study NATURAL JOIN sample NATURAL JOIN nodes ORDER BY node_id INTO OUTFILE '/tmp/njoin_513.txt'; SELECT DISTINCT accession, node_id, treatment, time_point, concentration -- meter study_id en el SELECT no cambia el n de entradas recuperadas FROM study NATURAL JOIN sample NATURAL JOIN nodes NATURAL JOIN cmap_foodrugs ORDER BY node_id INTO OUTFILE '/tmp/njoin_401.txt'; ``` Si miramos la cabecera de ambos ficheros: ![](https://i.imgur.com/CWiljxF.png) Vemos que en la query sin `cmap_foodrugs` aparecen los nodos 2, 4 y 5; y que en la otra no. De hecho, es un fichero de 513 líneas en el que el último `node_id` es el 519: solo faltan 6 nodos. En el otro, llegamos igualmente hasta el 519, pero solo tenemos 401 líneas: faltan 118 nodos. **Si buscamos estos nodos que faltan en `cmap_foodrugs`, vemos que no hay entradas con ese `node_id`:** ![](https://i.imgur.com/GLActKf.png) Mientras que, por ejemplo, para `node_id = 1` recuperamos 70895 filas. #### Otra aproximación Otra cosa que se me ha ocurrido es ver cuántos tratamientos diferentes tenemos por estudio: ```sql=! SELECT study_id,treatment,COUNT(sample_id) FROM sample GROUP BY study_id,treatment; -- sale lo mismo con SELECT DISTINCT ``` Con esto tenemos una tabla de 576 filas. Echemos un vistazo a las primeras líneas: ``` +----------+---------------------------+------------------+ | study_id | treatment | count(sample_id) | +----------+---------------------------+------------------+ | 0 | cranberry extract | 3 | | 0 | grapeseed extract | 3 | | 0 | control | 3 | | 0 | red wine extract | 3 | | 1 | control | 18 | | 1 | sulforaphane | 18 | | 2 | NULL | 28 | | 3 | control | 3 | | 3 | orange extract | 3 | | 4 | eusynstyelamide B | 3 | | 4 | control | 6 | ``` Si quitamos los controles, nos quedan 421 filas; 420 si quitamos un estudio en el que `treatment = '-'`. El NULL que sale arriba no sé de dónde sale porque al buscar `WHERE treatment != 'control'` ya deja de salir. De hecho para este estudio solo tenemos las columnas `sample_id`, `study_id` y `GSM`. No me termina de quedar claro qué diferencia hay entre esto y la "búsqueda vacía". Probando esto: ```sql=! SELECT DISTINCT study_id,accession,treatment,COUNT(sample_id),COUNT(node_id) FROM sample NATURAL JOIN study NATURAL JOIN nodes GROUP BY study_id,treatment ORDER BY accession; ``` Vemos que las columnas `COUNT(sample_id)` y `COUNT(node_id)` son iguales, eso sí, tenemos **418 filas**. ``` ----------+-----------+-------------------------------------------------------------------------------------------+------------------+----------------+ | study_id | accession | treatment | COUNT(sample_id) | COUNT(node_id) | +----------+-----------+-------------------------------------------------------------------------------------------+------------------+----------------+ | 69 | 5145 | vitamin D | 3 | 3 | | 0 | 5556 | cranberry extract | 3 | 3 | | 0 | 5556 | grapeseed extract | 3 | 3 | | 0 | 5556 | red wine extract | 3 | 3 | | 28 | 9647 | apple procyanidins extract | 3 | 3 | | 28 | 9647 | apple procyanidins extract + tnf-α | 3 | 3 | | 28 | 9647 | tnf-α | 3 | 3 | | 76 | 11355 | Lactobacillus plantarum dead | 8 | 8 | | 76 | 11355 | Lactobacillus plantarum midlog | 8 | 8 | | 76 | 11355 | Lactobacillus plantarum stationary | 8 | 8 | ``` Comparo las dos queries: ```sql=! SELECT DISTINCT study_id,accession,treatment,COUNT(sample_id),COUNT(node_id) FROM sample NATURAL JOIN study NATURAL JOIN nodes GROUP BY study_id,treatment ORDER BY accession; -- 418 filas SELECT DISTINCT accession, node_id, treatment, time_point, concentration FROM study NATURAL JOIN sample NATURAL JOIN nodes ORDER BY accession; -- 513 filas ``` Aquí veremos que hay diferencias porque en la segunda búsqueda diferenciamos también muestras con distintos tiempos o concentraciones, mientras que la primera lo agrupa todo junto. Podríamos sacar lo mismo si además agrupásemos por `time_point` y `concentration`: ```sql SELECT DISTINCT study_id,accession,treatment,time_point,concentration,COUNT(sample_id) FROM sample NATURAL JOIN study NATURAL JOIN nodes GROUP BY study_id,treatment,time_point,concentration ORDER BY accession; -- 513 filas ``` Y para replicar la búsqueda de FooDrugs: ```sql SELECT study_id,accession,treatment,time_point,concentration,COUNT(sample_id),COUNT(node_id) FROM sample NATURAL JOIN study NATURAL JOIN nodes WHERE treatment = 'resveratrol' GROUP BY study_id,treatment,time_point,concentration ORDER BY accession; ``` :::success En esta búsqueda **no tenemos controles** al estar haciendo el join con la tabla `nodes`. ::: :::info - [x] Enseñarle esto a Enrique y ver si es por aquí por donde van los tiros. - [x] Preguntarle si puedo ir mirando algo más de esto. ::: ## Entendiendo las consultas ### Fármaco que interacciona con un compuesto alimenticio Acabamos de verlo: ```sql SELECT DISTINCT study.accession, nodes.node_id, sample.treatment, sample.time_point, sample.concentration, sample.origin_name FROM study NATURAL JOIN sample NATURAL JOIN nodes RIGHT JOIN cmap_foodrugs ON nodes.node_id = cmap_foodrugs.node_id WHERE sample.treatment = ? -- AND cell_line = ? ``` * El símbolo `?` en `WHERE sample.treatment = ?` sirve como _placeholder_: ahí irá lo que introduzca el usuaio en la barra de búsqueda. :::warning - [ ] Queda resolver **por qué hace un `RIGHT JOIN`** en lugar de otro `NATURAL JOIN` ::: ### Elección de un alimento específico Si tras la búsqueda que acabamos de hacer hacemos clic en la primera fila: ![](https://i.imgur.com/hbdrToo.png) Estamos encontrando información para el nodo 350. Para replicar estos resultados necesitamos usar este comando: ```sql SELECT node_id, cmap_node_id, tau, compound, cell_line FROM cmap_foodrugs NATURAL JOIN cmap WHERE node_id = 350 AND ABS(tau) >= 90 AND pert_type = 'trt_cp' ORDER BY ABS(tau) DESC LIMIT 10; ``` ``` +---------+--------------+----------+----------------+-----------+ | node_id | cmap_node_id | tau | compound | cell_line | +---------+--------------+----------+----------------+-----------+ | 350 | 66114 | -99.9636 | goserelin | HT29 | | 350 | 10272 | -99.9562 | proadifen | VCAP | | 350 | 51780 | 99.9263 | eliprodil | HCC515 | | 350 | 11398 | 99.8821 | iproniazid | HCC515 | | 350 | 56906 | 99.882 | SB-216763 | A375 | | 350 | 7296 | -99.8701 | BRD-K00256256 | A375 | | 350 | 55115 | 99.8111 | SU-1498 | A375 | | 350 | 29143 | 99.7939 | hydroxyfasudil | HT29 | | 350 | 61490 | -99.7789 | CGP-54626 | HCC515 | | 350 | 12281 | -99.6964 | esomeprazole | HT29 | +---------+--------------+----------+----------------+-----------+ 10 rows in set (0,20 sec) ``` :::success Vemos que esto coincide con la query que venía en el documento: ```sql SELECT DISTINCT cmap_foodrugs.*, cmap.compound, cmap.cell_line, REPLACE(REPLACE(cmap.pert_type, 'trt_sh.cgs', 'Consensus signature FROM shRNAs targeting the same gene'), 'trt_oe', 'cDNA for overexpression of wild-type gene') as pert_type FROM cmap_foodrugs NATURAL JOIN cmap WHERE cmap_foodrugs.node_id = ? AND abs(tau) >= ?; ``` > "Cuando el usuario escoge un alimento especifico. A continuacion se añade `AND ( cmap.pert_type = 'trt_cp' or cmap.pert_type = 'trt_lig' )` si busca **ali-fármaco** o `AND (cmap.pert_type = 'trt_oe' or cmap.pert_type = 'trt_oe.mut' or cmap.pert_type = 'trt_sh' or cmap.pert_type = 'trt_sh.cgs' or cmap.pert_type = 'trt_sh.css' )` si busca **ali-gen**. Al final se añade `order by abs(cmap_foodrugs.tau) desc`" :::spoiler Para un ejemplo **ali-gen:** ![](https://i.imgur.com/QZqz9Pe.png) ```sql SELECT DISTINCT cmap_foodrugs.*, cmap.compound, cmap.cell_line, REPLACE(REPLACE(cmap.pert_type, 'trt_sh.cgs', 'Consensus signature FROM shRNAs targeting the same gene'), 'trt_oe', 'cDNA for overexpression of wild-type gene') as pert_type FROM cmap_foodrugs NATURAL JOIN cmap WHERE cmap_foodrugs.node_id = 350 AND abs(tau) >= 90 AND (cmap.pert_type = 'trt_oe' or cmap.pert_type = 'trt_oe.mut' or cmap.pert_type = 'trt_sh' or cmap.pert_type = 'trt_sh.cgs' or cmap.pert_type = 'trt_sh.css' ) ORDER BY ABS(tau) DESC LIMIT 10; ``` ``` +---------+--------------+----------+----------+-----------+---------------------------------------------------------+ | node_id | cmap_node_id | tau | compound | cell_line | pert_type | +---------+--------------+----------+----------+-----------+---------------------------------------------------------+ | 350 | 46711 | 99.9728 | DCK | HEPG2 | cDNA for overexpression of wild-type gene | | 350 | 61603 | 99.9562 | DDX10 | VCAP | Consensus signature FROM shRNAs targeting the same gene | | 350 | 45768 | 99.9562 | ZNF385B | VCAP | Consensus signature FROM shRNAs targeting the same gene | | 350 | 18934 | 99.9519 | IL18RAP | PC3 | cDNA for overexpression of wild-type gene | | 350 | 2711 | 99.9514 | SLC7A1 | MCF7 | cDNA for overexpression of wild-type gene | | 350 | 16116 | 99.9479 | FOXP3 | HT29 | cDNA for overexpression of wild-type gene | | 350 | 37783 | 99.9415 | NGRN | HA1E | Consensus signature FROM shRNAs targeting the same gene | | 350 | 43972 | 99.941 | MAP3K8 | HCC515 | Consensus signature FROM shRNAs targeting the same gene | | 350 | 36718 | -99.941 | NRP1 | HCC515 | cDNA for overexpression of wild-type gene | | 350 | 5673 | -99.9344 | BDKRB2 | VCAP | Consensus signature FROM shRNAs targeting the same gene | +---------+--------------+----------+----------+-----------+---------------------------------------------------------+ 10 rows in set (0,21 sec) ``` ::: ### Compuesto alimenticio que interacciona con fármacos ![](https://i.imgur.com/yj59fgy.png) Probamos con: ```sql SELECT nodes.node_id, cmap.compound, cmap.cell_line, study.accession FROM cmap NATURAL JOIN cmap_foodrugs NATURAL JOIN nodes NATURAL JOIN sample NATURAL JOIN study WHERE cmap.cmap_node_id = cmap_foodrugs.cmap_node_id AND cmap_foodrugs.node_id = nodes.node_id AND nodes.sample_id = sample.sample_id AND sample.study_id = study.study_id AND cmap.compound = 'ibuprofen' AND (tau >= 90 OR tau >= -90); ``` Esto tarda mucho. Si miramos el documento vemos que la solución es mucho más sencilla: ```sql SELECT DISTINCT cmap_node_id AS node_id, compound, cell_line FROM cmap WHERE cmap.compound = ? AND (cmap.pert_type = 'trt_cp') ``` :::warning * Aquí lo que no me queda claro es que este resultado realmente no te enseña con qué alimentos interacciona el fármaco... * Además, en `node_id` en realidad vemos el `cmap_node_id`, no es un poco confuso? (para mí lo es) ::: Para intentar resolver estas dudas voy a obtener los `node_id` correspondientes a los 8 `cmap_node_id` que tenemos en los resultados: ```sql SELECT DISTINCT cmap_node_id, node_id, compound, cell_line FROM cmap NATURAL JOIN cmap_foodrugs WHERE cmap.compound = 'ibuprofen' AND cmap.pert_type = 'trt_cp'; ``` :::warning Y vemos que ahora nos salen **3184 filas** en vez de 8, porque cada nodo del CMAP se conecta con varios nodos nuestros (es decir, un solo compuesto del CMAP muestra interacciones con numerosos de los tratamientos de los estudios recogidos). Así, esta query nos dice **que este fármaco tiene interacciones con alimentos de los estudios recogidos**, pero no nos dice con cuáles (podrían ser muchos). ::: :::info **Columna `pert_type`** Esta columna tiene 5 valores posibles: ``` mysql> select distinct pert_type from cmap; +-------------+ | pert_type | +-------------+ | trt_oe | | trt_cp | | trt_sh.cgs | | ctl_vector | | ctl_vehicle | +-------------+ 5 rows in set (0,02 sec) ``` En la [documentación de CMAP](https://clue.io/connectopedia/perturbagen_types_and_controls) podemos ver que `pert_type` viene de _**perturbagen type**_: >"A perturbagen is a (chemical or genetic) reagent used in the laboratory to treat cells and measure the resulting biological response. In the case of CMap, changes in gene expression are measured following treatment with a perturbagen." Hay más valores posibles en esta columna más allá de los 5 que tenemos nosotros. Centrándonos en estos 5 (el resto están en el link): * **`trt_oe`**: cDNA for overexpression of wild-type gene * **`trt_cp`**: Compound * **`trt_sh.cgs`**: Consensus signature from shRNAs targeting the same gene * **`ctl_vector`**: Controls - vector for genetic perturbation (e.g empty vector, GFP) * **`ctl_vehicle`**: Controls - vehicle for compound treatment (e.g DMSO) ::: ### Elección de un nodo concreto Si tras la búsqueda anterior seleccionamos la quinta fila: ![](https://i.imgur.com/UI3VR8E.png) Es lo mismo que esta query: ```sql SELECT DISTINCT treatment, concentration, time_point, origin_name, cmap_foodrugs.* FROM cmap_foodrugs NATURAL JOIN nodes NATURAL JOIN sample WHERE cmap_node_id = '59203' AND ABS(tau) >= 90 ORDER BY ABS(tau) DESC; ``` ``` +---------------------------------------+---------------+------------+-------------------------------+---------+--------------+----------+ | treatment | concentration | time_point | origin_name | node_id | cmap_node_id | tau | +---------------------------------------+---------------+------------+-------------------------------+---------+--------------+----------+ | wortmannin | - | 24 hours | CUTLL1 | 512 | 59203 | 98.7589 | | 3-o-acetyl-β-boswellic_acid | 46 μg/ml | 6-8 hours | MDA-MB-231 | 12 | 59203 | -97.4848 | | tcdc | - | 11 days | Epithelium  | 412 | 59203 | 97.3318 | | phenoxibenzamine | - | 24 hours | CUTLL1 | 321 | 59203 | 96.2468 | | after 2 weeks milk | - | - | Whole blood | 25 | 59203 | 94.7371 | | H202 | 300µM | 24 hours | SK-N-MC | 208 | 59203 | -94.528 | | boswellia serrata extract | 128 µg/mL | 6-8 hours | MDA-MB-231 | 76 | 59203 | -94.0978 | | vitamin D | 100 nM | 24 hours | RWPE-1 | 480 | 59203 | 93.3946 | | fruit and vegetable juice concentrate | - | 8 weeks | PBMC | 164 | 59203 | -93.126 | | ferulic acid | - | 6 hours | breast cancer cell line MCF-7 | 147 | 59203 | 92.9195 | +---------------------------------------+---------------+------------+-------------------------------+---------+--------------+----------+ 10 rows in set (0,01 sec) ``` :::success Lo que tenemos en el excel es: ```sql SELECT DISTINCT sample.treatment, sample.concentration, sample.time_point, sample.origin_name, cmap_foodrugs.* FROM study NATURAL JOIN sample NATURAL JOIN nodes RIGHT JOIN cmap_foodrugs ON nodes.node_id = cmap_foodrugs.node_id NATURAL JOIN cmap WHERE cmap_foodrugs.cmap_node_id = ? AND abs(tau) >= ? ``` > Cuando el usuario escoge un farmaco especifico. A continuacion se añade `AND ( cmap.pert_type = 'trt_cp' or cmap.pert_type = 'trt_lig' )` y `order by abs(cmap_foodrugs.tau) desc` :::warning - [ ] Queda ver por qué se está hacendo el `RIGHT JOIN` ::: ### Text mining Buscamos "resveratrol". Nos salen entradas con resveratrol tanto en la columna `food` como en `drug`, y con distintas mayúsculas / minúsculas: ![](https://i.imgur.com/9DMUS3V.png) ![](https://i.imgur.com/My2Qlqa.png) :::warning creo que aquí me quedé a medias ::: ## Entendiendo la tabla `nodes` Vamos a empezar por las dos muestras a las que se les ha asignado el `node_id = 1`: ```sql select * from nodes limit 3; ``` ``` +---------+-----------+ | node_id | sample_id | +---------+-----------+ | 1 | 4487 | | 1 | 4503 | | 2 | 1136 | +---------+-----------+ 3 rows in set (0,00 sec) ``` ```sql select * from sample where sample_id = 4487; ``` ``` +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ | sample_id | study_id | treatment | origin_type | origin_name | GSM | time_point | concentration | +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ | 4487 | 133 | prodrug g668684 | cell line | HCT-116 | 767904 | 72 hours | 150 nm | +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ 1 row in set (0,01 sec) ``` ```sql select * from sample where sample_id = 4503; ``` ``` +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ | sample_id | study_id | treatment | origin_type | origin_name | GSM | time_point | concentration | +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ | 4503 | 133 | prodrug g668684 | cell line | HCT-116 | 767903 | 72 hours | 150 nm | +-----------+----------+------------------+-------------+-------------+--------+------------+---------------+ 1 row in set (0,00 sec) ``` Vemos que solo cambian las columnas `GSM` y `sample_id`: los **identificadores de las muestras**. Probando con `node_id = 2` sacamos 160 entradas, que se quedan en 80 si hacemos: ```sql select distinct study_id, treatment, origin_type, origin_name, GSM, time_point, concentration from sample natural join nodes where node_id =2; ``` Si quitamos `GSM` de la query anterior, obtenemos una sola fila. Si ahora hacemos esto: ```sql select distinct node_id, study_id, treatment, origin_type, origin_name, time_point, concentration from sample natural join nodes; ``` Recuperamos **571** entradas, de manera que hay `node_id` repetidos. Me llevo esto a un fichero para echar un vistazo. :::warning El `node_id = 74` no existe, el `465` tampoco... y así con más ::: ### A partir de excel Me he quedado con la tabla que sale de la última query (combinaciones únicas de ID de nodo, ID de estudio, tratamiento, tipo de origen y nombre, tiempo y concentración) y me he dedicado a buscar **muestras distintas con el mismo ID de nodo**. Tenemos 510 `node_id` distintos y 2996 `sample_id` distintos en la tabla `nodes`, así que algo no cuadra. :::info Aquí ya estamos filtrando mucho, porque puede haber duplicados por ejemplo que no vamos a ver al tener todo igual salvo por los identificadores de muestra (`sample_id` y `GSM`) ::: Aquí vemos que hay filas repetidas, por ejemplo, para muestras donde lo único que cambia es el `origin_name`, a veces junto con `origin_type`; y unas pocas donde lo único que cambia es el tiempo. :::danger De esto lo que me raya es que se supone que los nodos son combinaciones **tiempo, concentración y tratamiento** únicas, y estamos viendo que hay veces donde dos muestras con distintos tiempos se han asignado al mismo nodo... Nodos 265, 302 y 427. Esto **no es un problema de las muestras con `time_point = 0`.** Por ejemplo, los nodos 357 y 203 (estudio 52) únicamente tienen muestras con ese tiempo, sin mezclar con otras muestras del estudio. El nodo 357 solo tiene muestras de t = 0, y en ese mismo estudio se miran también 6 meses y 12 meses. El nodo 203 es lo mismo, porque es el mismo estudio solo que con otro compuesto. ![](https://i.imgur.com/H5YG8Ve.png) Ejemplo: :::spoiler ``` mysql> select * from nodes natural join sample where node_id = 265; +-----------+---------+----------+--------------+-------------+-------------+--------+------------+---------------+ | sample_id | node_id | study_id | treatment | origin_type | origin_name | GSM | time_point | concentration | +-----------+---------+----------+--------------+-------------+-------------+--------+------------+---------------+ | 354 | 265 | 15 | low fat diet | tissue | PBMC | 701130 | 3 months | - | | 357 | 265 | 15 | low fat diet | tissue | PBMC | 701133 | 0 | - | | 358 | 265 | 15 | low fat diet | tissue | PBMC | 701136 | 3 months | - | | 360 | 265 | 15 | low fat diet | tissue | PBMC | 701139 | 0 | - | | 363 | 265 | 15 | low fat diet | tissue | PBMC | 701142 | 0 | - | | 364 | 265 | 15 | low fat diet | tissue | PBMC | 701145 | 3 months | - | | 369 | 265 | 15 | low fat diet | tissue | PBMC | 701135 | 0 | - | | 374 | 265 | 15 | low fat diet | tissue | PBMC | 701138 | 3 months | - | | 378 | 265 | 15 | low fat diet | tissue | PBMC | 701141 | 0 | - | | 381 | 265 | 15 | low fat diet | tissue | PBMC | 701144 | 0 | - | | 382 | 265 | 15 | low fat diet | tissue | PBMC | 701147 | 3 months | - | | 385 | 265 | 15 | low fat diet | tissue | PBMC | 701126 | 0 | - | | 386 | 265 | 15 | low fat diet | tissue | PBMC | 701127 | 3 months | - | | 391 | 265 | 15 | low fat diet | tissue | PBMC | 701143 | 3 months | - | | 393 | 265 | 15 | low fat diet | tissue | PBMC | 701129 | 3 months | - | | 395 | 265 | 15 | low fat diet | tissue | PBMC | 701146 | 0 | - | | 399 | 265 | 15 | low fat diet | tissue | PBMC | 701152 | 3 months | - | | 410 | 265 | 15 | low fat diet | tissue | PBMC | 701140 | 3 months | - | | 413 | 265 | 15 | low fat diet | tissue | PBMC | 701128 | 3 months | - | | 414 | 265 | 15 | low fat diet | tissue | PBMC | 701134 | 3 months | - | | 415 | 265 | 15 | low fat diet | tissue | PBMC | 701137 | 0 | - | +-----------+---------+----------+--------------+-------------+-------------+--------+------------+---------------+ 21 rows in set (0,00 sec) ``` ::: También sobra una fila correspondiente al estudio 27, donde en lugar de control pone `'ref'`. Lo mismo con el 40. Los nodos se han sacado de una forma muy parecida a esto: ```sql select distinct study_id, time_point, concentration, treatment from sample where treatment != 'control'; -- 530 entradas, sobran 20 select distinct study_id, time_point, concentration, treatment from sample where treatment != 'control' into outfile '/tmp/distinct_study_tp_conc_ttmt.txt'; select distinct node_id, study_id, treatment, time_point, concentration from sample natural join nodes into outfile '/tmp/nodes_513.txt'; -- Query OK, 513 rows affected (0,01 sec) ``` :::success Sacamos estas dos tablas, miramos cuántas veces se repite cada `study_id` y miramos aquellas filas donde no coincida. Si sumamos las diferencias da 17. La cosa está en que en la tabla que hemos hecho nosotros manualmente sin mirar `nodes` hay 17 filas que en la otra no están. Vamos a ver qué pasa... :male-detective: ::: ```r setwd('~/FooDrugs') library(dplyr) # nodes.tab contains the result of a natural join operation between the # sample and the nodes tables in FooDrugs database: nodes.tab <- read.table('nodes_513.txt', header = FALSE, sep = '\t', na.strings = '-') colnames(nodes.tab) <- c('node_id', 'study_id', 'treatment', 'time_point', 'concentration') # distinct.tab contains the result of a select operation within the # sample table selecting unique study-time point-concentration-treatment # combinations, **excluding controls** distinct.tab <- read.table('distinct_study_tp_conc_ttmt.txt', header = FALSE, sep = '\t', na.strings = '-') colnames(distinct.tab) <- c('study_id', 'time_point', 'concentration', 'treatment') # now we will compare the number of times that each study_id appears # in both tables: nodes.times <- nodes.tab %>% count(study_id) distinct.times <- distinct.tab %>% count(study_id) times <- nodes.times %>% left_join(distinct.times, by = 'study_id') colnames(times) <- c('study_id', 'nodes', 'distinct') times$equal <- times['nodes'] == times['distinct'] # see difference: tmp <- times %>% filter(equal == FALSE) tmp[distinct] - tmp[nodes] tmp$distinct - tmp$nodes sum(tmp$distinct - tmp$nodes) # we can see that the difference equals 17, mistery solved! ``` ``` study_id nodes distinct nodes 1 17 4 7 FALSE 2 27 1 2 FALSE 3 40 1 2 FALSE 4 80 3 4 FALSE 5 96 1 2 FALSE 6 103 2 3 FALSE 7 110 1 2 FALSE 8 113 1 2 FALSE 9 115 1 2 FALSE 10 117 6 9 FALSE 11 141 3 6 FALSE ``` - **17**: faltan 3 con `time_point = 0` - **27**: la que falta yo creo que es un control, solo que pone `treatment = ref` en lugar de control - **40**: `treatment = ref` - **80**: falta fila com `time_point = 0` - **96**: falta fila com `time_point = 0` - **103**: `treatment = ref` - **110**: falta fila com `time_point = 0` - **113**: falta fila com `time_point = 0` - **115**: falta fila com `time_point = 0` - **117**: faltan 3 con `time_point = 0` - **141**: faltan 3 con `time_point = 0` :::danger Al buscar: ```r nodes.tab %>% filter(time_point == 0) ``` Salen 6 filas: ``` node_id study_id treatment time_point concentration 1 203 52 grape extract 0 8 mg 2 265 15 low fat diet 0 <NA> 3 302 15 nuts 0 <NA> 4 357 52 resveratrol-enriched grape extract 0 8 mg 5 427 15 Traditional Mediterranean Diet enriched with virgin olive oil 0 <NA> ``` No sé si las que fallan son estas o las que hemos visto antes: **¿los nodos con `time_point = 0` deben existir o no?** Además, los nodos 265. 302 y 427 tienen dos combinaciones distintas asignadas, porque se les ha asignado la muestra com `time_point = 3 months` y también la muestra con `time_point = 0` ::: ## Tabla Enrique ### Duda condiciones Enrique me ha pedido que considere que cada condición incluya también los `origin_name` distintos, excluyendo controles. Entonces ahora nos surge una nueva duda: al hacer los "nodos" manualmente nos salen más filas que al hacer el natural join con nodes: ```sql -- hacemos los nodos manualmente: 585 filas SELECT DISTINCT study_id, treatment, time_point, concentration, origin_name, origin_type FROM study NATURAL JOIN sample WHERE treatment != 'control' and treatment != 'ref' INTO OUTFILE '/tmp/origin_585.txt'; -- 585 filas ``` ```sql -- natural join con nodes: 571 filas SELECT DISTINCT study_id, node_id, treatment, time_point, concentration, origin_name, origin_type FROM study NATURAL JOIN sample NATURAL JOIN nodes INTO OUTFILE '/tmp/origin_571.txt'; ``` Vamos a hacer lo mismo de mirar en R los sitios donde haya diferencias. Primero sacamos las tablas: ``` mysql> SELECT DISTINCT study_id, node_id, treatment, time_point, concentration, origin_name, origin_type -> FROM study NATURAL JOIN sample NATURAL JOIN nodes -> INTO OUTFILE '/tmp/origin_571.txt'; Query OK, 571 rows affected (0,01 sec) mysql> SELECT DISTINCT study_id, treatment, time_point, concentration, origin_name, origin_type -> FROM study NATURAL JOIN sample -> WHERE treatment != 'control' and treatment != 'ref' -> INTO OUTFILE '/tmp/origin_585.txt'; Query OK, 585 rows affected (0,04 sec) ``` Y corremos el script exactamente igual. Sale esta tabla: ``` study_id nodes distinct nodes 1 17 4 7 FALSE 2 80 3 4 FALSE 3 96 1 2 FALSE 4 110 1 2 FALSE 5 113 1 2 FALSE 6 115 1 2 FALSE 7 117 6 9 FALSE 8 141 3 6 FALSE ``` :::success Vemos que son los mismos estudios que antes, quitando los de `treatment = 'ref'`. Es decir, **nos faltan nodos con `time_point = 0`**, que según lo hablado con Enrique sí deben existir. **Por ello para el report voy a trabajar con 585 condiciones.** ::: ### Texto A total of ~~119~~ **161** GEO series with ~~334~~ **585** conditions for food or bioactive compounds were retrieved following the strategy described above. **Each condition is defined as a food/biocomponent per time point per concentration per cell line (or tissue, or primary cell) per study.** Characteristics of these series are shown in Table XXX. :::info El **426** sale de una búsqueda vacía hecha según se veía [aquí](https://hackmd.io/zw-eHRfgT-GWhNebtAh7AA?view#N%C3%BAmero-de-perfiles-GEO). El **510** sale del número de nodos (`node_id` únicos). ::: One-color arrays ~~92~~ **131** Two-color arrays <font color = 'red'> ~~15~~ **10** </font> RNA seq ~~12~~ **20** :::info Esto sale de: ```sql select study_type,count(distinct study_id) from study group by study_type; ``` ``` +-------------------+--------------------------+ | study_type | count(distinct study_id) | +-------------------+--------------------------+ | HTS | 20 | | One color array | 131 | | Two channel array | 10 | +-------------------+--------------------------+ 3 rows in set (0,00 sec) ``` :::danger Esto es un poco raro. Perdemos 5 estudios de arrays de dos colores... - [ ] Buscar GEO accession en TFM / Preguntar a Enrique - [ ] Si no... Se me ocurre mirar si en la versión antigua que me pasó David pasa lo mismo ::: Studies conducted on cell lines ~~63~~ **96** Studies conducted on primary cells ~~66~~ **27** Studies conducted on biopsies ~~10~~ **41** :::info Si hacemos lo mismo que acabamos de ver pero con `origin_type` vemos que vamos a tener que mirar alguna cosa manualmente: ```sql select origin_type, count(distinct study_id) from sample group by origin_type; ``` ``` +-----------------------------------------------+--------------------------+ | origin_type | count(distinct study_id) | +-----------------------------------------------+--------------------------+ | NULL | 1 | | generated from H9 human embryonic stem cells | 1 | | vehicle treated | 1 | | vorinostat treated | 1 | | - | 3 | | CCD-18Co | 1 | | cel type | 1 | | cell line | 81 | | cell type | 34 | | regulatory T cell | 1 | | tissue | 40 | +-----------------------------------------------+--------------------------+ 11 rows in set (0,02 sec) ``` Hemos ido mirando los estudios que no coincidían con `cell_line`, `cell_type` ni con `tissue`. Ahora queda mirar estas tres categorías: - en `cell_line` solo hay líneas celulares; - en `cell_type` hay línas celulares y células primarias; - en `tissue` hay biopsias y células primarias. Voy a sacar una tablita para ir haciendo cálculos: ```sql select distinct origin_name, accession from sample natural join study where origin_type = 'tissue' or origin_type = 'cell type' order by origin_name into outfile '/tmp/cell_type_or_tissue.txt'; -- Query OK, 84 rows affected (0,01 sec) ``` Y ahora vamos a ir eliminando cosas. 1. Sabemos que todo lo que tenga `primary` son **células primarias:** ```bash grep -i 'primary' cell_type_or_tissue.txt # 5 todos distintos grep -iv 'primary' cell_type_or_tissue.txt > ctt2.txt ``` Vemos que hay mezcla de `cell type` y `tissue` en **`origin_type`**: ``` (base) blanca@totoro:~/FooDrugs$ grep 'primary' ctt_3cols.txt cell type primary chondrocytes 75181 tissue primary human bronchial smooth muscle cells 5145 cell type primary myotubes 18589 tissue primary tumor sample 110248 ``` 2. Todo lo que contenga 'blood' o 'PBMC' viene de una **biopsia líquida**: ```bash grep -iE 'blood|PBMC' ctt2.txt # funciona bien, todos unicos grep -iEc 'blood|PBMC' ctt2.txt # 20 lineas grep -iEv 'blood|PBMC' ctt2.txt > ctt3.txt ``` Aquí también tenemos mezcla. 3. Hay algunas cosas que sabemos que son **cell lines**: - ARPE-19 Cells - BEAS-2B immortalized human bronchial epithelium - CACO-2 - HUVEC - LNCaP - MCF7 - PC-3 - Raji cells - SUDHL10 Cells - SUDHL2 Cells ```bash grep -iEc 'arpe|beas-2b|caco-2|huvec|lncap|mcf7|pc-3|raji|sudhl' ctt3.txt # 10 lineas, 7 unicos grep -iEv 'arpe|beas-2b|caco-2|huvec|lncap|mcf7|pc-3|raji|sudhl' ctt3.txt > ctt4.txt ``` Ya solo nos queda discriminar **biopsia _vs._ primarias**. 4. En `classification_cl_prim_biopsy.ods` pude verse el resto de la clasificación (hecha manualmente) Otro: - PrEC **creo** que son **primarias** cotilleando el [fabricante](https://bioscience.lonza.com/lonza_bs/CH/en/Primary-and-Stem-Cells/p/000000000000185069/PrEC-%E2%80%93-Human-Prostate-Epithelial-Cells) - RCC PDX tumor son **primarias** pero derivadas de una **biopsia**... ([link](https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSM1973613)) --> para mí esto cuenta como primarias Para confimar he pasado todo a ficheros independientes (uno para cada cosa clasificada) y hacemos el `sort` + `uniq`. ```bash= grep -i 'primary' ctt_3cols.txt > primary.txt grep -iE 'arpe|beas-2b|caco-2|huvec|lncap|mcf7|pc-3|raji|sudhl' ctt_3cols.txt > cell_line.txt grep -iE 'blood|PBMC' ctt_3cols.txt > biopsy.txt # pego manualmente las tablas del excel # pego manualmente las del cuaderno ``` :::danger - He decidido que si en algún sitio pone que se aisló algún tipo celular concreto es **primary** y si no, es **biopsy** - Muestras de leche y heces son **biopsy** - tejido 3D y células reprogramadas son **cell line** ::: Number of different GEO platforms ~~48~~ **69** :::info Aquí hacemos: ```sql select distinct gpl from study; ``` Y vemos que con esto no vale, porque hay estudios con más de una plataforma. Pongo las primeras 5 entradas que se recuperan: ``` +-------------------------------+ | gpl | +-------------------------------+ | 570 | | 11154 | | 6104 | | 16604 | | 1;7;6;9;2 | ``` Toca entonces volver a sacar un fichero de texto. ```sql select distinct gpl from study into outfile '/tmp/gpl.txt'; ``` Ahora es muy sencillo calcular el número de valores diferentes en bash: ```bash cat gpl.txt | tr ';' '\n' | sort | uniq | wc -l # salida: 69 ``` ::: Series which study more than one compound ~~34~~ **66** :::info Aquí me salen bastantes más, lo he hecho de esta forma: ```sql select study_id, count(distinct treatment) from sample where treatment != 'control' and treatment != 'ref' group by study_id having count(distinct treatment) > 1; -- 66 rows in set ``` Probamos otro approach por confirmar: ```sql select distinct study_id, treatment from sample where treatment != 'control' and treatment != 'ref' into outfile '/tmp/tmp.txt'; ``` ```bash cut -f1 tmp.txt | uniq -d | wc -l # 66 ``` ::: Series which study more than one concentration ~~5~~ **39** :::info ```sql select study_id, count(distinct concentration) from sample where treatment != 'control' and treatment != 'ref' group by study_id having count(distinct concentration) > 1; -- 39 rows ``` ::: Series which study more than one time point ~~14~~ **33** :::info ```sql select study_id, count(distinct time_point) from sample where treatment != 'control' and treatment != 'ref' group by study_id having count(distinct time_point) > 1; ``` ::: :::success Todo esto lo he hecho también en `compare_tables.R` y los números coinciden :confetti_ball: ```r= # more than 1 treatment by.treatment <- cl.distinct.tab %>% select(study_id, treatment) %>% distinct() %>% count(study_id) by.treatment %>% filter(n > 1) # 66 rows --> OK # more than 1 concentration by.concentration <- cl.distinct.tab %>% select(study_id, concentration) %>% distinct() %>% count(study_id) by.concentration %>% filter(n > 1) # 39 rows --> OK # more than 1 time point by.time_point <- cl.distinct.tab %>% select(study_id, time_point) %>% distinct() %>% count(study_id) by.time_point %>% filter(n > 1) # 33 rows --> OK ``` ::: Table XXX.- Characteristics of GEO series retrieved ## Creación de tablas - trigger en tabla `sample` :::spoiler ```sql -- -- Table structure for table `sample` -- DROP TABLE IF EXISTS `sample`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `sample` ( `sample_id` int NOT NULL AUTO_INCREMENT, `study_id` int NOT NULL, `treatment` varchar(163) DEFAULT 'control', `origin_type` varchar(50) DEFAULT 'cell line', `origin_name` varchar(150) DEFAULT NULL, `GSM` int NOT NULL, `time_point` varchar(50) DEFAULT NULL, `concentration` varchar(150) DEFAULT '0', PRIMARY KEY (`sample_id`), KEY `study_id` (`study_id`), CONSTRAINT `sample_ibfk_1` FOREIGN KEY (`study_id`) REFERENCES `study` (`study_id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=5069 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sample` -- LOCK TABLES `sample` WRITE; /*!40000 ALTER TABLE `sample` DISABLE KEYS */; INSERT INTO `sample` VALUES (0,0,'cranberry extract','cell type','endothelial cells',129143,'6 hours','-'), --... /*!40000 ALTER TABLE `sample` ENABLE KEYS */; UNLOCK TABLES; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`marco2`@`localhost`*/ /*!50003 TRIGGER `create_node` AFTER INSERT ON `sample` FOR EACH ROW begin declare id_to_insert integer; declare is_occupied integer; case when (NEW.concentration != "0" and NEW.time_point != "0") and (NEW.treatment != "control") then set is_occupied :=(SELECT EXISTS (SELECT 1 FROM `nodes`)); case when is_occupied = 1 then set id_to_insert := (select COALESCE( ( select distinct node_id from sample natural join nodes where treatment = NEW.treatment and time_point = NEW.time_point and concentration = NEW.concentration LIMIT 1), (select max(node_id) from nodes) + 1)); else set id_to_insert=0; end case; insert into nodes(node_id, sample_id) values( id_to_insert, NEW.sample_id); else begin end; end case; end */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; ``` ::: # Pipeline de transcriptómica ## Lista de scripts 1. A partir de una lista de GSEs, se corre el pipeline de análisis diferencial desde el script **`runLimmaFromGSEList.sh`** para estudios de microarrays o desde el script **`runLimmaHTSFromGSEList.sh`** para estudios de transcriptómica. 2. Estos scripts a su vez llaman a otros scripts: - `create_DM.py` para generar la matriz de diseño y matriz de nodos - `DownloadDataMatrix.sh` para descargar datos CEL en el caso de los estudios de microarrays (en transciptómica asume `HTS_gene_counts/numerogse_counts.tsv`) - **`runLimma.R`** para correr el análisis y obtener las topTables y los ficheros GMT :::info - Los ficheros **GMT (*Gene Matrix Transposed*)** recogen los nombres de los genes asociados a cada gene set. En este caso lo que entiendo que tenemos es un fichero por gene set (UP/DN), con una sola fila que dice los nombres de los genes - Las **topTables** son tablas rankeadas con el top de genes que salen al aplicar el modelo lineal a un estudio. ::: 3. El script **`runLimma.R`** requiere: - **Tres argumentos:** (1) una matriz de diseño, (2) un archivo de expresión y (3) un archivo de nodos; - **Dos directorios:** (1) gmtFiles, donde escribir los gene sets para CMAP, y (2) topTable, para escribir los topTables. 4. Además, **`runLimma.R`** recoge funciones de otros tres scripts: - `detectDesign.R`: funciones para obtener información sobre cada estudio y generar la matriz de contraste y de diseño que hay que pasarle a `limma` - `HTS_functions.R`: funciones para analizar RNASeq - `normalizedArrayFunctions.R`: funciones para el análisis de microarrays. Tiene unas cuantas funciones: - varias para pasar identificadores de genes a ENSEMBL; - **correr el modelo lineal, hacer la corrección de eBayes y sacar los contrastes;** - **escribir los topTables;** - **detectar top 150 y bottom 150 genes del BINGSpace para la carpeta gmtFiles;** - ver qué hacer si un probeset vincula a varios genes o viceversa. ## Proceso `runLimma.R` Es un script bastante corto. Podemos quedarnos con que: 1. Incorpora las funciones necesarias: ```r= Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 8) source("detectDesign.R") source("normalizedArraysFunctions.R") source("HTS_functions.R") ``` 2. Parsea el input y prepara el output: ```r=+ args = commandArgs(trailingOnly=TRUE) DM <- args[1] data_matrix <- args[2] nodes <- args[3] #filename suffix for outputs f <- strsplit(data_matrix, "/")[[1]] f <- f[length(f)] f <- strsplit(f, "_")[[1]][1] ``` 3. Prepara la matriz de diseño y detecta si el estudio es HTS o microarrays: ```r=+ con <- file(DM,"r") first_line <- readLines(con,n=1) ret <- detect_design(DM, log = "", sep = "\t") #print(ret$designMatrix) print(ret$contMatrix) ``` 4. En función de si es HTS o microarrays, prepara los datos necesarios para el resto del análisis: ```r=+ #checker wheter HTS or microarrays if(first_line == "#HTS"){ counts <- prepare_matrix(data_matrix, sep = "\t", header = 1) annotatedEset <- proccessHTS(counts, ret$designMatrix) }else{ annotatedEset <- annotateGSE(data_matrix) } ``` 5. Prepara la matriz de nodos: ```r=+ node_ids <- read.table(nodes, sep = "\t", header = T) ``` 6. Corre el análisis diferencial (modelos lineales, etc.): ```r=+ generalizedTtest(annotatedEset, ret$designMatrix, ret$contMatrix,node_ids,"",".", f) ``` :::info Todos los pasos de correr el análisis, quedarse con el top de genes y preparar las cosas para llamar a CMAP están dentro de la función `generalizedTtest`, que está definida dentro del script `normalizedArrayFunctions.R` ::: ### Función `generalizedTtest` La función toma como argumentos: - `annotatedGseset`: datos de expresión a utilizar - `designMatrix`: matriz de diseño - `contMatrix`: matriz de contrastes - `id_matrix`: matriz de nodos - `log = ""`: para fichero log cuando se busquen genes del BINGspace (ahí veremos si se encontraon menos de 0 significativos, qué ficheros UP/DN se han grabado, etc.) - `direc`: directorio donde se guardará la salida (archivos gmt y topTables) - `file`: nombre de la GSE para generar después los nombres de los archivos de salida ```r= generalizedTtest<- function(annotatedGseset, designMatrix, contMatrix, id_matrix, log = "", direc, file ){ designMatrix <- designMatrix[order(match(paste("GSM", rownames(designMatrix), sep = ""), colnames(annotatedGseset))), , drop = F] ``` Transforma los datos de expresión logarítmicamente: ```r=+ #reorder the design matrix #so row ordering matches column ordering of annotatedGseset #limma will not look for rownames exprs(annotatedGseset) <- log2(exprs(annotatedGseset)) ``` Ajusta el modelo lineal ( `lmFit`), lleva a cabo los contrastes (`contrasts.Fit`) y aplica la corrección de eBayes (`eBayes`): :::info Manual de `eBayes`: > Given a linear model fit from `lmFit`, compute moderated t-statistics, moderated F-statistic, and log-odds of differential expression by empirical Bayes moderation of the standard errors towards a global value. > **Aquí no hace ninguna corrección de p-valores**. ::: ```r=+ fit <- lmFit(annotatedGseset, designMatrix) fit <- contrasts.fit(fit, contMatrix) fit <- eBayes(fit) ``` Para cada contraste de interés (cada `coeff` o columna de la matriz de contraste): ```r=+ for(coeff in colnames(contMatrix)){ GSMs <- names(which(designMatrix[,coeff] != 0)) node <- id_matrix[ id_matrix$GSM %in% strtoi(GSMs), "node_id" ][1] ``` - Obtiene el top de genes con `topTable`, aportándole: - el modelo lineal ajustado (`fit`); - el contraste que nos interesa (`coeff`); - el máximo de genes de la lista (`nrow(annotatedGseset)`); - el método para ajustar el p-valor (`"BH"`); - el estadístico usado para hacer el ranking de genes (`"logFC"`) :::danger Mi problema aquí es que **no se ha dado un valor al parámetro `p.value`**, que indica el **punto de corte de los p-valores tras el ajuste**. **El valor por defecto de este parámetro es `p.value = 1`.** ::: ```r=+ tt <- topTable(fit, coef = coeff, number= nrow(annotatedGseset), adjust.method="BH", sort.by="logFC") ``` Aplica la función `getMaxMean`, definida también en el script `normalizedArrayFunctions.R`, para quedarse con una sola fila si hay varias que mapeen al mismo gen: ```r=+ summarizedGenes <- getMaxMean(tt[ , c("entrezIDs", "logFC"), drop = F]) ``` Escribe la topTable en el fichero de salida: ```r=+ write.table(tt, file = paste( direc,"/topTable/", node, "___", file, "_top_table_", coeff , sep = "") ) ``` Llama a la función `getBINGSpaceGenes`, definida en el propio script, para escribir finalmente los ficheros gmt que se enviarán a CMAP: :::success Esta es la función que obtiene el top 150 y botttom 150 de genes y que escribe los ficheros a CMAP. ::: ```r=+ getBINGSpaceGenes(signGenes = summarizedGenes, probeToEntrezIDs = F, name = paste(node, "_",file,coeff, sep = "_"), log = log, direc = direc ) } } ``` ## Función `getBINGSpaceGenes` Lo que interpreto que hace esta función es: 1. Quedarse con todo el conjunto de genes up / down de la topTable (que está en `signGenes`, generando los dataframes `upgenes` y `dngenes`: ```r= getBINGSpaceGenes <- function(signGenes, col = "entrezIDs",probeToEntrezIDs = F,topGenes = 150, name, log = "", direc = "."){ #probeToEntrezIDs: dataframe of rows:probeset IDs, one column: entrez gene IDs #signGenes: dataframe of rows: probeset IDs, columns logFC #generate gmt fileto use in clue io #clue io uses only a subset of genespace, as well as a limit of 150 genes #subset those allowed, and if there are more than 150, sort based on logFC if(probeToEntrezIDs){ ## OJO: esto por default es FALSE upgenes <- merge(subset(signGenes, signGenes$logFC > 0), probeToEntrezIDs , 0) rownames(upgenes) <- upgenes$Row.names upgenes$Row.names <- NULL dngenes <- merge(subset(signGenes, signGenes$logFC < 0), gseset@featureData@data, 0) rownames(dngenes) <- dngenes$Row.names dngenes$Row.names <- NULL } else{ ## lo que corre por defecto ## col es el entrezID signGenes[, col] <- as.integer(signGenes[, col]) upgenes <- subset(signGenes, signGenes$logFC > 0) dngenes <- subset(signGenes, signGenes$logFC < 0) } ``` 2. Selecciona los genes up que están en el BINGSpace (el espacio de genes recogidos en CMAP), si son más de 150, los ordena por logFC y toma el top 150. Si son menos de 10, manda un aviso y **para el análisis**, de manera que **no se hace ninguna query a CMAP**: :::danger Como en `topTable` no se aporta el parámetro `p.value`, yo esperaba aquí encontrar que se filtrara la tabla en función de la columna correspondiente al p-valor ajustado, pero parece que ordena por logFC sin hacer ningún filtrado antes. ::: ```r=+ upgenes <- upgenes[!is.na(upgenes[, col, drop = F]), ] upgenes <- upgenes[upgenes[, col] %in% BINGspace$Entrez.ID, ] if (nrow(upgenes) > 150){ upgenes <- upgenes[order(-1*upgenes$logFC), col][1:150] }else if(nrow(upgenes) < 10){ cat(paste("Less than 10 significantly differentially expressed upgenes found in BINGspace for ", name, ". Analysis stopped", sep = ""), sep = "\n" , file = log, append = T) return(NULL) }else{ upgenes <- upgenes[order(-1*upgenes$logFC), col] } ``` 3. Hace lo mismo con los genes down. Sin embargo, en este caso, si hay menos de 10 genes la query se hace igualmente, dando un aviso al usuario de que no se van a enviar los genes downregulados: ```r=+ }else if(nrow(dngenes) < 10){ cat(paste("Less than 10 significantly differentially expressed dngenes found in BINGspace for ", name, ". Skipping dngenes list", sep = ""), sep = "\n", file = log, append = T) writeDngenes <- FALSE } ``` 5. Escribe los ficheros gmt: ```r=+ upgenes <- c(paste(name, "_up", sep = ""), "TAG", upgenes) dngenes <- c(paste(name, "_dn", sep = ""), "TAG", dngenes) fwrite(as.list(upgenes), file = paste(direc,"/gmtFiles/",name, "UP.gmt", sep = ""), sep = "\t") if(writeDngenes){ fwrite(as.list(dngenes), file = paste(direc,"/gmtFiles/",name, "DN.gmt", sep = ""), sep = "\t") cat(paste("_up and _dn GMT files for ",direc,"/gmtFiles/", name, " created.", sep = ""), sep = "\n" , file = log, append = T) } else{ cat(paste("_up GMT files for ", name, " created.", sep = ""), sep = "\n" , file = log, append = T) } } ``` ## Distribución de genes up y down Guardo la tabla con el siguiente comando: ```sql select node_id, geneSet, count(entrez_id) from topTable group by node_id, geneSet order by node_id into outfile '/tmp/topTable_geneSet_numbers.txt'; ``` Y ahora vamos a mirar un poco. Vemos que para casi todos los nodos tenemos que el geneSet 'NULL' tiene muchísimos genes: ![](https://i.imgur.com/FZ0QVeL.png) Mientras tanto, los geneSets UP y DN tienen menos: ![](https://i.imgur.com/fkfg0rK.png) > La línea horizontal marca dónde está el 150. Podemos hacer un histograma para ver cuántos nodos tienen < 150 genes en alguno de estos geneSets. Este lo he hecho restringiendo para ver únicamente los geneSets con un tamaño < 200, porque si no no se ve nada: ![](https://i.imgur.com/UgZRZaP.png) > Las líneas verticales marcan el 10 (mínimo para enviar una query) y el 150 (máximo de genes por query). En la gráfica anterior podemos comprobar que __no hay geneSets con un tamaño < 10__. De hecho, si hacemos un `summary`: ``` Min. 1st Qu. Median Mean 3rd Qu. Max. 36.0 527.5 665.0 10497.1 7276.0 239830.0 ``` Por otro lado, tenemos 46 geneSets UP y 47 DN con < 150 genes (44 solapan entre ambos). :::warning No termino de entender ahora por qué esto sale así, porque en la tabla `cmap_foodrugs` solo tenemos 398 node_id y aquí hay 410. Si todos estos gene sets tienen más de 10 genes, no entiendo por qué no tienen conexión con cmap... ::: En la intersección de los `node_id` de ambas tablas tenemos 398 nodos, que son los que aparecen en `cmap_foodrugs`. Los que están solo en `topTable` son: ```r unique(genesets$node_id)[!unique(genesets$node_id) %in% unique(cmap_foodrugs$node_id)] [1] 111 203 209 283 318 326 327 328 329 357 395 456 ``` Los nodos de `topTable` con menos de 3 geneSets son: ```r names(which(table(genesets$node_id) < 3)) [1] "111" "203" "209" "283" "357" "359" "395" ``` > Todos estos tienen en común que no tienen DN. Algunos tienen solo NULL (203, 357, 395) y otros tienen NULL y UP (111, 209, 283, 359). * Revisando estos nodos sí es verdad que la mitad no tienen todos los geneSets, bien porque solo tengan NULL o NULL y UP. * Ninguno de ellos tiene geneSet DN. * Sin embargo, hay nodos a los que les falta algún geneSet que no están en esta lista (nodo 359), y hay nodos de la lista con 3 geneSets (318, 326, 327, 328, 329, 456) * Por tanto, el número de geneSets no es el motivo por el cual faltan `node_id` en `cmap_foodrugs`. Lo que se me ocurre ahora es mirar los p-values... No vaya a ser que sí haya un filtro que no hayamos visto, y en realidad sean los que no tenan como mínimo x valores con p < 0.05, o lo que sea. :::danger Pausa aquí porque no funcionaba la VPN :) voy a intentar configurar la base de datos en la partición de Windows del portátil ::: Mientras tanto otra opción es buscar estos nodos en la página web y ver qué onda con ellos. Para saber las condiciones de cada nodo he ido a una tabla que tenía guardada en el directorio '/fnsCloud-FooDrugs/misc-stuff' llamada `nodes_sample_NJ`: > **Están en `topTable` pero no en `cmap_foodrugs`:** 111 203 209 283 318 326 327 328 329 357 395 456 > **Tienen menos de 3 geneSets**: "111" "203" "209" "283" "357" "359" "395" | node_id | treatment | concentration | time_point | | ------- | ---------------------------------- | ------------- | ---------- | | 111 | dasatinib 12 weeks | - | 13 weeks | | 203 | grape extract | 8 mg | 0 | | 209 | heavy drinking | - | - | | 283 | moderate drinking | - | - | | 318 | perifosine | 10 uM | 24 hours | | 326 | prochlorperazine | - | 24 hours | | 327 | prodrug g668684 | 7 uM | 72 hours | | 328 | prodrug g669043 | 1500 nm | 72 hours | | 329 | prodrug g669043 | 150 nm | 72 hours | | 357 | resveratrol-enriched grape extract | 8 mg | 0 | | 359 | resveratrol-enriched grape extract | 16 mg | 12 months | | 395 | sorafenib | 4 uM | 72 hours | | 456 | tvb3567 | | 48 hours | | 456 | tvb3567 | | 48 hours | | 456 | tvb3567 | | 48 hours | > en el nodo 456 tenemos tres filas "iguales" porque vienen de líneas celulares diferentes :) # Red ## Intentando entender tabla `cmap_foodrugs` No entiendo por qué hay filas duplicadas... Vamos a ver cuántas hay y a ver si viéndolas puedo sacar algo en claro :male-detective: ```bash ## directorio: ~/fnscloud-FooDrugs/misc-stuff # cmap_foodrugs.txt es simplemente la tabla cmap_foodrugs de mySQL exportada cat cmap_foodrugs.txt | sort | uniq | wc -l # 28,216,210 wc -l cmap_foodrugs.txt # 28,683,990 cmap_foodrugs.txt cat cmap_foodrugs.txt | sort | uniq -d | wc -l # 396,885 cat cmap_foodrugs.txt | sort | uniq -d | head # node_id cmap_node_id tau # 110 0 0 # 110 10 0 # 110 100 0 # 110 1000 0 # 110 10000 0 # 110 10001 -71.5964 # 110 10002 -16.3902 # 110 10003 88.0374 # 110 10004 0 # 110 10005 0 # separamos lineas duplicadas a un archivo aparte: cat cmap_foodrugs.txt | sort | uniq -d > cmap_foodrugs_duplines.txt ``` Si miramos el archivo que acabamos de generar para almacenar todas las líneas duplicadas (`cmap_foodrugs_duplines.txt`), veremos que hay **6 nodos de GEO**, con valores de tau para **todos los nodos de CMAP**: ```bash # vamos a ver que node_id unicos salen: cut -f1 cmap_foodrugs_duplines.txt | uniq # 110 # 112 # 113 # 114 # 115 # 118 # y que cmap_node_id unicos tenemos... # en este caso si hace falta sort al ser la 2a columna cut -f2 cmap_foodrugs_duplines.txt | sort | uniq | wc -l # 70,895 --> TODOS los nodos de CMAP # las taus: cut -f3 cmap_foodrugs_duplines.txt | sort | uniq | wc -l # 104,505 ``` De aquí puedo entender lo de CMAP, porque al final tenemos que probar todas las posibles interacciones. Lo que no entiendo es que solo haya 6 nodos de los obtenidos por nosotros para los cuales tengamos filas repetidas... > Si miramos nodo a nodo, todos tienen 70895 repeticiones menos el 118, que se repite solo 42410 veces. Así que deberíamos tener $70895 · 5 + 42410 = 354475 + 42410 = 396885$ líneas repetidas, que es exactamente lo que encontramos! :confetti_ball: Vamos a mirar, para el `node_id = 110`, cuántas repeticiones tenemos: ```sql select node_id, cmap_node_id, tau from cmap_foodrugs where node_id = 110 order by cmap_node_id; -- 141,790 filas select distinct node_id, cmap_node_id, tau from cmap_foodrugs where node_id = 110 order by cmap_node_id; -- 70,895 filas ``` Es decir... Este nodo tiene 70,895 conexiones posibles con nodos de CMAP (tantos como nodos de CMAP hay). Cada una de estas tiene un tau asociado. Encontramos que cada una de estas conexiones, definida por una combinación `node_id`-`cmap_node_id`-`tau`, se repite dos veces en la tabla `cmap_foodrugs`. Ahora podemos repetir el proceso para todos los nodos con repeticiones modificando un poco el comando anterior, y guardando un nuevo fichero de texto de cuatro columnas: número de repeticiones de la fila, `node_id`, `cmap_node_id` y `tau`. Vamos a ver: ```bash cat cmap_foodrugs.txt | sort | uniq -dc > cmap_foodrugs_duplines_count.txt cut -f1 cmap_foodrugs_duplines_count.txt | sort | uniq # 2 110 # 2 112 # 2 114 # 2 115 # 2 118 # 3 113 ``` Sigo sin tener ni idea de qué pasa :male-detective: Simplemente por tener la información de los estudios, concentraciones, etc. correspondientes a cada uno de los nodos de la discordia: ```sql select distinct node_id, study_id, treatment, origin_name, time_point, concentration from nodes natural join sample where node_id = 110 or node_id = 112 or node_id = 113 or node_id = 114 or node_id = 115 or node_id = 118; ``` ``` +---------+----------+--------------------+-------------+------------+---------------+ | node_id | study_id | treatment | origin_name | time_point | concentration | +---------+----------+--------------------+-------------+------------+---------------+ | 110 | 140 | dasatinib 12 weeks | Kasumi-1 | 12 weeks | - | | 112 | 133 | decitabine | HCT-116 | 72 hours | 5 µM | | 113 | 133 | decitabine | HCT-116 | 72 hours | 120 nm | | 114 | 89 | Delta tocotrienol | HeLa | 24 hours | 2 µg/ml | | 115 | 60 | deoxycholic acid | MCF7 | 12 hours | 10 µM | | 118 | 157 | diclofenac | VMM39 | 8 hours | - | | 118 | 157 | diclofenac | SLM2 | 8 hours | - | | 118 | 157 | diclofenac | DM331 | 8 hours | - | +---------+----------+--------------------+-------------+------------+---------------+ 8 rows in set (0,00 sec) ``` # Interacciones en ambas estrategias ## Resveratrol ## Todas las interacciones posibles **Todas las interacciones obtenidas por GEO + CMAP.** Estos comandos tardan muchísimo, recordemos que hay ~ 1 millón de edges con tau > 90. Pruebo también con > 95 para restringir un poco más: ```sql select distinct compound,treatment from cmap natural join cmap_foodrugs natural join nodes natural join sample where abs(tau) > 90 into outfile '/tmp/all_CMAP_interactions_90.txt'; -- Query OK, 524081 rows affected (10 min 0,79 sec) select distinct compound,treatment from cmap natural join cmap_foodrugs natural join nodes natural join sample where abs(tau) > 95 into outfile '/tmp/all_CMAP_interactions_95.txt'; -- Query OK, 299177 rows affected (9 min 52,66 sec) ``` **Todas las interacciones obtenidas por text mining.** Esto es mucho más sencillo: ```sql select distinct food, drug from TM_interactions into outfile '/tmp/all_TM_interactions.txt'; -- Query OK, 5487 rows affected (0,01 sec) ``` :::spoiler ```bash mkdir fnscloud-FooDrugs/misc-stuff/comparing-interactions sudo mv '/tmp/all_CMAP_interactions_90.txt' fnscloud-FooDrugs/misc-stuff/comparing-interactions sudo mv '/tmp/all_CMAP_interactions_95.txt' fnscloud-FooDrugs/misc-stuff/comparing-interactions sudo chmod ugo+rw fnscloud-FooDrugs/misc-stuff/comparing-interactions/all_CMAP_interactions_9* sudo mv '/tmp/all_TM_interactions.txt' fnscloud-FooDrugs/misc-stuff/comparing-interactions/ sudo chmod ugo+rw fnscloud-FooDrugs/misc-stuff/comparing-interactions/all_TM_interactions.txt ``` ::: Primero vamos a ordenar todos los archivos: ```bash sort all_CMAP_interactions_95.txt > sorted_95.txt sort all_TM_interactions.txt > sorted_TM.txt sort all_CMAP_interactions_90.txt > sorted_90.txt ``` Lo más sencillo ahora es buscar las líneas en común entre cualquiera de los ficheros de la red y el de TM: ```bash comm -12 sorted_TM.txt sorted_90.txt ``` ``` coumestrol genistein daidzein genistein genistein estradiol myricetin quercetin naringenin doxorubicin nobiletin atorvastatin phloretin quercetin pinocembrin quercetin piperine resveratrol quercetin atorvastatin ``` ```bash comm -12 sorted_TM.txt sorted_95.txt ``` ``` coumestrol genistein myricetin quercetin nobiletin atorvastatin phloretin quercetin piperine resveratrol quercetin atorvastatin ``` ```bash comm -12 sorted_TM.txt sorted_90.txt > comm_TM_90.txt ``` Pero... También puede ser que haya compuestos que CMap haya detectado como 'compound' ( = fármaco) y el TM como 'food', y eso no lo veremos aquí. Con permutar las columnas de uno de los ficheros nos vale. Permutaremos el de text mining, para no tener que permutar ambos archivos de CMAP: ```bash paste <(cut -f2 sorted_TM.txt) <(cut -f1 sorted_TM.txt) > permuted_TM.txt sort permuted_TM.txt > sorted_permuted_TM.txt rm permuted_TM.txt ``` Y volvemos a comparar: ```bash comm -12 sorted_permuted_TM.txt sorted_90.txt ``` ``` acitretin vitamin A atorvastatin quercetin dasatinib quercetin erlotinib genistein estradiol genistein midazolam quercetin mitoxantrone quercetin nocodazole quercetin pantoprazole quercetin prazosin quercetin pterostilbene quercetin retinol vitamin D rosiglitazone quercetin simvastatin quercetin sulfasalazine quercetin tamoxifen estradiol tamoxifen estrogen tamoxifen genistein valsartan resveratrol ``` Lo guardamos en otro fichero: ```bash comm -12 sorted_permuted_TM.txt sorted_90.txt > comm_pTM_90.txt ``` Y los concatenamos: ```bash cat comm_TM_90.txt comm_pTM_90.txt | sort > final_TMvs90.txt ``` :::info El fichero final tiene **29 líneas**... | Compound (drug) | Treatment (food) | | ------------- | ------------- | acitretin |vitamin A atorvastatin| quercetin coumestrol |genistein daidzein |genistein dasatinib |quercetin erlotinib |genistein estradiol |genistein genistein |estradiol midazolam |quercetin mitoxantrone| quercetin myricetin | quercetin naringenin |doxorubicin nobiletin |atorvastatin nocodazole |quercetin pantoprazole| quercetin phloretin |quercetin pinocembrin |quercetin piperine |resveratrol prazosin |quercetin pterostilbene| quercetin quercetin |atorvastatin retinol |vitamin D rosiglitazone| quercetin simvastatin |quercetin sulfasalazine| quercetin tamoxifen |estradiol tamoxifen |estrogen tamoxifen |genistein valsartan |resveratrol ::: # Tablas para el paper ## GEO A partir de lo que había hecho Óscar en su HackMD he sacado una tabla más o menos parecida, pero sin lo de 'other': ```sql SELECT treatment, COUNT(DISTINCT concat(lower(treatment), concentration,time_point)) AS n_treatments, COUNT(DISTINCT study_id) AS n_series FROM sample GROUP BY treatment ORDER BY n_treatments DESC INTO OUTFILE '/tmp/most_common_treatments.txt'; ``` ```bash sudo mv /tmp/most_common_treatments.txt fnscloud-FooDrugs/misc-stuff/ sudo chmod ugo+rw fnscloud-FooDrugs/misc-stuff/most_common_treatments.txt ``` Las 10 primeras filas: | treatment | experiments | series | |------------------ | ----------- | ---------- | | control |58 |153 | | vitamin D |17 |17 | | genistein |13 |2 | | quercetin |11 |4 | | frankincense |9 |1 | | etoposide |7 |1 | | methanesulfonate |7 |1 | | resveratrol |5 |5 | | vorinostat |5 |3 | | doxorubicin |4 |2 | Top 10 de tratamientos más frecuentes en la tabla “sample”, ordenados por el número de estudios en el que aparecen. Entre ellos aparecen control y ref para referirse a las muestras sin tratar. En total tenemos 370 tratamientos diferentes, de los cuales 346 aparecen en un solo estudio. > Ojo: esto es un poco tramposo, porque aquí aparecen como tratamientos distintos gallic_acid y Gallic Acid, por ejemplo. Óscar lo ha contemplado en su HackMD: https://hackmd.io/@OscarPiette/ryTvmiCTc#Resultado3 ## Text mining Para las tablas de esta parte veo que Óscar se ha peleado bastante y ha tenido que hacer muchs cambios manuales. Me fío de él para esto: https://hackmd.io/@OscarPiette/ryTvmiCTc#Resultado1 > Aquí ojo porque en `n_texts` no sé si tenemos referencias a un mismo texto como si fueran de textos distintos... Y lo de poner "dopamine" como food no sé yo si es muy adecuado. ## Mirando categorías de todos :::info Creo que aquí nos puede ser útil hacer algo como la **tabla 2** del TFM de Marco: ![](https://i.imgur.com/qNeeZT2.png) - Entiendo que para los compuestos de **GEO / CMap** esto ya está hecho? Al menos se puede ver en la página web... La cosa es que no sé de primeras dónde podemos encontrarlo. - me extraña que no haya una categoría para "polyphenols" (sé que los flavonoids lo son, pero ¿?) - ¿Sería más o menos sencillo hacer esto también para los términos de la parte de text mining? ::: Vamos a empezar por obtener los foods que tenemos en CMAP y en Text mining por separado, y a ver cuál es su solapamiento. Como tengo las dos tablas (`sample` y `TM_interactions`) en `~/fnscloud-FooDrugs/misc-stuff`, las importo a R para que me sea más fácil y sigo por ahí. Scrit: `overlap_food_drugs.R` :::danger Aquí se me complica un poco la cosa, porque no siempre coincide la consideracón de algo como **food** o como **drug**. Pero claro, si consideramos todos los tratamiento de GEO y compuestos de CMap por un lado y todos los foo y drug de text mining por otro, acabamos con muchísimos compuestos, y muchos de los cuales no nos interesan. - Posible solución: usar solo los `pert_type == trt_cp`. El resto no nos interesan y así trabajaríamos con 2545 compuestos (de CMap) en lugar de 6395. - **¿será suficiente con hacer esto?** > Por ahora voy a seguir **solo con GEO** y con todos los de text mining (tanto food como drugs), porque realmente lo que puede "fallar" es eso (en CMap consideramo que todo son drugs) ::: Así que lo que voy a mandar a KEGG phytochemicals es: - La lista de foods y drugs de text mining (`text_mining_both.txt`); - también solo la lista de foods (`text_mining_foods.txt`); - la lista de treatments de GEO (`geo_treatments.txt`); - la unión entre las dos listas anteriores (_teniendo en cuenta foods **y** drugs_) (`text_mining_geo_union.txt`); - la intersección de las dos listas (`text_mining_geo_intersection.txt`); - **vamos a incorporar también los tratamientos tipo _compound_ del CMap (`all_foods_and_drugs.txt`).** :::danger El razonamiento detrás de esto es que si incorporo los drugs del text mining, tendré que incorporar también los de CMap... ::: Con esto podemos ver: - si el text mining está muy sesgado con respecto a lo que hay en GEO (comparamos ambas listas de TM con GEO, si salen cosas parecidas guay y si no también será interesante porque nos dirá si la detección de foods o de drugs funciona mejor que la otra); - si la lista de interacciones que coinciden entre ambos recursos está sesgada con respecto a lo que tenemos en la base de datos (ej. si en la base de datos hubiera un 20% de flavonoides y en la lista de interacciones en común fueran el 100%). :::warning Pasos para hacer esto: 1. Mandar todo a KEGG --> podemos hacer por separado GEO, TM y CMap y ya luego unir los text files resultantes 2. A ver cómo se hacía la estadística del enriquecimiento este que pretendemos hacer 3. Hacer la estadítica :-) :::