Anexando tablas#
Al procesar múltiples imágenes, potencialmente utilizando varias bibliotecas de procesamiento de imágenes, una tarea común es combinar tablas.
Comenzamos con dos pequeñas tablas de mediciones que podrían haberse obtenido de diferentes funciones o diferentes bibliotecas.
import pandas as pd
table1 = pd.DataFrame({
"label": [1, 2, 3],
"circularity": [0.3, 0.5, 0.7],
"elongation": [2.3, 3.4, 1.2],
})
table1
| label | circularity | elongation | |
|---|---|---|---|
| 0 | 1 | 0.3 | 2.3 |
| 1 | 2 | 0.5 | 3.4 |
| 2 | 3 | 0.7 | 1.2 |
table2 = pd.DataFrame({
"label": [3, 2, 1, 4],
"area": [22, 32, 25, 18],
"skewness": [0.5, 0.6, 0.3, 0.3],
})
table2
| label | area | skewness | |
|---|---|---|---|
| 0 | 3 | 22 | 0.5 |
| 1 | 2 | 32 | 0.6 |
| 2 | 1 | 25 | 0.3 |
| 3 | 4 | 18 | 0.3 |
Combinando columnas de tablas#
Según la documentación de pandas, hay múltiples formas de combinar tablas. Primero usaremos un ejemplo incorrecto para resaltar los errores comunes al combinar tablas.
En el siguiente ejemplo, las mediciones de las etiquetas 1 y 3 están mezcladas. Además, una de nuestras tablas no contenía mediciones para la etiqueta 4.
wrongly_combined_tables = pd.concat([table1, table2], axis=1)
wrongly_combined_tables
| label | circularity | elongation | label | area | skewness | |
|---|---|---|---|---|---|---|
| 0 | 1.0 | 0.3 | 2.3 | 3 | 22 | 0.5 |
| 1 | 2.0 | 0.5 | 3.4 | 2 | 32 | 0.6 |
| 2 | 3.0 | 0.7 | 1.2 | 1 | 25 | 0.3 |
| 3 | NaN | NaN | NaN | 4 | 18 | 0.3 |
Una mejor manera de combinar tablas es el comando merge. Permite especificar explícitamente on qué columna se deben combinar las tablas. Los científicos de datos hablan del ‘índice’ o ‘identificador’ de las filas en las tablas.
correctly_combined_tables1 = pd.merge(table1, table2, how='inner', on='label')
correctly_combined_tables1
| label | circularity | elongation | area | skewness | |
|---|---|---|---|---|---|
| 0 | 1 | 0.3 | 2.3 | 25 | 0.3 |
| 1 | 2 | 0.5 | 3.4 | 32 | 0.6 |
| 2 | 3 | 0.7 | 1.2 | 22 | 0.5 |
Puede notar que en el ejemplo anterior, la etiqueta 4 está ausente. También podemos obtenerla en nuestra tabla realizando una unión externa.
correctly_combined_tables2 = pd.merge(table1, table2, how='outer', on='label')
correctly_combined_tables2
| label | circularity | elongation | area | skewness | |
|---|---|---|---|---|---|
| 0 | 1 | 0.3 | 2.3 | 25 | 0.3 |
| 1 | 2 | 0.5 | 3.4 | 32 | 0.6 |
| 2 | 3 | 0.7 | 1.2 | 22 | 0.5 |
| 3 | 4 | NaN | NaN | 18 | 0.3 |
correctly_combined_tables2 = pd.merge(table1, table2, how='right', on='label')
correctly_combined_tables2
| label | circularity | elongation | area | skewness | |
|---|---|---|---|---|---|
| 0 | 3 | 0.7 | 1.2 | 22 | 0.5 |
| 1 | 2 | 0.5 | 3.4 | 32 | 0.6 |
| 2 | 1 | 0.3 | 2.3 | 25 | 0.3 |
| 3 | 4 | NaN | NaN | 18 | 0.3 |
Supongamos que hay un nombre de medición común de diferentes tablas. Por ejemplo, la tabla3 a continuación también contiene “elongation”.
table3 = pd.DataFrame({
"label": [3, 2, 1, 4],
"area": [22, 32, 25, 18],
"skewness": [0.5, 0.6, 0.3, 0.3],
"elongation": [2.3, 3.4, 1.2, 1.1]
})
table3
| label | area | skewness | elongation | |
|---|---|---|---|---|
| 0 | 3 | 22 | 0.5 | 2.3 |
| 1 | 2 | 32 | 0.6 | 3.4 |
| 2 | 1 | 25 | 0.3 | 1.2 |
| 3 | 4 | 18 | 0.3 | 1.1 |
Aplicar merge aún preserva ambas mediciones en columnas diferentes.
correctly_combined_tables3 = pd.merge(table1, table3, how='outer', on='label')
correctly_combined_tables3
| label | circularity | elongation_x | area | skewness | elongation_y | |
|---|---|---|---|---|---|---|
| 0 | 1 | 0.3 | 2.3 | 25 | 0.3 | 1.2 |
| 1 | 2 | 0.5 | 3.4 | 32 | 0.6 | 3.4 |
| 2 | 3 | 0.7 | 1.2 | 22 | 0.5 | 2.3 |
| 3 | 4 | NaN | NaN | 18 | 0.3 | 1.1 |
Podemos cambiar ‘x’ e ‘y’ pasando otros sufijos.
correctly_combined_tables3 = pd.merge(table1, table3, how='outer', on='label', suffixes=('_method1', '_method2'))
correctly_combined_tables3
| label | circularity | elongation_method1 | area | skewness | elongation_method2 | |
|---|---|---|---|---|---|---|
| 0 | 1 | 0.3 | 2.3 | 25 | 0.3 | 1.2 |
| 1 | 2 | 0.5 | 3.4 | 32 | 0.6 | 3.4 |
| 2 | 3 | 0.7 | 1.2 | 22 | 0.5 | 2.3 |
| 3 | 4 | NaN | NaN | 18 | 0.3 | 1.1 |
Combinando mediciones de múltiples archivos de imagen#
Al aplicar un flujo de trabajo a muchas imágenes, obtendrías tablas con los mismos nombres de columnas, pero con un número variable de filas. Para calcular estadísticas para carpetas completas o para realizar aprendizaje automático, generalmente necesitamos concatenar esas tablas, pero es importante mantener un registro de los archivos de origen.
Vamos a abrir dos tablas generadas al aplicar el mismo flujo de trabajo a diferentes archivos.
df1 = pd.read_csv('../../data/BBBC007_20P1_POS0007_D_1UL.csv')
df1.head()
| area | intensity_mean | major_axis_length | minor_axis_length | aspect_ratio | |
|---|---|---|---|---|---|
| 0 | 256 | 93.250000 | 19.995017 | 17.021559 | 1.174688 |
| 1 | 90 | 82.488889 | 15.939969 | 7.516326 | 2.120713 |
| 2 | 577 | 90.637782 | 35.324458 | 21.759434 | 1.623409 |
| 3 | 270 | 95.640741 | 20.229431 | 17.669052 | 1.144908 |
| 4 | 153 | 84.908497 | 15.683703 | 12.420475 | 1.262730 |
df2 = pd.read_csv('../../data/BBBC007_20P1_POS0010_D_1UL.csv')
df2.head()
| area | intensity_mean | major_axis_length | minor_axis_length | aspect_ratio | |
|---|---|---|---|---|---|
| 0 | 139 | 96.546763 | 17.504104 | 10.292770 | 1.700621 |
| 1 | 360 | 86.613889 | 35.746808 | 14.983124 | 2.385805 |
| 2 | 43 | 91.488372 | 12.967884 | 4.351573 | 2.980045 |
| 3 | 140 | 73.742857 | 18.940508 | 10.314404 | 1.836316 |
| 4 | 144 | 89.375000 | 13.639308 | 13.458532 | 1.013432 |
En este caso particular donde sabemos que tenemos las mismas columnas, podríamos concatenarlas en una sola tabla grande.
big_df = pd.concat([df1, df2], axis=0)
big_df
| area | intensity_mean | major_axis_length | minor_axis_length | aspect_ratio | |
|---|---|---|---|---|---|
| 0 | 256 | 93.250000 | 19.995017 | 17.021559 | 1.174688 |
| 1 | 90 | 82.488889 | 15.939969 | 7.516326 | 2.120713 |
| 2 | 577 | 90.637782 | 35.324458 | 21.759434 | 1.623409 |
| 3 | 270 | 95.640741 | 20.229431 | 17.669052 | 1.144908 |
| 4 | 153 | 84.908497 | 15.683703 | 12.420475 | 1.262730 |
| ... | ... | ... | ... | ... | ... |
| 42 | 315 | 91.133333 | 20.927095 | 19.209283 | 1.089426 |
| 43 | 206 | 94.262136 | 23.381879 | 11.669668 | 2.003646 |
| 44 | 45 | 68.377778 | 9.406371 | 6.276445 | 1.498678 |
| 45 | 33 | 76.727273 | 10.724275 | 4.174568 | 2.568955 |
| 46 | 16 | 76.750000 | 7.745967 | 2.783882 | 2.782433 |
111 rows × 5 columns
El problema es que perdemos su identidad de origen. Una solución fácil es agregar una nueva columna con el nombre del archivo antes de concatenarlos. Esto facilitará separarlos nuevamente y graficarlos más adelante.
Cuando asignamos un solo valor a una nueva columna, se asigna a todas las filas.
df1['file_name'] = 'BBBC007_20P1_POS0007_D_1UL'
df2['file_name'] = 'BBBC007_20P1_POS0010_D_1UL'
big_df = pd.concat([df1, df2], axis=0)
big_df
| area | intensity_mean | major_axis_length | minor_axis_length | aspect_ratio | file_name | |
|---|---|---|---|---|---|---|
| 0 | 256 | 93.250000 | 19.995017 | 17.021559 | 1.174688 | BBBC007_20P1_POS0007_D_1UL |
| 1 | 90 | 82.488889 | 15.939969 | 7.516326 | 2.120713 | BBBC007_20P1_POS0007_D_1UL |
| 2 | 577 | 90.637782 | 35.324458 | 21.759434 | 1.623409 | BBBC007_20P1_POS0007_D_1UL |
| 3 | 270 | 95.640741 | 20.229431 | 17.669052 | 1.144908 | BBBC007_20P1_POS0007_D_1UL |
| 4 | 153 | 84.908497 | 15.683703 | 12.420475 | 1.262730 | BBBC007_20P1_POS0007_D_1UL |
| ... | ... | ... | ... | ... | ... | ... |
| 42 | 315 | 91.133333 | 20.927095 | 19.209283 | 1.089426 | BBBC007_20P1_POS0010_D_1UL |
| 43 | 206 | 94.262136 | 23.381879 | 11.669668 | 2.003646 | BBBC007_20P1_POS0010_D_1UL |
| 44 | 45 | 68.377778 | 9.406371 | 6.276445 | 1.498678 | BBBC007_20P1_POS0010_D_1UL |
| 45 | 33 | 76.727273 | 10.724275 | 4.174568 | 2.568955 | BBBC007_20P1_POS0010_D_1UL |
| 46 | 16 | 76.750000 | 7.745967 | 2.783882 | 2.782433 | BBBC007_20P1_POS0010_D_1UL |
111 rows × 6 columns
Ahora podemos distinguir con seguridad el origen de cada fila.