¿Qué es y cómo se utiliza un Workflow? Tidymodels (Parte 1/2)

Análisis e implementación de workflows, recetas y modelos dentro del ecosistema Tidymodels.

Rafael Zambrano https://rafael-zambrano-blog-ds.netlify.app/
07-04-2021

⚠️ 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 🙌.

Resumen 📖

¿De que hablaremos en este post?

Un “Workflow” 🔄 es el componente fundamental que permite conectar el preprocesamiento de datos y el modelado predictivo 💡, haciendo uso del marco {tidymodels} de 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 la mayoría de posts 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?

📍 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 profundizar más en los temas de este post 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.

Librerías 📚

Datos 💾

🔘 Los datos corresponden al recuento de delitos ocurridos en 2023 esquinas de la ciudad de Buenos Aires, desde enero del 2017 hasta el 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} 📦

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 esquina_1         1                1              73             41
 2 esquina_10        1                5             106             47
 3 esquina_100       1                4              25             11
 4 esquina_10~       1                0               4              1
 5 esquina_10~       1                0              31             16
 6 esquina_10~       1                1              11              6
 7 esquina_10~       1                1              16             11
 8 esquina_10~       1                1              19              8
 9 esquina_10~       1                0               9              6
10 esquina_10~       1                3              27             14
# ... with 26,289 more rows

🔎 En muchas de las salidas, se observa 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 hiperparametros 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>
<21038/5261/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,038 x 55
   id          pliegue delitos_last_ye~ delitos_last_12 delitos_last_6
   <chr>         <int>            <dbl>           <dbl>          <dbl>
 1 esquina_10~       1                0               4              1
 2 esquina_10~       1                0              31             16
 3 esquina_10~       1                1              11              6
 4 esquina_10~       1                1              19              8
 5 esquina_10~       1                0               9              6
 6 esquina_101       1                2              28             13
 7 esquina_10~       1                0               9              8
 8 esquina_10~       1                2              12              6
 9 esquina_10~       1                2              16              7
10 esquina_102       1                1              34             18
# ... with 21,028 more rows
testing(splits) %>% print(n_extra = 0)
# A tibble: 5,261 x 55
   id          pliegue delitos_last_ye~ delitos_last_12 delitos_last_6
   <chr>         <int>            <dbl>           <dbl>          <dbl>
 1 esquina_10~       1                0               9              1
 2 esquina_10~       1                1              28             20
 3 esquina_10~       1                0              15              8
 4 esquina_103       1                2              32             16
 5 esquina_10~       1                4              38             23
 6 esquina_104       1                3              19              8
 7 esquina_10~       1                1              41             21
 8 esquina_10~       1                7              53             16
 9 esquina_10~       1                0              17             11
10 esquina_10~       1                2             111             64
# ... with 5,251 more rows

¿Que es un workflow 🔄 ? 🤔

🔎 Es un objeto perteneciente al ecosistema {tidymodels} de R, 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:

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

nearest_neighbor()
K-Nearest Neighbor Model Specification (unknown)

Computational engine: kknn 
nearest_neighbor() %>% set_engine("kknn")
K-Nearest Neighbor Model Specification (unknown)

Computational engine: kknn 
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 de á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.

❗️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 🎯

A modo didáctico 🤓, 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  1.60
2  1.60
3  1.60
4  1.60
5  3.42
6  1.60

✅ 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 del paquete 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_crime”, realizaremos primero un preprocesamiento tradicional sobre los datos mediante {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,038 x 53
   delitos_last_year delitos_last_12 delitos_last_6 delitos_last_3
   <chr>             <chr>           <chr>          <chr>         
 1 Bajo              Medio           Bajo           Bajo          
 2 Bajo              Alto            Alto           Alto          
 3 Bajo              Alto            Alto           Medio         
 4 Bajo              Alto            Alto           Alto          
 5 Bajo              Alto            Alto           Medio         
 6 Medio             Alto            Alto           Alto          
 7 Bajo              Alto            Alto           Alto          
 8 Medio             Alto            Alto           Medio         
 9 Medio             Alto            Alto           Medio         
10 Bajo              Alto            Alto           Alto          
# ... with 21,028 more rows

¿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")
Receta %>% prep() %>% bake(new_data = training(splits))
# A tibble: 21,038 x 53
   delitos_last_year delitos_last_12 delitos_last_6 delitos_last_3
               <dbl>           <dbl>          <dbl>          <dbl>
 1            -0.827          -0.796         -0.787         -0.719
 2            -0.827          -0.252         -0.222         -0.285
 3            -0.595          -0.655         -0.599         -0.574
 4            -0.595          -0.494         -0.523         -0.430
 5            -0.827          -0.695         -0.599         -0.502
 6            -0.362          -0.312         -0.335         -0.357
 7            -0.827          -0.695         -0.523         -0.357
 8            -0.362          -0.635         -0.599         -0.647
 9            -0.362          -0.554         -0.561         -0.574
10            -0.595          -0.192         -0.146         -0.285
# ... with 21,028 more rows, and 49 more variables:
#   delitos_last_1 <dbl>, delitos <fct>, mm_last_3 <dbl>,
#   mm_last_1 <dbl>, temperatura_last_3 <dbl>,
#   temperatura_last_1 <dbl>, dias_last_3 <dbl>, dias_last_1 <dbl>,
#   veloc_last_3 <dbl>, veloc_last_1 <dbl>, universidades <dbl>,
#   local_bailables <dbl>, wifi <dbl>, hotel_baja <dbl>,
#   hotel_alta <dbl>, cine <dbl>, teatros <dbl>, ...

📌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_* fucntions, 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()

Receta_fit
Recipe

Inputs:

  55 variables (no declared roles)

Training data contained 21038 data points and no missing data.

Operations:

Variables removed id, pliegue [trained]
Variable mutation for delitos_last_3, mm_last_3, tempe... [trained]
Variable mutation for ~case_when(delitos <= 1 ~ "Bajo"... [trained]
Centering and scaling for delitos_last_year, delitos_last_12, d... [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.55 normalización
 2 delitos_last_12    mean       43.5  normalización
 3 delitos_last_6     mean       21.9  normalización
 4 delitos_last_3     mean        3.64 normalización
 5 delitos_last_1     mean        3.64 normalización
 6 mm_last_3          mean      116.   normalización
 7 mm_last_1          mean      120.   normalización
 8 temperatura_last_3 mean       18.8  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 el método 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,038 x 53
   delitos_last_year delitos_last_12 delitos_last_6 delitos_last_3
               <dbl>           <dbl>          <dbl>          <dbl>
 1            -0.827          -0.796         -0.787         -0.719
 2            -0.827          -0.252         -0.222         -0.285
 3            -0.595          -0.655         -0.599         -0.574
 4            -0.595          -0.494         -0.523         -0.430
 5            -0.827          -0.695         -0.599         -0.502
 6            -0.362          -0.312         -0.335         -0.357
 7            -0.827          -0.695         -0.523         -0.357
 8            -0.362          -0.635         -0.599         -0.647
 9            -0.362          -0.554         -0.561         -0.574
10            -0.595          -0.192         -0.146         -0.285
# ... with 21,028 more rows

👉🏻Recorriendo la tabla se observa la aplicación de todos los paso de procesamiento, se eliminaron las columnas “id” y “pliegues”, se discretizan y normalizaron 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,261 x 53
   delitos_last_year delitos_last_12 delitos_last_6 delitos_last_3
               <dbl>           <dbl>          <dbl>          <dbl>
 1            -0.827         -0.695         -0.787        -0.719  
 2            -0.595         -0.312         -0.0707       -0.285  
 3            -0.827         -0.575         -0.523        -0.502  
 4            -0.362         -0.232         -0.222         0.0779 
 5             0.104         -0.111          0.0424        0.00541
 6            -0.129         -0.494         -0.523        -0.430  
 7            -0.595         -0.0504        -0.0330        0.150  
 8             0.802          0.191         -0.222        -0.285  
 9            -0.827         -0.534         -0.410        -0.285  
10            -0.362          1.36           1.59          1.38   
# ... with 5,251 more rows

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

💡 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: 85 Kb 
call:
  xgboost::xgb.train(params = list(eta = 0.3, max_depth = 6, gamma = 0, 
    colsample_bytree = 1, colsample_bynode = 1, min_child_weight = 1, 
    subsample = 1, objective = "reg:squarederror"), data = x$data, 
    nrounds = 15, watchlist = x$watchlist, verbose = 0, nthread = 1)
params (as set within xgb.train):
  eta = "0.3", max_depth = "6", gamma = "0", colsample_bytree = "1", colsample_bynode = "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.445449
       2      3.508220
---                   
      14      1.950900
      15      1.941021

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

wf_fit %>% 
  predict(testing(splits)) %>% 
  head()
# A tibble: 6 x 1
  .pred
  <dbl>
1 0.942
2 2.30 
3 1.31 
4 2.51 
5 3.12 
6 1.89 

📌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(), corresponde 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.771 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 0.942    12       0 Preprocessor1_Model1
2 train/test split 2.30     15       2 Preprocessor1_Model1
3 train/test split 1.31     24       2 Preprocessor1_Model1
4 train/test split 2.51     34       3 Preprocessor1_Model1
5 train/test split 3.12     39       6 Preprocessor1_Model1
6 train/test split 1.89     45       2 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,261 x 57
   delitos  .pred .resid id           pliegue delitos_last_year
     <dbl>  <dbl>  <dbl> <chr>          <int>             <dbl>
 1       0  0.942 -0.942 esquina_1009       1                 0
 2       2  2.30  -0.300 esquina_1011       1                 1
 3       2  1.31   0.686 esquina_1020       1                 0
 4       3  2.51   0.490 esquina_103        1                 2
 5       6  3.12   2.88  esquina_1034       1                 4
 6       2  1.89   0.111 esquina_104        1                 3
 7       6  3.83   2.17  esquina_1051       1                 1
 8       1  3.75  -2.75  esquina_1052       1                 7
 9       5  1.76   3.24  esquina_1058       1                 0
10      11 12.2   -1.15  esquina_1061       1                 2
# ... with 5,251 more rows

👉🏻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 👌🏻.

Contactos ✉

Rafael Zambrano, Linkedin, Twitter, Github, Blogpost.

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".

Citation

For attribution, please cite this work as

Zambrano (2021, July 4). Rafael Zambrano: ¿Qué es y cómo se utiliza un Workflow? Tidymodels (Parte 1/2). Retrieved from https://rafael-zambrano-blog-ds.netlify.app/posts/workflowsr_part1/

BibTeX citation

@misc{zambrano2021¿qué,
  author = {Zambrano, Rafael},
  title = {Rafael Zambrano: ¿Qué es y cómo se utiliza un Workflow? Tidymodels (Parte 1/2)},
  url = {https://rafael-zambrano-blog-ds.netlify.app/posts/workflowsr_part1/},
  year = {2021}
}