Combinando tablas#
Cuando se trabaja con muchas tablas en una base de datos relacional, a menudo tiene sentido combinar tablas en tablas resumen y almacenarlas antes de un procesamiento adicional. Como recordatorio, adquirimos images durante los experiments y las correspondientes measurements. Ahora nos gustaría extraer una tabla de overview que contenga la información más importante de image, experiments y measurements.
import pandas as pd
from pandasql import sqldf
Nuevamente comenzamos cargando nuestras tablas desde el disco.
experiment = pd.read_csv("../../data/experiments.csv")
image = pd.read_csv("../../data/images.csv")
measurement = pd.read_csv("../../data/measurements.csv")
Una vez más, definimos una función que puede consultar la base de datos.
query_database = lambda q: sqldf(q, globals())
Inner Join#
SQL también permite combinar tablas usando la declaración JOIN. El más común es el INNER JOIN que permite combinar dos tablas de manera que las filas se emparejen según una conexión definida ON en la que se combinan las tablas. El INNER JOIN se asegura de que solo se muestren las filas para las que ambas tablas tienen entradas. Es por eso que el experimento ID 4 no se muestra en la siguiente tabla. No se almacenaron imágenes durante el experimento 4.
query = """
SELECT *
FROM experiment
INNER JOIN image ON experiment.ID = image.experiment_ID
"""
query_database(query)
| Unnamed: 0 | ID | Comment | Concentration | Unnamed: 0 | ID | Filename | experiment_ID | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 |
| 1 | 0 | 1 | High dose | 50 | 1 | 2 | image1.tif | 1 |
| 2 | 0 | 1 | High dose | 50 | 2 | 3 | image2.tif | 1 |
| 3 | 1 | 2 | Medium dose | 20 | 3 | 4 | image3.tif | 2 |
| 4 | 1 | 2 | Medium dose | 20 | 4 | 5 | image4.tif | 2 |
| 5 | 1 | 2 | Medium dose | 20 | 5 | 6 | image5.tif | 2 |
| 6 | 2 | 3 | Control | 0 | 6 | 7 | image6.tif | 3 |
| 7 | 2 | 3 | Control | 0 | 7 | 8 | image7.tif | 3 |
| 8 | 2 | 3 | Control | 0 | 8 | 9 | image8.tif | 3 |
| 9 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 |
Outer Join#
Si uno quisiera combinar las tablas de manera que también se liste el experimento 4, necesitamos usar un OUTER JOIN. En este caso, como la tabla de experimentos está a la izquierda de nuestra declaración SQL, usamos el LEFT OUTER JOIN. El experimento 4 se lista entonces en nuestra tabla con algunos NaNs en las columnas de las imágenes.
query = """
SELECT *
FROM experiment
LEFT OUTER JOIN image ON experiment.ID = image.experiment_ID
"""
query_database(query)
| Unnamed: 0 | ID | Comment | Concentration | Unnamed: 0 | ID | Filename | experiment_ID | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | High dose | 50 | 0.0 | 1.0 | image0.tif | 1.0 |
| 1 | 0 | 1 | High dose | 50 | 1.0 | 2.0 | image1.tif | 1.0 |
| 2 | 0 | 1 | High dose | 50 | 2.0 | 3.0 | image2.tif | 1.0 |
| 3 | 1 | 2 | Medium dose | 20 | 3.0 | 4.0 | image3.tif | 2.0 |
| 4 | 1 | 2 | Medium dose | 20 | 4.0 | 5.0 | image4.tif | 2.0 |
| 5 | 1 | 2 | Medium dose | 20 | 5.0 | 6.0 | image5.tif | 2.0 |
| 6 | 2 | 3 | Control | 0 | 6.0 | 7.0 | image6.tif | 3.0 |
| 7 | 2 | 3 | Control | 0 | 7.0 | 8.0 | image7.tif | 3.0 |
| 8 | 2 | 3 | Control | 0 | 8.0 | 9.0 | image8.tif | 3.0 |
| 9 | 2 | 3 | Control | 0 | 9.0 | 10.0 | image9.tif | 3.0 |
| 10 | 3 | 4 | Super high dose | 1000 | NaN | NaN | None | NaN |
Combinando muchas tablas#
Las declaraciones JOIN anteriores también se pueden usar en cadenas para combinar más de dos tablas. Por ejemplo, podríamos estar interesados en el número de células a lo largo del tiempo para cada imagen en cada experimento
query = """
SELECT *
FROM experiment
LEFT OUTER JOIN image ON experiment.ID = image.experiment_ID
INNER JOIN measurement ON image.ID = measurement.image_ID
"""
query_database(query)
| Unnamed: 0 | ID | Comment | Concentration | Unnamed: 0 | ID | Filename | experiment_ID | Unnamed: 0 | image_ID | time_after_exposure_in_s | number_of_cells | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 | 0 | 1 | 0 | 96 |
| 1 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 | 1 | 1 | 1 | 47 |
| 2 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 | 2 | 1 | 2 | 32 |
| 3 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 | 3 | 1 | 3 | 24 |
| 4 | 0 | 1 | High dose | 50 | 0 | 1 | image0.tif | 1 | 4 | 1 | 4 | 19 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 95 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 | 95 | 10 | 5 | 142 |
| 96 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 | 96 | 10 | 6 | 148 |
| 97 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 | 97 | 10 | 7 | 144 |
| 98 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 | 98 | 10 | 8 | 141 |
| 99 | 2 | 3 | Control | 0 | 9 | 10 | image9.tif | 3 | 99 | 10 | 9 | 140 |
100 rows × 12 columns
Seleccionando columnas#
Cuando una tabla se vuelve demasiado ancha y tiene columnas que realmente no necesitamos para nuestro análisis, se recomienda reemplazar el * en la declaración SELECT con nombres de columnas.
query = """
SELECT experiment.Comment, image.Filename, measurement.time_after_exposure_in_s, measurement.number_of_cells
FROM experiment
LEFT OUTER JOIN image ON experiment.ID = image.experiment_ID
INNER JOIN measurement ON image.ID = measurement.image_ID
"""
query_database(query)
| Comment | Filename | time_after_exposure_in_s | number_of_cells | |
|---|---|---|---|---|
| 0 | High dose | image0.tif | 0 | 96 |
| 1 | High dose | image0.tif | 1 | 47 |
| 2 | High dose | image0.tif | 2 | 32 |
| 3 | High dose | image0.tif | 3 | 24 |
| 4 | High dose | image0.tif | 4 | 19 |
| ... | ... | ... | ... | ... |
| 95 | Control | image9.tif | 5 | 142 |
| 96 | Control | image9.tif | 6 | 148 |
| 97 | Control | image9.tif | 7 | 144 |
| 98 | Control | image9.tif | 8 | 141 |
| 99 | Control | image9.tif | 9 | 140 |
100 rows × 4 columns
Renombrando columnas#
Usando la declaración AS, también podemos renombrar columnas y hacer que nuestra nueva tabla sea más fácil de leer y entender.
query = """
SELECT experiment.Comment AS experiment_name, image.Filename, measurement.time_after_exposure_in_s, measurement.number_of_cells
FROM experiment
LEFT OUTER JOIN image ON experiment.ID = image.experiment_ID
INNER JOIN measurement ON image.ID = measurement.image_ID
"""
query_database(query)
| experiment_name | Filename | time_after_exposure_in_s | number_of_cells | |
|---|---|---|---|---|
| 0 | High dose | image0.tif | 0 | 96 |
| 1 | High dose | image0.tif | 1 | 47 |
| 2 | High dose | image0.tif | 2 | 32 |
| 3 | High dose | image0.tif | 3 | 24 |
| 4 | High dose | image0.tif | 4 | 19 |
| ... | ... | ... | ... | ... |
| 95 | Control | image9.tif | 5 | 142 |
| 96 | Control | image9.tif | 6 | 148 |
| 97 | Control | image9.tif | 7 | 144 |
| 98 | Control | image9.tif | 8 | 141 |
| 99 | Control | image9.tif | 9 | 140 |
100 rows × 4 columns
Como esta declaración es bastante complicada y no deberíamos copiarla y pegarla una y otra vez, podemos almacenarla en un DataFrame de pandas overview.
query = """
SELECT experiment.Comment AS experiment_name, image.Filename, measurement.time_after_exposure_in_s, measurement.number_of_cells
FROM experiment
LEFT OUTER JOIN image ON experiment.ID = image.experiment_ID
INNER JOIN measurement ON image.ID = measurement.image_ID
"""
overview = query_database(query)
La tabla overview es parte de nuestra base de datos. Nota: El resumen aún no se ha guardado en el disco.
query = """
SELECT *
FROM overview
"""
query_database(query)
| experiment_name | Filename | time_after_exposure_in_s | number_of_cells | |
|---|---|---|---|---|
| 0 | High dose | image0.tif | 0 | 96 |
| 1 | High dose | image0.tif | 1 | 47 |
| 2 | High dose | image0.tif | 2 | 32 |
| 3 | High dose | image0.tif | 3 | 24 |
| 4 | High dose | image0.tif | 4 | 19 |
| ... | ... | ... | ... | ... |
| 95 | Control | image9.tif | 5 | 142 |
| 96 | Control | image9.tif | 6 | 148 |
| 97 | Control | image9.tif | 7 | 144 |
| 98 | Control | image9.tif | 8 | 141 |
| 99 | Control | image9.tif | 9 | 140 |
100 rows × 4 columns
Para reutilizarlo en lecciones posteriores, guardamos el resumen en el disco.
overview.to_csv("../../data/overview.csv")