Capítulo 2 Feature Engineering

2.1 Regresión polinomial

Es común encontrar en la literatura este tema junto con la regresión múltiple, sin embargo, el alcance de esta transformación va a más allá de la regresión lineal, por lo que estudiaremos este tema como parte de la ingeniería de variables y no como un modelo lineal exclusivamente.

El objetivo de la transformación polinomial dentro de la ingeniería de variables es crear nuevas variables que puedan explicar la relación entre la variable de respuesta y explicativa a través de un polinomio de grado k.

En el siguiente gráfico se representa mediante una línea roja a la regresión lineal y mediante una curva azul al polinomio que relaciona a las variables independiente y dependiente.

Es evidente que existe un mejor ajuste cuando se considera un polinomio de grado k en vez de la componente lineal. Este método ofrece mayor flexibilidad para el ajuste de un modelo. Será importante mediar posteriormente entre el sesgo y la varianza de forma que podamos tener un mejor ajuste sin caer en el sobreajuste. La fórmula que expresa la relación entre variable de respuesta y explicativas es ahora:

\[Y_i \sim X_1 + X_1^2\]

Es importante mencionar que cuando se ajusta un modelo polinomial de segundo orden, se deben mantener ambas variables en el model (la original y la cuadrática). Cuando se tenga un modelo polinomial de grado k, se deberán conservar los k elementos que componen el polinomio:

\[Y\sim X_1+X_1^2 + ... + X_1^k\]

Estas transformaciones son posibles realizarlas a múltiples variables que conforman el conjunto de datos que sirve de insumo para el modelo. A través de los pasos secuenciales en las recetas podemos integrar esta tarea a través de la función step_poly( ). Veamos un ejemplo:

library(tidymodels)

n = 100
set.seed(12345)
original_data <- tibble(x = seq(-10, 10, lengt=n)) %>% 
  mutate(y = x^3 + rnorm(n, mean = 0, sd = 100))
  
poly_recipe <- recipe(y ~ x, data = original_data) %>% 
  step_poly(x, degree = 3) %>% 
  prep()

juice(poly_recipe)
## # A tibble: 100 × 4
##        y x_poly_1 x_poly_2 x_poly_3
##    <dbl>    <dbl>    <dbl>    <dbl>
##  1 -941.   -0.171    0.217  -0.249 
##  2 -870.   -0.168    0.204  -0.219 
##  3 -895.   -0.165    0.191  -0.190 
##  4 -874.   -0.161    0.178  -0.163 
##  5 -716.   -0.158    0.166  -0.137 
##  6 -908.   -0.154    0.154  -0.113 
##  7 -616.   -0.151    0.142  -0.0904
##  8 -661.   -0.147    0.131  -0.0690
##  9 -618.   -0.144    0.119  -0.0489
## 10 -640.   -0.140    0.108  -0.0302
## # … with 90 more rows
juice(poly_recipe) %>% lm(y ~ x_poly_1 + x_poly_2 + x_poly_3, data = .)
## 
## Call:
## lm(formula = y ~ x_poly_1 + x_poly_2 + x_poly_3, data = .)
## 
## Coefficients:
## (Intercept)     x_poly_1     x_poly_2     x_poly_3  
##       24.52      3643.95       -34.12      1541.84
juice(poly_recipe) %>% 
  mutate(
    x = seq(-10, 10, lengt=n),
    y_est = 24.52 + 3643.95 * x_poly_1 -34.12 * x_poly_2 + 1541.84 * x_poly_3) %>% 
  ggplot(aes(x = x, y = y)) +
  geom_point() +
  geom_smooth(aes(y = y_est)) +
  ggtitle("Regresión polinomial")

En el ejemplo anterior puede mostrarse que es a través de la ponderación de los términos polinomiales que se logra estimar a la variable de respuesta Y.

Este mismo procedimiento puede usarse en la receta de la ingeniería de datos para realizar la predicción de una variable de respuesta mediante cualquier otro algoritmo predictivo.

2.2 Análisis de Componentes Principales

El análisis PCA (por sus siglas en inglés) es una técnica de reducción de dimensión útil tanto para el proceso de análisis exploratorio, el inferencial y predictivo. Es una técnica ampliamente usada en muchos estudios, pues permite sintetizar la información relevante y desechar aquello que no aporta tanto. Es particularmente útil en el caso de conjuntos de datos “amplios” en donde las variables están correlacionadas entre sí y donde se tienen muchas variables para cada observación.

En los conjuntos de datos donde hay muchas variables presentes, no es fácil trazar los datos en su formato original, lo que dificulta tener una idea de las tendencias presentes en ellos. PCA permite ver la estructura general de los datos, identificando qué observaciones son similares entre sí y cuáles son diferentes. Esto puede permitirnos identificar grupos de muestras que son similares y determinar qué variables hacen a un grupo diferente de otro.

La idea detrás de esta técnica es la siguiente:

  • Se desean crear nuevas variables llamadas Componentes Principales, las cuales son creadas como combinación lineal (transformación lineal) de las variables originales, por lo que cada una de las variables nuevas contiene parcialmente información de todas las variables originales.

\[Z_1 = a_{11}X_1 +a_{12}X_2 + ... + a_{1p}X_p\] \[Z_2 = a_{21}X_1 +a_{22}X_2 + ... + a_{2p}X_p\] \[...\] \[Z_p = a_{p1}X_1 +a_{p2}X_2 + ... + a_{pp}X_p\]

  • Se desea que la primer componente principal capture la mayor varianza posible de todo el conjunto de datos.

\[\forall i \in 2,...,p \quad Var(Z_1)>Var(Z_i)\]

  • La segunda componente principal deberá SER INDEPENDIENTE de la primera y deberá abarcar la mayor varianza posible del restante. Esta condición se debe cumplir para toda componente i, de tal forma que las nuevas componentes creadas son independientes entre sí y acumulan la mayor proporción de varianza en las primeras de ellas, dejando la mínima proporción de varianza a las últimas componentes.

\[Z_1 \perp\!\!\!\perp Z_2 \quad \& \quad Var(Z_1)>Var(Z_2)>Var(Z_i)\]

  • El punto anterior permite desechar unas cuantas componentes (las últimas) sin perder mucha varianza.

¡¡ RECORDAR !!

  • A través de CPA se logra retener la mayor cantidad de varianza útil pero usando menos componentes que el número de variables originales.

  • Para que este proceso sea efectivo, debe existir ALTA correlación entre las variables originales.

Cuando muchas variables se correlacionan entre sí, todas contribuirán fuertemente al mismo componente principal. Cada componente principal suma un cierto porcentaje de la variación total en el conjunto de datos. Cuando sus variables iniciales estén fuertemente correlacionadas entre sí y podrá aproximar la mayor parte de la complejidad de su conjunto de datos con solo unos pocos componentes principales.

Agregar componentes adicionales hace que la estimación del conjunto de datos total sea más precisa, pero también más difícil de manejar.

2.2.1 Eigenvalores y eigenvectores

Los vectores propios y los valores propios vienen en pares: cada vector propio tiene un valor propio correspondiente. Los vectores propios son la ponderación que permite crear la combinación lineal de las variables para conformar cada componente principal, mientras que el valor propio es la varianza asociada a cada componente principal. Desde un punto de vista geométrico, el eigenvector es la dirección del vector determinado por la componente principal y el eigenvalor es la magnitud de dicho vector.

  • El valor propio de una componente es la varianza de este.

  • La suma acumulada de los primeros \(j\) eigenvalores representa la varianza acumulada de las primeras \(j\) componentes principales

El número de valores propios y vectores propios que existe es igual al número de dimensiones que tiene el conjunto de datos.

2.2.2 Implementación en R

library(sf)
library(magrittr)
library(tidymodels)

indice_marg <- st_read('data/IMEF_2010.dbf', quiet = TRUE)
glimpse(indice_marg)
## Rows: 32
## Columns: 16
## $ CVE_ENT <chr> "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "1…
## $ AÑO     <int> 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 20…
## $ POB_TOT <int> 1184996, 3155070, 637026, 822441, 2748391, 650555, 4796580, 34…
## $ ANALF   <dbl> 3.274040, 2.600783, 3.234464, 8.370643, 2.645050, 5.157943, 17…
## $ SPRIM   <dbl> 14.754823, 12.987567, 14.273833, 22.541207, 12.168029, 18.4761…
## $ OVSDE   <dbl> 1.0649743, 0.4322072, 0.9436751, 6.4196750, 1.0916308, 0.68577…
## $ OVSEE   <dbl> 0.62347891, 0.94517891, 2.84464884, 2.59080046, 0.53707721, 0.…
## $ OVSAE   <dbl> 0.9854257, 3.5616214, 7.0865085, 9.7378176, 1.3908497, 1.17060…
## $ VHAC    <dbl> 30.33066, 29.05839, 31.73806, 45.96720, 30.26891, 31.32052, 53…
## $ OVPT    <dbl> 1.761813, 3.398537, 5.814081, 4.500699, 1.423701, 4.691477, 15…
## $ PL_5000 <dbl> 25.1626166, 10.3491523, 15.6188287, 30.8755279, 12.1486353, 14…
## $ PO2SM   <dbl> 33.64880, 21.86970, 23.29986, 45.51076, 30.04270, 32.04402, 69…
## $ IM      <dbl> -0.91086057, -1.14014880, -0.68128749, 0.43357139, -1.14000448…
## $ GM      <chr> "Bajo", "Muy bajo", "Bajo", "Alto", "Muy bajo", "Bajo", "Muy a…
## $ LUGAR   <int> 28, 30, 23, 10, 29, 26, 2, 21, 32, 15, 14, 1, 6, 27, 22, 8, 19…
## $ NOM_ENT <chr> "Aguascalientes", "Baja California", "Baja California Sur", "C…
indice_marg %>% dplyr::count(GM, sort = TRUE)
##         GM n
## 1    Medio 9
## 2     Alto 8
## 3     Bajo 8
## 4 Muy bajo 4
## 5 Muy alto 3
pca_recipe <- recipe(IM ~ ., data = indice_marg) %>%
  update_role(NOM_ENT, GM, new_role = "id") %>%
  step_normalize(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM) %>%
  step_pca(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM, num_comp=9, res="res") %>% 
  step_rm(LUGAR, AÑO, POB_TOT) %>% 
  prep()

juice(pca_recipe)
## # A tibble: 32 × 13
##    CVE_ENT GM     NOM_ENT      IM    PC1     PC2     PC3     PC4     PC5     PC6
##    <fct>   <fct>  <fct>     <dbl>  <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
##  1 01      Bajo   Aguasc… -0.911  -2.34  -0.227   0.372   0.492   0.264   0.0764
##  2 02      Muy b… Baja C… -1.14   -2.93   0.595  -0.0597 -0.492   0.291  -0.0508
##  3 03      Bajo   Baja C… -0.681  -1.75   1.37   -0.683  -0.400  -0.304   0.160 
##  4 04      Alto   Campec…  0.434   1.12  -0.819  -0.151  -0.271  -0.929   0.178 
##  5 05      Muy b… Coahui… -1.14   -2.93  -0.144   0.157  -0.133   0.0419 -0.0786
##  6 06      Bajo   Colima  -0.779  -2.00   0.0316  0.552  -0.136   0.320  -0.729 
##  7 07      Muy a… Chiapas  2.32    5.96   0.132   1.36   -0.0122 -0.673  -0.471 
##  8 08      Bajo   Chihua… -0.520  -1.34   1.05   -1.27    0.633  -0.646  -0.387 
##  9 09      Muy b… Distri… -1.48   -3.81   0.110   0.159  -0.453   0.205  -0.355 
## 10 10      Medio  Durango  0.0525  0.135  0.675  -1.50    0.929  -0.448   0.146 
## # … with 22 more rows, and 3 more variables: PC7 <dbl>, PC8 <dbl>, PC9 <dbl>

Veamos los pasos de esta receta:

  • Primero, debemos decirle a la receta qué datos se usan para predecir la variable de respuesta.

  • Se actualiza el rol de las variables nombre de entidad y grado de marginación con la función NOM_ENT, ya que es una variable que queremos mantener por conveniencia como identificador de filas, pero no son un predictor ni variable de respuesta.

  • Necesitamos centrar y escalar los predictores numéricos, porque estamos a punto de implementar PCA.

  • Finalmente, usamos step_pca() para realizar el análisis de componentes principales.

  • La función prep() es la que realiza toda la preparación de la receta.

Una vez que hayamos hecho eso, podremos explorar los resultados del PCA. Comencemos por ver cómo resultó el PCA. Podemos ordenar los resultados mediante la función tidy(), incluido el paso de PCA, que es el segundo paso. Luego hagamos una visualización para ver cómo se ven los componentes.

A continuación se muestran la desviación estándar, porcentaje de varianza y porcentaje de varianza acumulada que aporta cada componente principal.

summary(pca_recipe$steps[[2]]$res)
## Importance of components:
##                          PC1     PC2    PC3     PC4     PC5     PC6     PC7
## Standard deviation     2.572 0.82085 0.7920 0.64640 0.52101 0.44069 0.31797
## Proportion of Variance 0.735 0.07487 0.0697 0.04643 0.03016 0.02158 0.01123
## Cumulative Proportion  0.735 0.80990 0.8796 0.92603 0.95619 0.97777 0.98900
##                            PC8     PC9
## Standard deviation     0.25660 0.18201
## Proportion of Variance 0.00732 0.00368
## Cumulative Proportion  0.99632 1.00000

Podemos observar que en la primera componente principal, las \(9\) variables que utilizó el Consejo Nacional de Población para obtener el Índice de Marginación 2010 aportan de manera positiva en el primer componente principal.

library(tidytext)

tidied_pca <- tidy(pca_recipe, 2)

tidied_pca %>%
  filter(component %in% paste0("PC", 1:4)) %>%
  group_by(component) %>%
  top_n(9, abs(value)) %>%
  ungroup() %>%
  mutate(terms = reorder_within(terms, abs(value), component)) %>%
  ggplot(aes(abs(value), terms, fill = value > 0)) +
  geom_col() +
  facet_wrap(~component, scales = "free_y") +
  scale_y_reordered() +
  labs(
    x = "Absolute value of contribution",
    y = NULL, fill = "Positive?"
  )+
  theme_minimal()

Notamos que las \(9\) variables aportan entre el \(25\%\) y el \(35\%\) a la primera componente principal.

2.2.3 Representación gráfica

library(ggrepel)

juice(pca_recipe) %>%
  mutate(GM = factor(GM, levels = c("Muy alto", "Alto", "Medio", "Bajo", "Muy bajo")), 
         ordered = T) %>% 
  ggplot(aes(PC1, PC2, label = NOM_ENT)) +
  geom_point(aes(color = GM), alpha = 0.7, size = 2) +
  geom_text_repel() +
  ggtitle("Grado de marginación de entidades")

Finalmente, podemos observar como (de izquierda a derecha) los estados con grado de marginación Muy bajo, Bajo, Medio, Alto y Muy Alto respectivamente.

juice(pca_recipe) %>% 
  ggplot(aes(x = IM, y = PC1)) +
  geom_smooth(method = "lm") +
  geom_point(size = 2) +
  ggtitle("Comparación: Índice Marginación Vs PCA CP1")
## `geom_smooth()` using formula 'y ~ x'

2.2.4 ¿Cuántas componentes retener?

Existe en la literatura basta información sobre el número de componentes a retener en un análisis de PCA. El siguiente gráfico lleva por nombre gráfico de codo y muestra el porcentaje de varianza explicado por cada componente principal.

library(factoextra)
library(FactoMineR)

res.pca <- indice_marg %>%
  select(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM) %>% 
  as.data.frame() %>% 
  set_rownames(indice_marg$NOM_ENT) %>% 
  PCA(graph=FALSE)

fviz_eig(res.pca, addlabels=TRUE, ylim=c(0, 100))

El grafico anterior muestra que hay una diferencia muy grande entre la varianza retenida por la 1er componente principal y el resto de las variables. Dependiendo del objetivo del analisis podra elegirse el numero adecuado de componentes a retener, no obstante, la literatura sugiere retener 1 o 2 componentes principales.

Regresando al tema de feature engineering, es posible realizar el proceso de componentes principales y elegir una de las dos opciones siguientes:

  1. Especificar el número de componentes a retener

  2. Indicar el porcentaje de varianza a alcanzar

La segunda opción elegirá tantas componentes como sean necesarias hasta alcanzar el hiperparámetro mínimo indicado. A continuación se ejemplifica:

Caso 1:

pca_recipe <- recipe(IM ~ ., data = indice_marg) %>%
  update_role(NOM_ENT, GM, new_role = "id") %>%
  step_normalize(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM) %>%
  step_pca(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM,num_comp=2) %>% 
  step_rm(LUGAR, AÑO, POB_TOT) %>% 
  prep()

juice(pca_recipe)
## # A tibble: 32 × 6
##    CVE_ENT GM       NOM_ENT                   IM    PC1     PC2
##    <fct>   <fct>    <fct>                  <dbl>  <dbl>   <dbl>
##  1 01      Bajo     Aguascalientes       -0.911  -2.34  -0.227 
##  2 02      Muy bajo Baja California      -1.14   -2.93   0.595 
##  3 03      Bajo     Baja California Sur  -0.681  -1.75   1.37  
##  4 04      Alto     Campeche              0.434   1.12  -0.819 
##  5 05      Muy bajo Coahuila de Zaragoza -1.14   -2.93  -0.144 
##  6 06      Bajo     Colima               -0.779  -2.00   0.0316
##  7 07      Muy alto Chiapas               2.32    5.96   0.132 
##  8 08      Bajo     Chihuahua            -0.520  -1.34   1.05  
##  9 09      Muy bajo Distrito Federal     -1.48   -3.81   0.110 
## 10 10      Medio    Durango               0.0525  0.135  0.675 
## # … with 22 more rows

Caso 2:

pca_recipe <- recipe(IM ~ ., data = indice_marg) %>%
  update_role(NOM_ENT, GM, new_role = "id") %>%
  step_normalize(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM) %>%
  step_pca(ANALF, SPRIM, OVSDE, OVSEE, OVSAE, VHAC, OVPT, PL_5000, PO2SM,threshold=0.90) %>% 
  step_rm(LUGAR, AÑO, POB_TOT) %>% 
  prep()

juice(pca_recipe)
## # A tibble: 32 × 8
##    CVE_ENT GM       NOM_ENT                   IM    PC1     PC2     PC3     PC4
##    <fct>   <fct>    <fct>                  <dbl>  <dbl>   <dbl>   <dbl>   <dbl>
##  1 01      Bajo     Aguascalientes       -0.911  -2.34  -0.227   0.372   0.492 
##  2 02      Muy bajo Baja California      -1.14   -2.93   0.595  -0.0597 -0.492 
##  3 03      Bajo     Baja California Sur  -0.681  -1.75   1.37   -0.683  -0.400 
##  4 04      Alto     Campeche              0.434   1.12  -0.819  -0.151  -0.271 
##  5 05      Muy bajo Coahuila de Zaragoza -1.14   -2.93  -0.144   0.157  -0.133 
##  6 06      Bajo     Colima               -0.779  -2.00   0.0316  0.552  -0.136 
##  7 07      Muy alto Chiapas               2.32    5.96   0.132   1.36   -0.0122
##  8 08      Bajo     Chihuahua            -0.520  -1.34   1.05   -1.27    0.633 
##  9 09      Muy bajo Distrito Federal     -1.48   -3.81   0.110   0.159  -0.453 
## 10 10      Medio    Durango               0.0525  0.135  0.675  -1.50    0.929 
## # … with 22 more rows

Así es como usaremos el análisis de componentes principales para mejorar la estructura de variables que sirven de input para cualquiera de los modelos posteriores. Continuaremos con un paso más de pre-procesamiento antes de comenzar a aprender nuevos modelos.

2.3 Imputación KNN

Antes de aprender el uso de la función de imputación, recordaremos brevemente como funciona el algoritmo de K-Nearest-Neighbor (KNN)

KNN es un algoritmo de aprendizaje supervisado que podemos usar tanto para regresión como clasificación. Es un algoritmo fácil de interpretar y que permite ser flexible en el balance entre sesgo y varianza (dependiendo de los hiper-parámetros seleccionados).

El algoritmo de K vecinos más cercanos realiza comparaciones entre un nuevo elemento y las observaciones anteriores que ya cuentan con etiqueta. La esencia de este algoritmo está en etiquetar a un nuevo elemento de manera similar a como están etiquetados aquellos K elementos que más se le parecen. Veremos este proceso para cada uno de los posibles casos:

2.3.1 Ventajas y limitaciones del Clasificador KNN

Ventajas:

  • KNN no hace ninguna suposición subyacente sobre los datos.
  • Con la adición de más puntos de datos, el clasificador evoluciona constantemente y es capaz de adaptarse rápidamente a los cambios en el conjunto de datos de entrada.
  • Le da al usuario la flexibilidad de elegir la métrica de medida de distancia.

Desventajas:

  • KNN es muy sensible a los valores atípicos.
  • No funciona con datos faltantes.
  • A medida que crece el conjunto de datos, la clasificación se vuelve más lenta.
  • Existe la llamada maldición de la dimensionalidad.

Este algoritmo es altamente usado para imputación de datos faltantes, ¿tiene lógica, cierto?, con recipes podemos aplicar un paso con la función: step_impute_knn antes llamada step_knnimpute(), podemos observar la documentación de la función en el siguiente enlace.

2.3.2 Implementación en R

Veámos cómo implementar la imputación por KNN para datos faltantes en R:

step_impute_knn(
  recipe,
  ...,                                        ### Variables a imputar
  neighbors = 5,
  impute_with = imp_vars(all_predictors()),
  id = rand_id("impute_knn")
)

Acerca de los parámetros:

  • neighbors: Número de vecinos

  • impute_with: Una llamada a imp_vars para especificar qué variables se usan para imputar las variables. Si una columna se incluye en ambas listas para ser imputada y para ser un predictor de imputación, se eliminará de esta última y no se usará para imputarse a sí misma.

  • id: Una cadena de caracteres que es exclusiva de este paso para identificarlo.

La función utiliza el conjunto de entrenamiento para imputar cualquier otro conjunto de datos. La única función de distancia disponible es la distancia de Gower, que se puede utilizar para combinaciones de datos nominales y numéricos.

Acerca de Gower El coeficiente de similitud de Gower propuesto en 1971 permite la manipulación simultánea de variables cuantitativas y cualitativas en una base de datos, mediante la aplicación de este coeficiente se logra hallar la similitud entre individuos a los cuales se les han medido una serie de características en común. Una similaridad alta, es decir cercana a 1, indicara gran homogeneidad entre los individuos; por el contrario, una similaridad cercana a cero indica que los individuos son diferentes

Vamos a utilizar los datos de biomass de la libreria modeldata que contiene un conjunto de datos donde diferentes combustibles de biomasa se caracterizan por la cantidad de ciertas moléculas (carbono, hidrógeno, oxígeno, nitrógeno y azufre) y el poder calorífico superior correspondiente (HHV). En esta base hemos retirado valores aleatoriamente sobre dos variables para realizar el ejercicio.

library(recipes)
library(modeldata)
library(DataExplorer)
data(biomass)

biomass_te_whole <- as_tibble(biomass)

# induce some missing data at random
set.seed(19735)
carb_missing <- sample(1:nrow(biomass_te_whole), 75)
nitro_missing <- sample(1:nrow(biomass_te_whole), 75)

biomass_te_whole$carbon[carb_missing] <- NA
biomass_te_whole$nitrogen[nitro_missing] <- NA
biomass_te_whole["carb_imputed"] <- "No"
biomass_te_whole$carb_imputed[carb_missing] <- "Yes"
biomass_te_whole["nitro_imputed"] <- "No"
biomass_te_whole$nitro_imputed[nitro_missing] <- "Yes"

biomass_tr <- biomass_te_whole %>% filter( dataset == 'Training')
biomass_te <- biomass_te_whole %>% filter( dataset == "Testing") 

biomass_te_whole[nitro_missing,]
## # A tibble: 75 × 10
##    sample      dataset carbon hydrogen oxygen nitrogen sulfur   HHV carb_imputed
##    <chr>       <chr>    <dbl>    <dbl>  <dbl>    <dbl>  <dbl> <dbl> <chr>       
##  1 Red Alder … Traini…   49.6     6.06   43.8       NA   0.07  19.3 No          
##  2 Cereals, H… Traini…   44.8     5      42.5       NA   0.13  18.4 No          
##  3 Sewage Slu… Traini…   28.3     4.07   17.5       NA   1.25  12.1 No          
##  4 Palm Fibre  Testing   47.5     6.01   36.4       NA   0.3   19.2 No          
##  5 Rice Straw  Traini…   35.7     4.62   39.1       NA   0     14.8 No          
##  6 Prune Pits  Traini…   49.3     6.59   41.8       NA   0.07  20.1 No          
##  7 Ryegrass S… Traini…   46.7     5.8    41.9       NA   0.2   18.5 No          
##  8 Peanut Hul… Traini…   45.8     5.46   39.6       NA   0.12  18.6 No          
##  9 Cereals, H… Traini…   NA       4.9    43.1       NA   0.12  18.2 Yes         
## 10 Erco Char   Traini…   65.9     2.57   12.8       NA   0.1   24.2 No          
## # … with 65 more rows, and 1 more variable: nitro_imputed <chr>
biomass_tr %>% DataExplorer::plot_missing( title = "Train" )

biomass_te %>% DataExplorer::plot_missing( title = "Test" )

En la primera opción vamos a aplicar el paso de immputación indicando qué columnas quieren ser imputadas y con cuáles variables queremos que se haga el proceso.

recipe_esp <- recipe(
  HHV ~ carbon + hydrogen + oxygen + nitrogen + sulfur, 
  data = biomass_tr) %>% 
  step_impute_knn(
    carbon, nitrogen,
    impute_with= imp_vars(hydrogen, oxygen),
    neighbors = 3) %>% 
  prep()

imputed_esp_train <- bake(recipe_esp, biomass_tr)
imputed_esp_test <- bake(recipe_esp, biomass_te) ## Probando la receta con un test

imputed_esp_train %>% DataExplorer::plot_missing( title = "Imputacion Train")

imputed_esp_test %>% DataExplorer::plot_missing(title = "Imputacion Test")

Sin embargo, siempre podemos pedirle al modelo que haga la imputación de todas las variables que tengan nulos, con toda la información de las demás variables disponibles (no nulas).

recipe <- recipe(
  HHV ~ carbon + hydrogen + oxygen + nitrogen + sulfur, 
  data = biomass_tr) %>% 
  step_impute_knn(all_predictors(), neighbors = 3) %>% 
  prep()

imputed <- bake(recipe, biomass_te) %>% 
  bind_cols(biomass_te %>% select(carb_imputed, nitro_imputed)) # prueba con test
imputed %>% DataExplorer::plot_missing()

biomass %>%  
  filter(dataset == 'Testing') %>% 
  select(nitrogen) %>% 
  bind_cols(imputed[,c(4, 8)]) %>% 
  ggplot(aes(x = `nitrogen...2`, y = `nitrogen...1`, color = nitro_imputed)) +
  geom_point() +
  ggtitle("Comparación de datos reales vs imputados (Nitrógeno)") +
  xlab("Imputado") +
  ylab("Real")

biomass %>%  
  filter(dataset == 'Testing') %>% 
  select(carbon) %>% 
  bind_cols(imputed[,c(1, 7)]) %>% 
  ggplot(aes(x = `carbon...2`, y = `carbon...1`, color = carb_imputed)) +
  geom_point() +
  ggtitle("Comparación de datos reales vs imputados (Carbono)") +
  xlab("Imputado") +
  ylab("Real")

2.4 Ejercicios

  1. Cada equipo estará a cargo de desarrollar una receta de feature engineering utilizando los pasos vistos en el curso pasado y el actual.

  2. Tendrán 15 días para probar distintas estrategias que mejoren las predicciones del precio de ventas.

  3. Se deberá entregar y explicar el código creado por el equipo. Este código servirá para los ejercicios de optimización de modelos en los siguientes capítulos.