Capítulo 4. Tratamiento de datos numéricos
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
4.0 Introducción
Datos cuantitativos son la medida de algo, ya sea el tamaño de una clase, las ventas mensuales o los resultados de los estudiantes. La forma natural de representar estas cantidades es numéricamente (por ejemplo, 29 alumnos, 529.392 $ en ventas). En este capítulo, cubriremos numerosas estrategias para transformar datos numéricos brutos en características creadas específicamente para algoritmos de aprendizaje automático.
4.1 Reescalar una característica
Solución
Utiliza MinMaxScaler
de scikit-learn para reescalar una matriz de características:
# Load libraries
import
numpy
as
np
from
sklearn
import
preprocessing
# Create feature
feature
=
np
.
array
([[
-
500.5
],
[
-
100.1
],
[
0
],
[
100.1
],
[
900.9
]])
# Create scaler
minmax_scale
=
preprocessing
.
MinMaxScaler
(
feature_range
=
(
0
,
1
))
# Scale feature
scaled_feature
=
minmax_scale
.
fit_transform
(
feature
)
# Show feature
scaled_feature
array([[ 0. ], [ 0.28571429], [ 0.35714286], [ 0.42857143], [ 1. ]])
Debate
El reescalado es una tarea de preprocesamiento habitual en el aprendizaje automático. Muchos de los algoritmos que se describen más adelante en este libro supondrán que todas las características están en la misma escala, normalmente de 0 a 1 o de -1 a 1. Existen varias técnicas de reescalado, pero una de las más sencillas se denomina escalado mín-máx. El escalado mín-máx utiliza los valores mínimo y máximo de una característica para reescalar los valores dentro de un intervalo. En concreto, min-max calcula
donde es el vector de características, es un elemento individual de la característica y es el elemento reescalado. En nuestro ejemplo, podemos ver en la matriz de salida que la característica se ha reescalado correctamente entre 0 y 1:
array([[ 0. ], [ 0.28571429], [ 0.35714286], [ 0.42857143], [ 1. ]])
MinMaxScaler
de scikit-learn ofrece dos opciones para reescalar una característica. Una opción es utilizar fit
para calcular los valores mínimo y máximo de la característica, y luego utilizar transform
para reescalar la característica. La segunda opción es utilizar fit_transform
para realizar ambas operaciones a la vez. No hay ninguna diferencia matemática entre las dos opciones, pero a veces resulta más práctico mantener las operaciones separadas, porque nos permite aplicar la misma transformación a distintos conjuntos de datos.
4.2 Normalizar una función
Solución
StandardScaler
de scikit-learn realiza ambas transformaciones:
# Load libraries
import
numpy
as
np
from
sklearn
import
preprocessing
# Create feature
x
=
np
.
array
([[
-
1000.1
],
[
-
200.2
],
[
500.5
],
[
600.6
],
[
9000.9
]])
# Create scaler
scaler
=
preprocessing
.
StandardScaler
()
# Transform the feature
standardized
=
scaler
.
fit_transform
(
x
)
# Show feature
standardized
array([[-0.76058269], [-0.54177196], [-0.35009716], [-0.32271504], [ 1.97516685]])
Debate
Una alternativa habitual al escalado mín-máx comentado en la Receta 4. 1 es el reescalado de las características para que tengan una distribución aproximadamente normal estándar. Para conseguirlo, utilizamos la normalización para transformar los datos de modo que tengan una media, de 0 y una desviación típica de 1. En concreto, cada elemento de la característica se transforma de modo que
donde es nuestra forma normalizada de. La característica transformada representa el número de desviaciones estándar del valor original respecto al valor medio de la característica (también llamado puntuación z en estadística).
La normalización es un método de escalado habitual para el preprocesamiento del aprendizaje automático y, según mi experiencia, se utiliza con más frecuencia que el escalado mín-máx. Sin embargo, depende del algoritmo de aprendizaje. Por ejemplo, el análisis de componentes principales suele funcionar mejor utilizando la estandarización, mientras que el escalado mín-máx suele recomendarse para las redes neuronales (ambos algoritmos se tratan más adelante en este libro). Como regla general, yo recomendaría utilizar por defecto la normalización, a menos que tengas una razón específica para utilizar una alternativa.
Podemos ver el efecto de la normalización observando la media y la desviación típica de la salida de nuestra solución:
# Print mean and standard deviation
(
"Mean:"
,
round
(
standardized
.
mean
()))
(
"Standard deviation:"
,
standardized
.
std
())
Mean: 0.0 Standard deviation: 1.0
Si nuestros datos tienen valores atípicos significativos, pueden afectar negativamente a nuestra normalización al afectar a la media y la varianza de la característica. En este caso, suele ser útil reescalar la característica utilizando la mediana y el rango de cuartiles. En scikit-learn, lo hacemos mediante el método RobustScaler
:
# Create scaler
robust_scaler
=
preprocessing
.
RobustScaler
()
# Transform feature
robust_scaler
.
fit_transform
(
x
)
array([[ -1.87387612], [ -0.875 ], [ 0. ], [ 0.125 ], [ 10.61488511]])
4.3 Normalizar las observaciones
Solución
Utiliza Normalizer
con un argumento norm
:
# Load libraries
import
numpy
as
np
from
sklearn.preprocessing
import
Normalizer
# Create feature matrix
features
=
np
.
array
([[
0.5
,
0.5
],
[
1.1
,
3.4
],
[
1.5
,
20.2
],
[
1.63
,
34.4
],
[
10.9
,
3.3
]])
# Create normalizer
normalizer
=
Normalizer
(
norm
=
"l2"
)
# Transform feature matrix
normalizer
.
transform
(
features
)
array([[ 0.70710678, 0.70710678], [ 0.30782029, 0.95144452], [ 0.07405353, 0.99725427], [ 0.04733062, 0.99887928], [ 0.95709822, 0.28976368]])
Debate
Muchos métodos de reescalado (por ejemplo, el escalado mín-máx y la normalización) operan sobre características; sin embargo, también podemos reescalar a través de observaciones individuales. Normalizer
reescala los valores de las observaciones individuales para que tengan norma unitaria (la suma de sus longitudes es 1). Este tipo de reescalado se suele utilizar cuando tenemos muchas características equivalentes (por ejemplo, en la clasificación de textos, cuando cada palabra o grupo de n palabras es una característica).
Normalizer
proporciona tres opciones de norma, siendo la norma euclidiana (a menudo llamada L2) el argumento por defecto:
donde es una observación individual y es el valor de esa observación para la rasgo.
# Transform feature matrix
features_l2_norm
=
Normalizer
(
norm
=
"l2"
)
.
transform
(
features
)
# Show feature matrix
features_l2_norm
array([[ 0.70710678, 0.70710678], [ 0.30782029, 0.95144452], [ 0.07405353, 0.99725427], [ 0.04733062, 0.99887928], [ 0.95709822, 0.28976368]])
Alternativamente, podemos especificar la norma de Manhattan (L1):
# Transform feature matrix
features_l1_norm
=
Normalizer
(
norm
=
"l1"
)
.
transform
(
features
)
# Show feature matrix
features_l1_norm
array([[ 0.5 , 0.5 ], [ 0.24444444, 0.75555556], [ 0.06912442, 0.93087558], [ 0.04524008, 0.95475992], [ 0.76760563, 0.23239437]])
Intuitivamente, la norma L2 puede considerarse como la distancia entre dos puntos de Nueva York para un pájaro (es decir, una línea recta), mientras que la L1 puede considerarse como la distancia para un humano que camina por la calle (camina una manzana hacia el norte, una hacia el este, una hacia el norte, una hacia el este, etc.), por eso se llama "norma Manhattan" o "norma Taxicab".
En la práctica, observa que norm="l1"
reescala los valores de una observación para que sumen 1, lo que a veces puede ser una cualidad deseable:
# Print sum
(
"Sum of the first observation
\'
s values:"
,
features_l1_norm
[
0
,
0
]
+
features_l1_norm
[
0
,
1
])
Sum of the first observation's values: 1.0
4.4 Generar características polinómicas y de interacción
Solución
Aunque algunos optan por crear manualmente las características polinómicas y de interacción de , scikit-learn ofrece un método incorporado:
# Load libraries
import
numpy
as
np
from
sklearn.preprocessing
import
PolynomialFeatures
# Create feature matrix
features
=
np
.
array
([[
2
,
3
],
[
2
,
3
],
[
2
,
3
]])
# Create PolynomialFeatures object
polynomial_interaction
=
PolynomialFeatures
(
degree
=
2
,
include_bias
=
False
)
# Create polynomial features
polynomial_interaction
.
fit_transform
(
features
)
array([[ 2., 3., 4., 6., 9.], [ 2., 3., 4., 6., 9.], [ 2., 3., 4., 6., 9.]])
El parámetro degree
determina el grado máximo del polinomio. Por ejemplo, degree=2
creará nuevas características elevadas a la segunda potencia:
mientras que degree=3
creará nuevas características elevadas a la segunda y tercera potencia:
Además, por defecto PolynomialFeatures
incluye funciones de interacción:
Podemos restringir las funciones creadas a sólo funciones de interacción estableciendointeraction_only
a True
:
interaction
=
PolynomialFeatures
(
degree
=
2
,
interaction_only
=
True
,
include_bias
=
False
)
interaction
.
fit_transform
(
features
)
array([[ 2., 3., 6.], [ 2., 3., 6.], [ 2., 3., 6.]])
Debate
Las características polinómicas suelen crearse cuando queremos incluir la noción de que existe una relación no lineal entre las características y el objetivo. Por ejemplo, podríamos sospechar que el efecto de la edad sobre la probabilidad de padecer una enfermedad grave no es constante en el tiempo, sino que aumenta a medida que aumenta la edad. Podemos codificar ese efecto no constante en una característica generando las formas de orden superior de esa característica (, etc.).
Además, a menudo nos encontramos con situaciones en las que el efecto de una característica depende de otra. Un ejemplo sencillo sería si intentáramos predecir si nuestro café está dulce o no, y tuviéramos dos características: (1) si el café se ha removido o no, y (2) si hemos añadido azúcar o no. Por separado, cada característica no predice el dulzor del café, pero la combinación de sus efectos sí. Es decir, un café sólo sería dulce si el café tuviera azúcar y estuviera removido. Los efectos de cada rasgo sobre el objetivo (el dulzor) dependen unos de otros. Podemos codificar esa relación incluyendo un rasgo de interacción que sea el producto de los rasgos individuales.
4.5 Funciones de transformación
Solución
En scikit-learn, utiliza FunctionTransformer
para aplicar una función a un conjunto de características:
# Load libraries
import
numpy
as
np
from
sklearn.preprocessing
import
FunctionTransformer
# Create feature matrix
features
=
np
.
array
([[
2
,
3
],
[
2
,
3
],
[
2
,
3
]])
# Define a simple function
def
add_ten
(
x
:
int
)
->
int
:
return
x
+
10
# Create transformer
ten_transformer
=
FunctionTransformer
(
add_ten
)
# Transform feature matrix
ten_transformer
.
transform
(
features
)
array([[12, 13], [12, 13], [12, 13]])
Podemos crear la misma transformación en pandas utilizando apply
:
# Load library
import
pandas
as
pd
# Create DataFrame
df
=
pd
.
DataFrame
(
features
,
columns
=
[
"feature_1"
,
"feature_2"
])
# Apply function
df
.
apply
(
add_ten
)
característica_1 | característica_2 | |
---|---|---|
0 | 12 | 13 |
1 | 12 | 13 |
2 | 12 | 13 |
Debate
Es habitual querer hacer algunas transformaciones personalizadas a una o varias características. Por ejemplo, podríamos querer crear una característica que sea el logaritmo natural de los valores de una característica diferente. Podemos hacerlo creando una función y luego asignándola a las características de utilizando FunctionTransformer
de scikit-learn o apply
de pandas. En la solución hemos creado una función muy sencilla, add_ten
, que suma 10 a cada entrada, pero no hay razón para que no podamos definir una función mucho más compleja.
4.6 Detectar valores atípicos
Solución
Desgraciadamente, detectar los valores atípicos es más un arte que una ciencia. Sin embargo, un método habitual consiste en suponer que los datos se distribuyen normalmente y, basándose en esa suposición, "dibujar" una elipse alrededor de los datos, clasificando cualquier observación dentro de la elipse como un valor anómalo (etiquetadocomo1
) y cualquier observación fuera de la elipse como un valor atípico (etiquetadocomo-1
):
# Load libraries
import
numpy
as
np
from
sklearn.covariance
import
EllipticEnvelope
from
sklearn.datasets
import
make_blobs
# Create simulated data
features
,
_
=
make_blobs
(
n_samples
=
10
,
n_features
=
2
,
centers
=
1
,
random_state
=
1
)
# Replace the first observation's values with extreme values
features
[
0
,
0
]
=
10000
features
[
0
,
1
]
=
10000
# Create detector
outlier_detector
=
EllipticEnvelope
(
contamination
=
.1
)
# Fit detector
outlier_detector
.
fit
(
features
)
# Predict outliers
outlier_detector
.
predict
(
features
)
array([-1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
En estas matrices, los valores de -1 se refieren a valores atípicos, mientras que los valores de 1 se refieren a valores atípicos. Una limitación importante de este enfoque es la necesidad de especificar un parámetrocontamination
, que es la proporción de observaciones que son valores atípicos, un valor que desconocemos. Piensa en contamination
como nuestra estimación de la limpieza de nuestros datos. Si esperamos que nuestros datos tengan pocos valores atípicos, podemos fijar contamination
en un valor pequeño. Sin embargo, si creemos que es probable que los datos tengan valores atípicos, podemos fijarlo en un valor más alto.
En lugar de mirar las observaciones en su conjunto, podemos mirar las características individuales e identificar los valores extremos en esas características utilizando el rango intercuartílico (IQR):
# Create one feature
feature
=
features
[:,
0
]
# Create a function to return index of outliers
def
indicies_of_outliers
(
x
:
int
)
->
np
.
array
(
int
):
q1
,
q3
=
np
.
percentile
(
x
,
[
25
,
75
])
iqr
=
q3
-
q1
lower_bound
=
q1
-
(
iqr
*
1.5
)
upper_bound
=
q3
+
(
iqr
*
1.5
)
return
np
.
where
((
x
>
upper_bound
)
|
(
x
<
lower_bound
))
# Run function
indicies_of_outliers
(
feature
)
(array([0]),)
La IQR es la diferencia entre el primer y el tercer cuartil de un conjunto de datos. Puedes pensar en el IQR como la dispersión del grueso de los datos, siendo los valores atípicos las observaciones alejadas de la concentración principal de datos. Los valores atípicos suelen definirse como cualquier valor 1,5 IQR inferior al primer cuartil, o 1,5 IQR superior al tercer cuartil.
Debate
No existe una única técnica mejor para detectar valores atípicos. En su lugar, tenemos una colección de técnicas, todas ellas con sus propias ventajas e inconvenientes. Nuestra mejor estrategia suele consistir en probar varias técnicas (por ejemplo, tanto la detección basada en EllipticEnvelope
como la basada en IQR) y observar los resultados en conjunto.
Si es posible, debemos fijarnos en las observaciones que detectamos como atípicas e intentar comprenderlas. Por ejemplo, si tenemos un conjunto de datos de casas y una característica es el número de habitaciones, ¿un valor atípico con 100 habitaciones es realmente una casa o es en realidad un hotel que se ha clasificado erróneamente?
4.7 Manejo de valores atípicos
Solución
Normalmente podemos utilizar tres estrategias para tratar los valores atípicos. En primer lugar, podemos descartarlos:
# Load library
import
pandas
as
pd
# Create DataFrame
houses
=
pd
.
DataFrame
()
houses
[
'Price'
]
=
[
534433
,
392333
,
293222
,
4322032
]
houses
[
'Bathrooms'
]
=
[
2
,
3.5
,
2
,
116
]
houses
[
'Square_Feet'
]
=
[
1500
,
2500
,
1500
,
48000
]
# Filter observations
houses
[
houses
[
'Bathrooms'
]
<
20
]
Precio | Baños | Pies_cuadrados | |
---|---|---|---|
0 | 534433 | 2.0 | 1500 |
1 | 392333 | 3.5 | 2500 |
2 | 293222 | 2.0 | 1500 |
En segundo lugar, podemos marcarlos como valores atípicos e incluir "Valor atípico" como característica:
# Load library
import
numpy
as
np
# Create feature based on boolean condition
houses
[
"Outlier"
]
=
np
.
where
(
houses
[
"Bathrooms"
]
<
20
,
0
,
1
)
# Show data
houses
Precio | Baños | Pies_cuadrados | Outlier | |
---|---|---|---|---|
0 | 534433 | 2.0 | 1500 | 0 |
1 | 392333 | 3.5 | 2500 | 0 |
2 | 293222 | 2.0 | 1500 | 0 |
3 | 4322032 | 116.0 | 48000 | 1 |
Por último, podemos transformar la característica para amortiguar el efecto del valor atípico:
# Log feature
houses
[
"Log_Of_Square_Feet"
]
=
[
np
.
log
(
x
)
for
x
in
houses
[
"Square_Feet"
]]
# Show data
houses
Precio | Baños | Pies_cuadrados | Outlier | Log_de_pies_cuadrados | |
---|---|---|---|---|---|
0 | 534433 | 2.0 | 1500 | 0 | 7.313220 |
1 | 392333 | 3.5 | 2500 | 0 | 7.824046 |
2 | 293222 | 2.0 | 1500 | 0 | 7.313220 |
3 | 4322032 | 116.0 | 48000 | 1 | 10.778956 |
Debate
Al igual que ocurre con la detección de valores atípicos, no existe una regla rígida para tratarlos. La forma de tratarlos debe basarse en dos aspectos. En primer lugar, debemos considerar qué los convierte en valores atípicos. Si creemos que son errores en los datos, como los de un sensor averiado o un valor mal codificado, entonces podríamos descartar la observación o sustituir los valores atípicos por NaN
, ya que no podemos fiarnos de esos valores. Sin embargo, si creemos que los valores atípicos son auténticos valores extremos (por ejemplo, una casa [mansión] con 200 baños), entonces es más apropiado marcarlos como valores atípicos o transformar sus valores.
En segundo lugar, la forma de tratar los valores atípicos debe basarse en nuestro objetivo para el aprendizaje automático. Por ejemplo, si queremos predecir el precio de la vivienda basándonos en sus características, podríamos suponer razonablemente que el precio de las mansiones con más de 100 baños se rige por una dinámica diferente a la de las viviendas unifamiliares normales. Además, si estamos entrenando un modelo para utilizarlo como parte de una aplicación web de préstamos hipotecarios en línea, podríamos suponer que entre nuestros usuarios potenciales no habrá multimillonarios que busquen comprar una mansión.
Entonces, ¿qué debemos hacer si tenemos valores atípicos? Piensa por qué son valores atípicos, ten en mente un objetivo final para los datos y, lo más importante, recuerda que no tomar la decisión de abordar los valores atípicos es en sí misma una decisión con implicaciones.
Un punto adicional: si tienes valores atípicos, la estandarización podría no ser adecuada porque la media y la varianza podrían estar muy influidas por los valores atípicos. En este caso , utiliza un método de reescalado más robusto frente a los valores atípicos, como RobustScaler
.
Ver también
4.8 Funciones de discretización
Solución
Dependiendo de cómo queramos dividir los datos, podemos utilizar dos técnicas. En primer lugar, podemos binarizar la característica según algún umbral:
# Load libraries
import
numpy
as
np
from
sklearn.preprocessing
import
Binarizer
# Create feature
age
=
np
.
array
([[
6
],
[
12
],
[
20
],
[
36
],
[
65
]])
# Create binarizer
binarizer
=
Binarizer
(
threshold
=
18
)
# Transform feature
binarizer
.
fit_transform
(
age
)
array([[0], [0], [1], [1], [1]])
En segundo lugar, podemos dividir los rasgos numéricos según varios umbrales:
# Bin feature
np
.
digitize
(
age
,
bins
=
[
20
,
30
,
64
])
array([[0], [0], [1], [2], [3]])
Ten en cuenta que los argumentos del parámetro bins
denotan el perímetro izquierdo de cada recipiente. Por ejemplo, el argumento 20
no incluye el elemento con el valor 20, sólo los dos valores menores que 20. Podemos cambiar este comportamiento ajustando el parámetro right
a True
:
# Bin feature
np
.
digitize
(
age
,
bins
=
[
20
,
30
,
64
],
right
=
True
)
array([[0], [0], [0], [2], [3]])
Debate
La discretización puede ser una estrategia fructífera cuando tenemos razones para creer que un rasgo numérico debería comportarse más como un rasgo categórico. Por ejemplo, podríamos creer que hay muy poca diferencia en los hábitos de consumo de los jóvenes de 19 y 20 años, pero una diferencia significativa entre los jóvenes de 20 y 21 años (la edad en Estados Unidos en la que los adultos jóvenes pueden consumir alcohol). En ese ejemplo, podría ser útil dividir a los individuos de nuestros datos entre los que pueden beber alcohol y los que no. Del mismo modo, en otros casos podría ser útil discretizar nuestros datos en tres o más intervalos.
En la solución , vimos dos métodos de discretización: Binarizer
de Scikit-learn para dos intervalos y digitize
de NumPy para tres o más intervalos; sin embargo, también podemos utilizar digitize
para binarizar características como Binarizer
especificando un único umbral:
# Bin feature
np
.
digitize
(
age
,
bins
=
[
18
])
array([[0], [0], [1], [1], [1]])
Ver también
4.9 Agrupar Observaciones Mediante Agrupaciones (Clustering)
Solución
Si en sabes que tienes k grupos, puedes utilizar la agrupación de k-medias para agrupar observaciones similares y generar una nueva característica que contenga la pertenencia a un grupo de cada observación:
# Load libraries
import
pandas
as
pd
from
sklearn.datasets
import
make_blobs
from
sklearn.cluster
import
KMeans
# Make simulated feature matrix
features
,
_
=
make_blobs
(
n_samples
=
50
,
n_features
=
2
,
centers
=
3
,
random_state
=
1
)
# Create DataFrame
dataframe
=
pd
.
DataFrame
(
features
,
columns
=
[
"feature_1"
,
"feature_2"
])
# Make k-means clusterer
clusterer
=
KMeans
(
3
,
random_state
=
0
)
# Fit clusterer
clusterer
.
fit
(
features
)
# Predict values
dataframe
[
"group"
]
=
clusterer
.
predict
(
features
)
# View first few observations
dataframe
.
head
(
5
)
característica_1 | característica_2 | grupo | |
---|---|---|---|
0 | -9.877554 | -3.336145 | 0 |
1 | -7.287210 | -8.353986 | 2 |
2 | -6.943061 | -7.023744 | 2 |
3 | -7.440167 | -8.791959 | 2 |
4 | -6.641388 | -8.075888 | 2 |
Debate
Nos estamos adelantando un poco y profundizaremos mucho más en los algoritmos de agrupación más adelante en el libro. En concreto, utilizamos algoritmos de aprendizaje no supervisado, como k-means, para agrupar las observaciones. El resultado es una característica categórica en la que las observaciones similares son miembros del mismo grupo.
No te preocupes si no has entendido todo eso: limítate a archivar la idea de que la agrupación puede utilizarse en el preprocesamiento. Y si realmente no puedes esperar, pasa ahora al Capítulo 19.
4.10 Borrar observaciones con valores perdidos
Solución
Eliminar las observaciones de con valores perdidos es fácil con una línea inteligente de NumPy:
# Load library
import
numpy
as
np
# Create feature matrix
features
=
np
.
array
([[
1.1
,
11.1
],
[
2.2
,
22.2
],
[
3.3
,
33.3
],
[
4.4
,
44.4
],
[
np
.
nan
,
55
]])
# Keep only observations that are not (denoted by ~) missing
features
[
~
np
.
isnan
(
features
)
.
any
(
axis
=
1
)]
array([[ 1.1, 11.1], [ 2.2, 22.2], [ 3.3, 33.3], [ 4.4, 44.4]])
Como alternativa, podemos eliminar las observaciones que faltan utilizando pandas:
# Load library
import
pandas
as
pd
# Load data
dataframe
=
pd
.
DataFrame
(
features
,
columns
=
[
"feature_1"
,
"feature_2"
])
# Remove observations with missing values
dataframe
.
dropna
()
característica_1 | característica_2 | |
---|---|---|
0 | 1.1 | 11.1 |
1 | 2.2 | 22.2 |
2 | 3.3 | 33.3 |
3 | 4.4 | 44.4 |
Debate
La mayoría de los algoritmos de aprendizaje automático no pueden manejar los valores que faltan en las matrices de objetivos y características. Por esta razón, no podemos ignorar los valores perdidos en nuestros datos y debemos abordar el problema durante el preprocesamiento.
La solución más sencilla es eliminar cada observación que contenga uno o más valores perdidos, una tarea que se realiza rápida y fácilmente utilizando NumPy o pandas.
Dicho esto, debemos ser muy reacios a borrar las observaciones con valores perdidos. Borrarlas es la opción nuclear, ya que nuestro algoritmo pierde el acceso a la información contenida en los valores no ausentes de la observación.
Igual de importante es que, dependiendo de la causa de los valores omitidos, la eliminación de observaciones puede introducir sesgos en nuestros datos de . Hay tres tipos de datos omitidos:
- Falta completamente al azar (MCAR)
-
La probabilidad de que falte un valor es independiente de todo. Por ejemplo, un encuestado lanza un dado antes de responder a una pregunta: si saca un seis, se salta esa pregunta.
- Falta al azar (MAR)
-
La probabilidad de que falte un valor no es completamente aleatoria, sino que depende de la información captada en otras características. Por ejemplo, en una encuesta se pregunta sobre la identidad de género y el salario anual, y es más probable que las mujeres omitan la pregunta sobre el salario; sin embargo, su falta de respuesta depende sólo de la información que hayamos capturado en nuestra característica de identidad de género.
- Falta no aleatoria (MNAR)
-
La probabilidad de que falte un valor no es aleatoria y depende de información no recogida en nuestras características. Por ejemplo, en una encuesta se pregunta por el salario anual, y es más probable que las mujeres se salten la pregunta sobre el salario, y no tenemos una característica de identidad de género en nuestros datos.
A veces es aceptable eliminar observaciones si son MCAR o MAR. Sin embargo, si el valor es MNAR, el hecho de que falte un valor es en sí mismo información. Eliminar observaciones MNAR puede inyectar sesgo en nuestros datos, porque estamos eliminando observaciones producidas por algún efecto sistemático no observado.
4.11 Imputar valores perdidos
Solución
Puedes imputar los valores perdidos utilizando k-nearest neighbors (KNN) o la clase SimpleImputer
de scikit-learn. Si tienes pocos datos, predice e imputa los valores perdidos utilizando vecinos más cercanos:
# Load libraries
import
numpy
as
np
from
sklearn.impute
import
KNNImputer
from
sklearn.preprocessing
import
StandardScaler
from
sklearn.datasets
import
make_blobs
# Make a simulated feature matrix
features
,
_
=
make_blobs
(
n_samples
=
1000
,
n_features
=
2
,
random_state
=
1
)
# Standardize the features
scaler
=
StandardScaler
()
standardized_features
=
scaler
.
fit_transform
(
features
)
# Replace the first feature's first value with a missing value
true_value
=
standardized_features
[
0
,
0
]
standardized_features
[
0
,
0
]
=
np
.
nan
# Predict the missing values in the feature matrix
knn_imputer
=
KNNImputer
(
n_neighbors
=
5
)
features_knn_imputed
=
knn_imputer
.
fit_transform
(
standardized_features
)
# Compare true and imputed values
(
"True Value:"
,
true_value
)
(
"Imputed Value:"
,
features_knn_imputed
[
0
,
0
])
True Value: 0.8730186114 Imputed Value: 1.09553327131
Como alternativa, podemos utilizar la clase SimpleImputer
de scikit-learn del módulo imputer
para rellenar los valores perdidos con la media, la mediana o el valor más frecuente de la característica. Sin embargo, normalmente obtendremos peores resultados que con KNN:
# Load libraries
import
numpy
as
np
from
sklearn.impute
import
SimpleImputer
from
sklearn.preprocessing
import
StandardScaler
from
sklearn.datasets
import
make_blobs
# Make a simulated feature matrix
features
,
_
=
make_blobs
(
n_samples
=
1000
,
n_features
=
2
,
random_state
=
1
)
# Standardize the features
scaler
=
StandardScaler
()
standardized_features
=
scaler
.
fit_transform
(
features
)
# Replace the first feature's first value with a missing value
true_value
=
standardized_features
[
0
,
0
]
standardized_features
[
0
,
0
]
=
np
.
nan
# Create imputer using the "mean" strategy
mean_imputer
=
SimpleImputer
(
strategy
=
"mean"
)
# Impute values
features_mean_imputed
=
mean_imputer
.
fit_transform
(
features
)
# Compare true and imputed values
(
"True Value:"
,
true_value
)
(
"Imputed Value:"
,
features_mean_imputed
[
0
,
0
])
True Value: 0.8730186114 Imputed Value: -3.05837272461
Debate
Hay dos estrategias principales para sustituir los datos que faltan por valores sustitutivos, cada una de las cuales tiene puntos fuertes y débiles. En primer lugar, podemos utilizar el aprendizaje automático para predecir los valores de los datos que faltan. Para ello, tratamos la característica con valores omitidos como un vector objetivo y utilizamos el subconjunto restante de características para predecir los valores omitidos. Aunque podemos utilizar una amplia gama de algoritmos de aprendizaje automático para imputar valores, una opción popular es KNN. El KNN se trata en profundidad en el Capítulo 15, pero la explicación breve es que el algoritmo utiliza las k observaciones más cercanas (según alguna métrica de distancia) para predecir el valor que falta. En nuestra solución, predecimos el valor que faltaba utilizando las cinco observaciones más cercanas.
El inconveniente de KNN es que, para saber qué observaciones están más cerca del valor que falta, tiene que calcular la distancia entre el valor que falta y cada una de las observaciones. Esto es razonable en conjuntos de datos pequeños, pero se convierte rápidamente en problemático si un conjunto de datos tiene millones de observaciones. En tales casos, los vecinos más próximos aproximados (RNA) son un enfoque más factible. Hablaremos de ANN enla Receta 15.5.
Una estrategia alternativa y más escalable que KNN es rellenar los valores perdidos de los datos numéricos con la media, la mediana o la moda. Por ejemplo, en nuestra solución utilizamos scikit-learn para rellenar los valores perdidos con el valor medio de una característica. A menudo, el valor imputado no se aproxima tanto al valor real como cuando utilizamos KNN, pero podemos escalar el relleno de la media a datos que contengan millones de observaciones más fácilmente.
Si utilizamos la imputación, conviene crear una característica binaria que indique si la observación contiene un valor imputado.
Get Recetario de Aprendizaje Automático con Python, 2ª Edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.