Manejo de valores NaN#

Al analizar datos tabulares, a veces hay celdas de tabla que no contienen datos. En Python, esto típicamente significa que el valor es Not a Number (NaN). No podemos asumir que estos valores son 0 o -1 o cualquier otro valor porque eso distorsionaría las estadísticas descriptivas, por ejemplo. Necesitamos tratar estas entradas NaN de manera diferente y este cuaderno introducirá cómo hacerlo.

Para obtener una primera vista de dónde los NaN juegan un papel, cargamos nuevamente una tabla de ejemplo y la ordenamos.

import numpy as np
import pandas as pd 

Estamos ordenando la tabla por el parámetro area para entender dónde los NaNs juegan un papel. Estamos ordenando la tabla usando sort_values.

data = pd.read_csv('../../data/Results.csv', index_col=0, delimiter=';')
data.sort_values(by = "Area", ascending=False)
Area Mean StdDev Min Max X Y XM YM Major Minor Angle %Area Type
190 2755.0 859.928 235.458 539.0 3880.0 108.710 302.158 110.999 300.247 144.475 24.280 39.318 100 C
81 2295.0 765.239 96.545 558.0 1431.0 375.003 134.888 374.982 135.359 65.769 44.429 127.247 100 B
209 1821.0 847.761 122.074 600.0 1510.0 287.795 321.115 288.074 321.824 55.879 41.492 112.124 100 A
252 1528.0 763.777 83.183 572.0 1172.0 191.969 385.944 192.487 385.697 63.150 30.808 34.424 100 B
265 1252.0 793.371 117.139 579.0 1668.0 262.071 394.497 262.268 394.326 60.154 26.500 50.147 100 A
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
113 1.0 587.000 0.000 587.0 587.0 399.500 117.500 399.500 117.500 1.128 1.128 0.000 100 A
310 1.0 866.000 0.000 866.0 866.0 343.500 408.500 343.500 408.500 1.128 1.128 0.000 100 A
219 1.0 763.000 0.000 763.0 763.0 411.500 296.500 411.500 296.500 1.128 1.128 0.000 100 A
3 NaN NaN NaN 608.0 964.0 NaN NaN NaN 7.665 7.359 NaN 101.121 100 A
5 NaN NaN 69.438 566.0 792.0 348.500 7.500 NaN 7.508 NaN 3.088 NaN 100 A

391 rows × 14 columns

También podríamos usar esta función para ordenar a lo largo de un eje (filas/columnas).

Como puedes ver, hay filas en la parte inferior que contienen NaNs. Estas están en la parte inferior de la tabla porque pandas no puede ordenarlas.

Una comprobación rápida de si hay NaNs en cualquier parte de una tabla es un control de calidad importante para una buena práctica científica:

data.isnull().values.any()
True

Ahora sabemos que tenemos NaNs en nuestra tabla. También podemos obtener algunas ideas más profundas sobre en qué columnas se encuentran estos valores NaN.

data.isnull().sum()
Area      2
Mean      5
StdDev    3
Min       3
Max       3
X         2
Y         3
XM        3
YM        5
Major     8
Minor     3
Angle     1
%Area     0
Type      0
dtype: int64

Para tener una idea de si podemos procesar más esa tabla, es posible que queramos saber el porcentaje de NaNs para cada columna:

data.isnull().mean().sort_values(ascending=False) *100
Major     2.046036
Mean      1.278772
YM        1.278772
StdDev    0.767263
Min       0.767263
Max       0.767263
Y         0.767263
XM        0.767263
Minor     0.767263
Area      0.511509
X         0.511509
Angle     0.255754
%Area     0.000000
Type      0.000000
dtype: float64

Eliminación de filas que contienen NaNs#

Dependiendo del tipo de análisis de datos que se deba realizar, puede tener sentido simplemente ignorar las columnas que contienen valores NaN. Alternativamente, es posible eliminar las filas que contienen NaNs.

Depende de tu proyecto y de lo que sea importante o no para el análisis. No es una respuesta fácil.

data_no_nan = data.dropna(how="any")
data_no_nan 
Area Mean StdDev Min Max X Y XM YM Major Minor Angle %Area Type
1 18.0 730.389 103.354 592.0 948.0 435.000 4.722 434.962 4.697 5.987 3.828 168.425 100 A
2 126.0 718.333 90.367 556.0 1046.0 388.087 8.683 388.183 8.687 16.559 9.688 175.471 100 A
4 68.0 686.985 61.169 571.0 880.0 126.147 8.809 126.192 8.811 15.136 5.720 168.133 100 A
6 669.0 697.164 72.863 539.0 957.0 471.696 26.253 471.694 26.197 36.656 23.237 124.340 100 A
7 5.0 658.600 49.161 607.0 710.0 28.300 8.100 28.284 8.103 3.144 2.025 161.565 100 A
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
383 94.0 746.617 85.198 550.0 1021.0 194.032 498.223 194.014 498.239 17.295 6.920 52.720 100 B
387 152.0 801.599 111.328 582.0 1263.0 348.487 497.632 348.451 497.675 17.773 10.889 11.829 100 A
389 60.0 758.033 77.309 601.0 947.0 259.000 499.300 258.990 499.289 9.476 8.062 90.000 100 A
390 12.0 714.833 67.294 551.0 785.0 240.167 498.167 240.179 498.148 4.606 3.317 168.690 100 A
391 23.0 695.043 67.356 611.0 846.0 49.891 503.022 49.882 502.979 6.454 4.537 73.243 100 A

374 rows × 14 columns

En la parte inferior de esa tabla, puedes ver que todavía contiene 374 de las 391 columnas originales. Si eliminas filas, debes documentar en tu futura publicación científica cuántos de cuántos conjuntos de datos fueron analizados.

Ahora también podemos verificar nuevamente si hay NaNs presentes.

data_no_nan.isnull().values.any()
False

Determinación de filas que contienen NaNs#

En algunos casos de uso, podría ser útil tener una lista de índices de filas donde hay valores NaN.

data = {
    'A': [0, 1, 22, 21, 12, 23],
    'B': [2, 3, np.nan,  2,  12, 22],
    'C': [2, 3, 44,  2,  np.nan, 52],
}

table = pd.DataFrame(data)
table
A B C
0 0 2.0 2.0
1 1 3.0 3.0
2 22 NaN 44.0
3 21 2.0 2.0
4 12 12.0 NaN
5 23 22.0 52.0
np.max(table.isnull().values, axis=1)
array([False, False,  True, False,  True, False])

Eliminación de columnas que contienen NaNs#

Como se mencionó anteriormente, a veces también tiene sentido eliminar columnas. Por ejemplo, si una columna está llena de valores NaN. Para mostrar esto, crearemos dicha columna:

data['difficult_measurement'] = np.nan
nan_table = pd.DataFrame(data)
nan_table
A B C difficult_measurement
0 0 2.0 2.0 NaN
1 1 3.0 3.0 NaN
2 22 NaN 44.0 NaN
3 21 2.0 2.0 NaN
4 12 12.0 NaN NaN
5 23 22.0 52.0 NaN

Ahora podemos eliminar la columna de esta manera:

table_dropped = nan_table.drop('difficult_measurement', axis=1)

Y tener la columna nuevamente eliminada de nuestra tabla

table_dropped
A B C
0 0 2.0 2.0
1 1 3.0 3.0
2 22 NaN 44.0
3 21 2.0 2.0
4 12 12.0 NaN
5 23 22.0 52.0