⚠️ Antes de empezar ...

❗️ Este post tiene como objetivo analizar el uso de algunos componentes del ecosistema tidymodels, mostrando las ventajas y casos de uso que pueden ser de utilidad en el trabajo diario de cualquier científico de datos 🤖. Siéntase libre de contribuir comentando o sugiriendo cualquier idea que considere 🙌. No soy un experto en el tema, pero estoy aprendiendo constantemente.

Resumen 📖

¿De qué hablaremos en este post?

🔘 Un "Workflow" 🔄 es el componente fundamental que permite conectar el preprocesamiento de datos y el modelado predictivo 💡 dentro del marco de tidymodels del lenguaje R. Se explica didácticamente la creación de un modelo, la construcción de una receta de preprocesamiento y la unificación de ambos elementos en un workflow 👌.

👉🏻 Para no usar los típicos datasets que habitualmente se muestran en cualquier post de ciencia de datos, se hará uso de un conjunto de datos referente a los delitos ocurridos en la ciudad de Buenos Aires 🚨.

¿Qué tópicos específicos trataremos en el post?

  • Creación de modelos 🧪

  • Creación de recetas 🥘

  • Implementación de workflows 🔄

📍 En una segunda parte, se mostrarán casos de uso avanzados, como el pivot de puntos en el mapa 🗺️ , reemuestreo espacial y discretización de localizaciones (lat/long) con modelos predictivos 🌐

🔎 Para estudiar con mayor profundidad y encontrar ejemplos reproducibles de casos de uso, se recomienda visitar la página oficial de tidymodels.

Empecemos 🚀

El foco de este post está orientado a la construcción e implementación de workflows, sin embargo, previamente se muestran las bibliotecas utilizadas y el dataset seleccionado como caso de uso.

Libraries 📚

library(tidymodels)
library(tidyverse)
library(leaflet)

Datos 💾

🔘 Los datos corresponden al recuento de delitos ocurridos en 2023 esquinas de la ciudad de Buenos Aires, durante diciembre del 2017 y 2019. Cada fila corresponde a una esquina en un momento específico, dicho momento se refleja en la columna pliegue, en total son 13 momentos por 2023 esquinas siendo un total de 26.999 observaciones.

👉🏻 Contiene además factores meteorológicos y de entorno físico, para estudiar en detalle la construcción y un caso de uso de este dataset consultar Predicción de delitos en CABA. Los datos se encuentran almacenados en el paquete sknifedatar 📦, a continuación se muestra el código para la descarga del paquete y el uso del dataset.

#install.packages("devtools")
devtools::install_github("rafzamb/sknifedatar")
library(sknifedatar)

data(data_crime_clime)
data_crime_clime %>% print(n_extra = 0)
# A tibble: 26,299 x 55
   id    pliegue delitos_last_ye… delitos_last_12 delitos_last_6
   <chr>   <int>            <dbl>           <dbl>          <dbl>
 1 esqu…       1                1              73             41
 2 esqu…       1                5             106             47
 3 esqu…       1                4              25             11
 4 esqu…       1                0               4              1
 5 esqu…       1                0              31             16
 6 esqu…       1                1              11              6
 7 esqu…       1                1              16             11
 8 esqu…       1                1              19              8
 9 esqu…       1                0               9              6
10 esqu…       1                3              27             14
# … with 26,289 more rows, and 50 more variables

🔎 En muchas de las salidas se observará la función print(n_extra = 0), en el caso de replicar el código, no hace falta agregarla, se utiliza simplemente como un elemento estético de la tabla.

Partición de datos ➗

🔘 Aunque el objetivo no es el ajuste de hiperparámetros o la aplicación de validación cruzada, se propone realizar una partición de los datos. Esto para fines didácticos 👨‍🏫, ya que permiten mostrar cómo aplicar flujos de trabajo y recetas sobre nuevos datos.

👉🏻 Para crear un conjunto de train y test, simplemente se aplica la función initial_split(). Requiere el dataset, la proporción de la partición y opcionalmente la estratificación por una variable.

set.seed(123)
splits <- initial_split(data_crime_clime, prop =0.8, strata =delitos)
splits
<Analysis/Assess/Total>
<21042/5257/26299>

❗La partición se guarda en la variable splits, siendo un objeto de tipo "rsplit" que almacena internamente el train y el test. Es decir, no hace falta crear en memoria los objetos train y test, splits ya tiene asociada las funciones training() y testing() para generar los objetos.

training(splits) %>% print(n_extra =0)
# A tibble: 21,042 x 55
   id    pliegue delitos_last_ye… delitos_last_12 delitos_last_6
   <chr>   <int>            <dbl>           <dbl>          <dbl>
 1 esqu…       1                5             106             47
 2 esqu…       1                4              25             11
 3 esqu…       1                0              31             16
 4 esqu…       1                1              11              6
 5 esqu…       1                1              16             11
 6 esqu…       1                1              19              8
 7 esqu…       1                0               9              6
 8 esqu…       1                3              22             11
 9 esqu…       1                0               9              1
10 esqu…       1                2              28             13
# … with 21,032 more rows, and 50 more variables
testing(splits) %>% print(n_extra =0)
# A tibble: 5,257 x 55
   id    pliegue delitos_last_ye… delitos_last_12 delitos_last_6
   <chr>   <int>            <dbl>           <dbl>          <dbl>
 1 esqu…       1                1              73             41
 2 esqu…       1                0               4              1
 3 esqu…       1                3              27             14
 4 esqu…       1                0              15              8
 5 esqu…       1                7              95             52
 6 esqu…       1               31             264            135
 7 esqu…       1                2              38             19
 8 esqu…       1                3              48             23
 9 esqu…       1                1              34             23
10 esqu…       1                1               8              5
# … with 5,247 more rows, and 50 more variables

¿Qué es un workflow 🔄 ? 🤔

🔎 Es un objeto perteneciente al ecosistema tidymodels, enlaza el preprocesamiento de datos con el modelado. Esto permite ajustar y realizar predicciones con un simple llamado fit/predict, sobre un objeto único que ya guarda todo el preprocesamiento realizado.

📌 Consta de 2 componentes, la receta 🍳 de preprocesamiento y el modelo 🤖 seleccionado, a continuación, se muestra el uso de estos dos elementos para finalmente unificarlos en un workflow.

¿Cómo crear un modelo? 🧪

👉🏻 Dentro de tidymodels, los modelos se crean principalmente a través del paquete parsnip 📦 (se puede usar automáticamente al cargar tidymodels), sigue la siguiente secuencia:

  • Selección del modelo.

  • Definición del algoritmo (paquete).

  • Especificar el tipo de predicción.

🔘 Veamos un ejemplo, creamos un modelo KNN, el objeto no se guarda en ninguna variable, simplemente se imprime la salida.

  • ✔ Selección del modelo.
nearest_neighbor()
K-Nearest Neighbor Model Specification (unknown)
  • ✔ Definición de algoritmo.
nearest_neighbor() %>% 
  set_engine("kknn")
K-Nearest Neighbor Model Specification (unknown)

Computational engine: kknn 
  • ✔ Especificar el tipo de predicción.
nearest_neighbor() %>% 
  set_engine("kknn") %>% 
  set_mode("regression")
K-Nearest Neighbor Model Specification (regression)

Computational engine: kknn 

👉🏻 Se aplica la misma secuencia para cualquier modelo, a modo de ejemplo, se muestra la creación de un modelo de regresión logística y un árbol de decisión:

Regresión logística

logistic_reg() %>% 
  set_engine("glm") %>% 
  set_mode("classification")
Logistic Regression Model Specification (classification)

Computational engine: glm 

Árbol de decisión

decision_tree() %>%
  set_engine("rpart") %>%
  set_mode("regression")
Decision Tree Model Specification (regression)

Computational engine: rpart 

🔎 Para consultar la lista de modelos disponibles, visitar Lista de Modelos , pueden verificarse los distintos métodos de llamados y los respectivos algoritmos o paquetes 📦 disponibles.

Pequeño atajo 🎁

📌 Tidymodels admite un gran número de modelos 🤖, con la opción adicional para algunos algoritmos de poder implementarlos a través de distintos paquetes 📦 usando la misma sintaxis. Además, con cada actualización se van agregando nuevos modelos.

👉🏻 Aunque podemos consultar los modelos mediante la Lista de modelos mencionada anteriormente, a través de la función parsnip_addin() de parsnip 📦 es posible invocar una interfaz interactiva para la selección de cualquier algoritmo, a continuación se muestra el procedimiento.

Hacer zoom a la pantalla 🔍

❗️Se pueden seleccionar múltiples algoritmos, automáticamente es generado el código necesario para crear los modelos solicitados, incluye además todos los hiperparámetros de los algoritmos.

Ajuste y predicción 🎯

🔘 Para fines didácticos 🤓, se crea el set de train y test a partir de los splits excluyendo las columnas "id" y "pliegues", simplemente para ajustar y realizar una predicción rápida.

train <- training(splits) %>% select(-c(id,pliegue))
test <- testing(splits) %>% select(-c(id,pliegue))

👉🏻 Se agrega un fit() al modelo luego del "%>%" , especificando la data para ajustar y las variables a predecir ("delitos ~ ." significa que la variable dependiente es delitos y las independientes "~." el resto de las variables.)

modelo <- decision_tree() %>%
  set_engine("rpart") %>%
  set_mode("regression")   
 
modelo %>% 
  fit(data = train, delitos ~ .) %>% 
  predict(test) %>% 
  head()
# A tibble: 6 x 1
  .pred
  <dbl>
1  5.59
2  1.57
3  1.57
4  1.57
5  5.59
6 22.0 

✅ Perfecto 👌🏻 este es el proceso básico de creación, ajuste y predicción. Ahora veamos el segundo componente, las recetas.

¿Qué es una receta? 🥘

🔘 Son un conjunto de pasos (Steps) que se ejecutan de manera secuencial para aplicar algún procedimiento de ingeniería de características. Estos se agrupan dentro de recipes 📦 (se carga automáticamente con tidymodels), puede usarse como preprocesamiento para el modelado o simplemente para la limpieza y transformación de datos.

👉 Utilizando el dataset de crímenes ocurridos data_crime_clime, primero se realiza un preprocesamiento tradicional sobre los datos utilizando dplyr 📦. A continuación, se implementarán las siguientes transformaciones.

Preprocesamiento tradicional:

  1. ✔ Se eliminan las columnas "id" y "pliegues".

  2. ✔ Se dividen por 3 todas las variables que tengan el texto "last_3" en sus nombres.

  3. ✔ Se discretiza la variable delitos.

  4. ✔ Se normalizan las variables numéricas.

data_procesada <- training(splits) %>%  

  select(-c(id,pliegue)) %>%   
  
  mutate(across(contains("last_3"), fn = ~ ./3)) %>%   
  
  mutate(across(contains("delitos"), ~ case_when(. <= 1 ~ "Bajo",
                                                 . <= 4  ~ "Medio",
                                                 . > 4 ~ "Alto"))) %>%
  mutate(across(where(is.numeric), scale))

🔎 Estos 4 pasos de preprocesamiento, ¿Responden a alguna lógica o requerimientos de modelos? No, es simplemente un ejemplo del abanico de transformaciones que se verán más adelante.

data_procesada %>% print(n_extra = 0)
# A tibble: 21,042 x 53
   delitos_last_ye… delitos_last_12 delitos_last_6 delitos_last_3
   <chr>            <chr>           <chr>          <chr>         
 1 Alto             Alto            Alto           Alto          
 2 Medio            Alto            Alto           Medio         
 3 Bajo             Alto            Alto           Alto          
 4 Bajo             Alto            Alto           Medio         
 5 Bajo             Alto            Alto           Medio         
 6 Bajo             Alto            Alto           Alto          
 7 Bajo             Alto            Alto           Medio         
 8 Medio            Alto            Alto           Medio         
 9 Bajo             Alto            Bajo           Bajo          
10 Medio            Alto            Alto           Alto          
# … with 21,032 more rows, and 49 more variables

¿Cómo podemos aplicar este procedimiento sobre nuevos datos? ⚙️

🔘 En un ambiente productivo, es rutinario aplicar pasos de preprocesamiento sobre nuevas observaciones, para este tipo de situaciones las recetas son una solución. A continuación, vamos a implementar los mismos pasos de preprocesamiento pero dentro de una receta.

Receta <- recipe(training(splits)) %>%   

  step_rm(c(id,pliegue), id = "remover") %>% 
  
  step_mutate_at(contains("last_3"),fn = ~ ./3,id = "dividir por 3") %>% 
  
  step_mutate(delitos = case_when(delitos <= 1 ~ "Bajo",
                                  delitos <= 4  ~ "Medio",
                                  delitos > 4 ~ "Alto"), 
                  id = "discretizar delitos") %>%   
                  
  step_normalize(all_numeric(), id = "normalización")
  
print(Receta)
Data Recipe

Inputs:

  55 variables (no declared roles)

Operations:

Delete terms c(id, pliegue)
Variable mutation for contains("last_3")
Variable mutation for delitos
Centering and scaling for all_numeric()

📌 El nombre de "recetas" (recipes) no es en vano, esta relacionado con la comida y la cocina 👨‍🍳 🍽️, los 4 pasos de procesamiento se pueden hacer a través de "sep_" functions, en la salida se observan todas las operaciones definidas en la receta.

👉🏻 Las recetas tienen asociados algunos métodos que permiten realizar distintas funciones, las dos principales son prep 🧂 (preparar) y bake 🍳 (hornear / cocinar). Vamos a preparar la receta (es decir, entrenarla).

Receta_fit <- Receta %>% prep(log = TRUE)
print(Receta_fit)
step_rm (remover): 
 removed (2): id, pliegue

step_mutate_at (dividir por 3): same number of columns

step_mutate (discretizar delitos): same number of columns

step_normalize (normalización): same number of columns

Data Recipe

Inputs:

  55 variables (no declared roles)

Training data contained 21042 data points and no missing data.

Operations:

Variables removed id, pliegue [trained]
Variable mutation for delitos_last_3, ... [trained]
Variable mutation for delitos [trained]
Centering and scaling for delitos_last_year, ... [trained]

🔎 Esta salida tiene dos diferencias con la anterior, primero, muestra el número de observaciones (quiere decir que recorrió todo el conjunto de datos).

Segundo, al final de cada paso muestra el estatus "[trained]" 💪🏻 y las columnas involucradas en cada operación, esto indica que la receta está lista para aplicarse sobre cualquier conjunto de datos ✅ . A través de la función tidy, se puede analizar de una forma más ordenada.

tidy(Receta_fit) %>% print(n_extra = 0)
# A tibble: 4 x 6
  number operation type      trained skip  id                 
   <int> <chr>     <chr>     <lgl>   <lgl> <chr>              
1      1 step      rm        TRUE    FALSE remover            
2      2 step      mutate_at TRUE    FALSE dividir por 3      
3      3 step      mutate    TRUE    FALSE discretizar delitos
4      4 step      normalize TRUE    FALSE normalización  

Podemos seleccionar un paso de preprocesamiento específico para ver los parámetros o procedimientos entrenados, seleccionamos el paso de normalización.

tidy(Receta_fit, id = "normalización") %>% print(n_extra = 0)
# A tibble: 104 x 4
   terms              statistic  value id           
   <chr>              <chr>      <dbl> <chr>        
 1 delitos_last_year  mean        3.56 normalización
 2 delitos_last_12    mean       43.6  normalización
 3 delitos_last_6     mean       22.0  normalización
 4 delitos_last_3     mean        3.66 normalización
 5 delitos_last_1     mean        3.65 normalización
 6 mm_last_3          mean      115.   normalización
 7 mm_last_1          mean      121.   normalización
 8 temperatura_last_3 mean       18.7  normalización
 9 temperatura_last_1 mean       19.0  normalización
10 dias_last_3        mean        8.00 normalización
# … with 94 more rows

📌 Recorriendo la tabla podemos observar la media y la desviación de la normalización para cada variable.

Aplicación de la receta sobre los datos ⏯️

Entrenada la receta, solo resta aplicar la función bake 🍳 (hornear / cocinar) sobre un conjunto de datos, por ejemplo aplicaremos la receta sobre los datos de entrenamiento.

train_procesado <- bake(Receta_fit, new_data = training(splits))
train_procesado %>% print(n_extra = 0)
# A tibble: 21,042 x 53
   delitos_last_ye… delitos_last_12 delitos_last_6 delitos_last_3
              <dbl>           <dbl>          <dbl>          <dbl>
 1            0.332           1.25           0.935          0.575
 2            0.102          -0.372         -0.409         -0.500
 3           -0.819          -0.252         -0.222         -0.285
 4           -0.589          -0.652         -0.596         -0.572
 5           -0.589          -0.552         -0.409         -0.500
 6           -0.589          -0.492         -0.521         -0.428
 7           -0.819          -0.692         -0.596         -0.500
 8           -0.128          -0.432         -0.409         -0.500
 9           -0.819          -0.692         -0.783         -0.715
10           -0.358          -0.312         -0.334         -0.357
# … with 21,032 more rows, and 49 more variables

👉🏻 Recorriendo la tabla se observa la aplicación de todos los pasos de procesamiento, se eliminaron las columnas "id" y "pliegues", fueron discretizadas y normalizadas las variables.
🔎 Ahora se aplicará la receta en un nuevo conjunto de datos, en este caso sobre los datos de test, pero puede implementarse sobre cualquier dataset.

test_procesado <- bake(Receta_fit, new_data = testing(splits))
test_procesado %>% print(n_extra = 0)

# A tibble: 5,257 x 53
   delitos_last_ye… delitos_last_12 delitos_last_6 delitos_last_3
              <dbl>           <dbl>          <dbl>          <dbl>
 1           -0.589          0.588          0.711         0.647  
 2           -0.819         -0.792         -0.783        -0.715  
 3           -0.128         -0.332         -0.297        -0.0698 
 4           -0.819         -0.572         -0.521        -0.500  
 5            0.792          1.03           1.12          0.934  
 6            6.32           4.41           4.22          3.44   
 7           -0.358         -0.112         -0.110         0.0736 
 8           -0.128          0.0876         0.0390        0.00192
 9           -0.589         -0.192          0.0390       -0.141  
10           -0.589         -0.712         -0.633        -0.715  
# … with 5,247 more rows, and 49 more variables

📌 Siguiendo un enfoque didáctico asociado a la preparación de un plato de comida 🍲, las recetas siguen el siguiente esquema:

  • ☑ Se buscan los ingredientes 🍍🍹🍗 (Organizó lo que quiero hacer y agregó los pasos de preprocesamiento)

  • ☑ Se escribe metodológicamente la receta, definiendo qué ingredientes agregar y qué hacer con cada uno 🗒️ (prep).

  • ☑ Hornear o cocinar la receta cuando lo pida el cliente 👨‍🍳🥗🍕 (Aplicar bake sobre nuevos datos)

💡 Creando un workflow 🔄

🔘 Entendiendo el proceso de creación de modelos y recetas, es natural querer utilizar ambos conceptos en el mismo flujo de trabajo, si bien puede hacerse manualmente, los workflows son una solución eficiente. A continuación, crearemos un modelo, una receta y lo agregaremos a un workflow.

workflow 🔄 = receta 🥘 + modelo ⚙️

Receta <- recipe(delitos ~ ., data = training(splits))%>%
step_rm(c(id,pliegue)) %>%
step_mutate_at(contains("last_3"), fn = ~ ./3)

Modelo <- boost_tree() %>% 
  set_mode("regression") %>%
  set_engine("xgboost")
  
wf <- workflow() %>% 
  add_model(Modelo) %>% 
  add_recipe(Receta)
  
wf
══ Workflow ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ──────────────────────────────────────────────────────
2 Recipe Steps

● step_rm()
● step_mutate_at()

── Model ─────────────────────────────────────────────────────────────
Boosted Tree Model Specification (regression)

Computational engine: xgboost 

🔎 Analicemos la salida, el primer elemento se delimita por la línea "── Preprocessor", indicando la receta dentro del workflow, el segundo elemento se observa luego del fragmento "── Model", se muestra el modelo, el algoritmo y tipo de predicción especificada.

Ajuste y predicciones con un workflow 👌🏻

🔘 La receta y el modelo son los dos componentes del workflow, al ajustar un workflow se entrena primero la receta y luego el modelo definido.

wf_fit <- wf %>% 
  fit(training(splits))

wf_fit
══ Workflow [trained] ════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ──────────────────────────────────────────────────────
2 Recipe Steps

● step_rm()
● step_mutate_at()

── Model ─────────────────────────────────────────────────────────────
##### xgb.Booster
raw: 60.2 Kb 
call:
  xgboost::xgb.train(params = list(eta = 0.3, max_depth = 6, gamma = 0, 
    colsample_bytree = 1, min_child_weight = 1, subsample = 1), 
    data = x$data, nrounds = 15, watchlist = x$watchlist, verbose = 0, 
    objective = "reg:squarederror", nthread = 1)
params (as set within xgb.train):
  eta = "0.3", max_depth = "6", gamma = "0", colsample_bytree = "1", min_child_weight = "1", subsample = "1", objective = "reg:squarederror", nthread = "1", validate_parameters = "TRUE"
xgb.attributes:
  niter
callbacks:
  cb.evaluation.log()
# of features: 52 
niter: 15
nfeatures : 52 
evaluation_log:
    iter training_rmse
       1      4.474720
       2      3.528147
---                   
      14      1.934659
      15      1.922183

👉🏻 Al inicio de la salida observamos el estatus "Workflow [trained]", esto indica que el objeto ya puede generar predicciones, además, ya se observan los parámetros de ajuste del modelo. A continuación, se realizarán predicciones sobre los datos de test.

wf_fit %>% 
  predict(testing(splits) %>% 
  head()
# A tibble: 6 x 1
   .pred
   <dbl>
1  6.22 
2  0.977
3  2.28 
4  1.28 
5  8.82 
6 20.3  

📌 Un aspecto fundamental en el funcionamiento de los workflows tiene que ver con los datos brutos, nótese que los splits y el train/test que se generan mediante las funciones testing() y training(), 💡corresponden a los datos brutos sin ningún tipo de preprocesamiento.

👉🏻 Por lo tanto, no hace falta manipular nuestra data original para entrenar el modelo. En el caso de generar predicciones, el workflow las generará con los datos brutos, esto hace que el flujo de trabajo dentro de tidymodels sea compacto, ordenado y fácil de auditar.

🚨 Pero...¿Qué ocurre con las funciones prep 🧂 (preparar) y bake 🍳 (hornear/cocinar) vistas anteriormente?

💡 Cuando se aplican las funciones fit() o predict() sobre un workflow, automáticamente se ejecutan prep y bake según sea necesario. Esta es la razón por la cual el proceso de ajuste y predicción se puede realizar de manera inmediata.

🔎 Cuando se ajustan hiperparámetros, se prueban diversos 🤖 modelos o se requieren múltiples recetas, los workflows 🔁 marcan la diferencia. Brindando mayor legibilidad en el código 🙌, flujos de trabajo más compactos y una menor cantidad de objetos almacenados en memoria 💾.

¿Cómo interactuar con las predicciones del workflow? 🔮

❗Para evaluar los resultados de un workflow, únicamente se necesita la función last_fit() (existen métodos más específicos) , esta toma 2 argumentos, el workflow ajustado y los splits.

resultados <- last_fit(wf_fit, splits)

¿Cómo puedo evaluar el rendimiento del modelo? 🎯

resultados %>% collect_metrics()
# A tibble: 2 x 4
  .metric .estimator .estimate .config             
  <chr>   <chr>          <dbl> <chr>               
1 rmse    standard       2.27  Preprocessor1_Model1
2 rsq     standard       0.750 Preprocessor1_Model1

¿Cómo puedo obtener las predicciones? 🔢

resultados %>% collect_predictions() %>% head()
# A tibble: 6 x 5
  id                .pred  .row delitos .config             
  <chr>             <dbl> <int>   <dbl> <chr>               
1 train/test split  6.22      1       9 Preprocessor1_Model1
2 train/test split  0.977     4       0 Preprocessor1_Model1
3 train/test split  2.28     10       2 Preprocessor1_Model1
4 train/test split  1.28     24       2 Preprocessor1_Model1
5 train/test split  8.82     44       8 Preprocessor1_Model1
6 train/test split 20.3      49      22 Preprocessor1_Model1

Bien... pero, ¿Si quisiera tener las predicciones junto con los datos originales, sin el preprocesamiento de la receta, además del error de predicción por observación? 🤔

resultados %>% 
  augment() %>% 
  relocate(delitos,.pred, .resid) %>% 
  print(n_extra = 0)
# A tibble: 5,257 x 57
   delitos  .pred .resid id    pliegue delitos_last_ye…
     <dbl>  <dbl>  <dbl> <chr>   <int>            <dbl>
 1       9  6.22   2.78  esqu…       1                1
 2       0  0.977 -0.977 esqu…       1                0
 3       2  2.28  -0.281 esqu…       1                3
 4       2  1.28   0.717 esqu…       1                0
 5       8  8.82  -0.817 esqu…       1                7
 6      22 20.3    1.66  esqu…       1               31
 7       3  3.44  -0.436 esqu…       1                2
 8       1  4.05  -3.05  esqu…       1                3
 9       2  3.17  -1.17  esqu…       1                1
10       0  1.09  -1.09  esqu…       1                1
# … with 5,247 more rows, and 51 more variables

👉🏻 La función relocate() no es necesaria, simplemente se usa para colocar las columnas "delitos", ".pred", ".resid" en las primeras posiciones del dataset.

Comentarios finales ✍🏻

🔘 Aunque los workflows son el tema principal de esta publicación, también se analizaron las recetas y la creación de modelos , siendo estos 3 elementos las piezas fundamentales de tidymodels. 🔎 En una segunda entrega, se mostrarán casos de uso con aplicaciones avanzadas de workflows.

Muchas gracias por leernos 👏🏻👏🏻👏🏻.

Siéntase libre de comentar y compartir 👌🏻.


Por Rafael Zambrano - Contacto: LinkedIn / Twitter / Blog-Post

Edición por Escuela de Datos Vivos.