Code
# devtools::install_github("paezha/idealista18")
library(idealista18)
library(tidyverse)
library(caret)
library(rsample)
library(randomForest)
En el Machine Learning Supervisado, trabajamos con datos etiquetados. Esto significa que cada ejemplo de nuestro conjunto de datos tiene una salida o etiqueta asociada. Nuestro objetivo es construir un modelo que aprenda de estos ejemplos etiquetados para resolver problemas de clasificación o regresión con datos nuevos.
Basándonos en la metodología CRISP-DM para ciencia de datos, (ver libro Fundamentos de ciencia de datos en R) en ML supervisado podríamos definir las siguientes etapas:
IBM (2024)
Fernández-Avilés and Montero (2024)
Definición del Problema: Comprender y definir claramente qué problema se está tratando de resolver.
Recolección de Datos: Obtener un conjunto de datos relevante y suficientemente grande para entrenar el modelo.
Preprocesamiento de Datos: Preparar los datos para el entrenamiento, lo que incluye limpieza, normalización, codificación, y más.
División de Datos: Separar los datos en conjuntos de entrenamiento, validación y prueba.
Selección y Entrenamiento del Modelo: Elegir un algoritmo de ML adecuado y usar el conjunto de entrenamiento para ajustar el modelo.
Evaluación del Modelo: Usar el conjunto de validación (y eventualmente el de prueba) para evaluar el rendimiento del modelo.
Ajuste y Optimización: Refinar el modelo para mejorar su rendimiento, ajustando parámetros o incluso volviendo a la etapa de preprocesamiento.
Despliegue: Implementar el modelo en un entorno real para hacer predicciones sobre nuevos datos.
Para el primer punto, debemos tener cierto conocimiento del negocio y los datos. Es fundamental para poder llevar a cabo la recolección de datos y la fase de preprocesamiento, que con frecuencia, ocupan la mayor parte del tiempo de un proyecto de datos.
Esta etapa suele llevar el mayor % de carga de trabajo.
Conocer y documentar la descripción y las fuentes de datos
Integración con diferentes fuentes (unión y combinaciones)
Exploración de los datos (EDA)
Selección de variables y subconjuntos de registros
Limpieza: imputación de valores perdidos, auditorías de calidad de datos, búsqueda de errores o incoherencias, conversiones de tipos, etc.
Construir nuevas variables (feature engineering)
Formateo: codificación de variables, escalado, transformaciones
Automatización o planificación de procesos de recolección <–> despliegue
Selección de técnicas o algoritmos de ML supervisado
Generar un diseño de comprobación: fijar unos criterios para evaluar diferentes modelos con las mismas reglas
Generación de modelos: se entrenan varios modelos
Validación del modelo: En base a los modelos generados y el diseño de comprobación
Se comprueba el modelo desde el punto de vista del negocio. ¿Cumple los requisitos específicos? coste de computación, rendimiento, métricas apropiadas, … En esta fase es posible que con frecuencia, se deba volver a fases anteriores.
Planificar el despliegue del modelo en producción
Planificar control y mantenimiento (monitorizaciones, control de versiones, alertas)
Comunicación: Elaboración de un informe o medio para presentar los resultados
Revisión final
Dataset ejemplo: Idealista
# devtools::install_github("paezha/idealista18")
library(idealista18)
library(tidyverse)
library(caret)
library(rsample)
library(randomForest)
# Lectura
<- as.data.frame(idealista18::Madrid_Sale) Madrid_Sale
Uso de joins, union
Dependerá de los datos disponibles y sus orígenes (CRMs, opendata, ERPs, DW)
Ejemplo Fundamentos de ciencia de datos en R)
Formatos incorrectos (tipos, fechas, enteros/caracteres)
Errores tipográficos. Ej. variables categóricas: mayúsculas, espacios,
Incoherencias en criterios de nombres y o categorías, etc.
Evitar variables redundantes
Evitar variables predictoras irrelevantes
Moderar la cantidad de variables
Objetivos:
Acelerar el algoritmo reduciendo el grado de complejidad del algoritmo
Evitar el overfitting
Incrementar la precisión
Filtro: Técnicas estadísticas para elegir un subconjunto, el mejor puntuado
Envoltura (wrapper): Mejores prestaciones en las métricas finales
Intrínsecos al algoritmo (ej. Random Forest): El propio algoritmo las selecciona
Variables que aportan poco valor, dan problemas al dividir el conjunto.
<- Madrid_Sale %>%
Madrid_Sale_num select_if(is.numeric)
<- nearZeroVar(Madrid_Sale_num, saveMetrics = T)
varianza # Con el argumento saveMetrics, se guardan los valores que se han utilizado para los cálculos.
# Se muestran los resultados
|>
varianza filter(nzv == TRUE)
freqRatio percentUnique zeroVar nzv
PARKINGSPACEPRICE 400.96970 0.153984074 FALSE TRUE
ISDUPLEX 36.89568 0.002109371 FALSE TRUE
ISSTUDIO 35.29977 0.002109371 FALSE TRUE
ISINTOPFLOOR 42.17623 0.002109371 FALSE TRUE
BUILTTYPEID_1 31.89903 0.002109371 FALSE TRUE
<- varianza |>
variables_descartadas filter(nzv == TRUE) |> rownames()
variables_descartadas
[1] "PARKINGSPACEPRICE" "ISDUPLEX" "ISSTUDIO"
[4] "ISINTOPFLOOR" "BUILTTYPEID_1"
<- Madrid_Sale_num |>
Madrid_Sale_num select(-(variables_descartadas))
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(variables_descartadas)
# Now:
data %>% select(all_of(variables_descartadas))
See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
La existencia de correlaciones elevadas tiene consecuencias negativas sobre la fiabilidad de las predicciones (o de la clasificación realizada). En el caso extremo el modelo tendrá problemas de colinealidad o multicolinealidad.
<- cor(Madrid_Sale_num[, 1:20]) # se cogen las 20 primeras a modo de ejemplo, porque no tienen NAs
madrid_cor <- findCorrelation(madrid_cor, cutoff = .9)
alta_corr colnames(madrid_cor[,(madrid_cor[alta_corr,] > 0.9)])
[1] "HASPARKINGSPACE" "ISPARKINGSPACEINCLUDEDINPRICE"
cor(Madrid_Sale_num$HASPARKINGSPACE, Madrid_Sale_num$ISPARKINGSPACEINCLUDEDINPRICE)
[1] 1
<- drop_na(Madrid_Sale_num) # Es necesario eliminar los NA.
Madrid_Sale_num_na <- findLinearCombos(Madrid_Sale_num_na)
combos combos
$linearCombos
$linearCombos[[1]]
[1] 12 11
$remove
[1] 12
#Madrid_Sale_num_na[, -combos$remove]
<- Madrid_Sale_num[, -alta_corr] Madrid_Sale_num
<- drop_na(Madrid_Sale_num) # Es necesario eliminar los NA.
Madrid_Sale_num_na <- findLinearCombos(Madrid_Sale_num_na)
combos combos
$linearCombos
list()
$remove
NULL
# Se toma una muestra con el paquete rsample
set.seed(7)
<- sample(1:nrow(Madrid_Sale_num), size = 5000, replace = FALSE)
Madrid_Sale_num_sample <- Madrid_Sale_num[Madrid_Sale_num_sample, ]
Madrid_Sale_num_sample # Se realiza binning con cuatro bins
<- Madrid_Sale_num_sample |>
Madrid_Sale_num_sample_bin mutate(price_bin = cut(PRICE, breaks = c(0, 250000, 500000, 750000, 10000000), labels = c("primerQ", "segundoQ", "tercerQ", "c"), include.lowest = TRUE)) |>
select(price_bin, CONSTRUCTEDAREA, ROOMNUMBER, BATHNUMBER, HASTERRACE, HASLIFT)
# Se eliminan los registros con valores missing
<- drop_na(Madrid_Sale_num_sample_bin)
Madrid_Sale_sample_na
# Usar random forest para la selección de variables
<- randomForest(price_bin ~ ., data = Madrid_Sale_num_sample_bin)
rf_modelo
# Listar las variables más importantes
varImp(rf_modelo)
Overall
CONSTRUCTEDAREA 830.69055
ROOMNUMBER 162.13449
BATHNUMBER 306.86141
HASTERRACE 41.42717
HASLIFT 192.62940
Depende del tipo de modelo (si lo requiere) y la distribución de los datos:
Consiste en crear nuevas variables con la información disponible para que los modelos obtengan mejores resultados:
En la unidad anterior hemos entrenado y probado los algoritmos con el mismo dataset. Esto provoca unos resultados con buenas métricas pero que no se ajustarían correctamente a datos que no se hayan incluído en el entrenamiento. Buscamos un algoritmo que no solo ajuste bien los datos pasados sino, lo que es más importante, que proporcione predicciones (futuras) acertadas (y precisas). Para ello, inicialmente, se dividen los datos en dos subconjuntos:
Entrenamiento (training): para desarrollar conjuntos de funciones, entrenar algoritmos, ajustar hiperparámetros, comparar modelos y realizar todas las demás actividades necesarias para seleccionar un modelo final.
Prueba (test): para validar la precisión del modelo seleccionado en la fase de entrenamiento. A la hora de dividir el conjunto de datos en los dos subconjuntos anteriores, hay que tomar dos decisiones:
¿Qué porcentaje de los datos (casos, observaciones) se incluye en cada subconjunto?
¿Cómo se seleccionan los casos u observaciones que van a cada subconjunto?
Cuanto más grande sea el subconjunto de entrenamiento, mejor será el predictor (o clasificador), aunque las mejoras serán cada vez más pequeñas. Por el contrario, cuanto más grande sea el subconjunto de prueba o test, más precisa será la estimación del error de predicción. Lo ideal sería tener un conjunto de datos muy grande y que ambos subconjuntos fueran grandes. De esta manera, los errores de predicción serían pequeños y tendrían poca variabilidad.
Este no es el caso en la práctica, y el dilema es elegir un buen predictor (o clasificador) o una buena estimación del error de predicción. En la práctica, lo más frecuente es incluir el 70% de los datos en el subconjunto de entrenamiento y el 30% en el de test, aunque los repartos 80% - 20% y 60% - 40% también son muy populares.
La selección de los datos se realiza mediante métodos de muestreo, siendo los más utilizados el muestreo aleatorio simple (m.a.s.) y el muestreo aleatorio estratificado. Si el conjunto de datos es pequeño, el m.a.s. podría desequilibrar los predictores y la variable objetivo.
set.seed(123)
<- createDataPartition(Madrid_Sale_num$PRICE, p = 0.7, list = FALSE)
index <- Madrid_Sale_num[index, ]
train <- Madrid_Sale_num[-index, ] test
dim(Madrid_Sale_num) # 94815
[1] 94815 34
dim(train) # 66373
[1] 66373 34
dim(test) # 28442
[1] 28442 34
set.seed(123) # para permitir reproducirlo
table(Madrid_Sale_num_sample_bin$price_bin) |> prop.table()
primerQ segundoQ tercerQ c
0.4716 0.2992 0.1072 0.1220
# 0.4776 0.3062 0.1024 0.1116
<- initial_split(Madrid_Sale_num_sample_bin, prop = 0.7, strata = "price_bin")
split_estrat <- training(split_estrat)
train_estrat <- testing(split_estrat) test_estrat
table(train_estrat$price_bin) |> prop.table()
primerQ segundoQ tercerQ c
0.4715633 0.2992284 0.1071735 0.1220349
# 0.4777015 0.2913093 0.1132075 0.1177816
table(test_estrat$price_bin) |> prop.table()
primerQ segundoQ tercerQ c
0.4716855 0.2991339 0.1072618 0.1219187
# 0.4799886 0.3061750 0.1023442 0.1114923
A menudo, los datos utilizados en determinadas áreas tienen menos del 1% de eventos raros, pero precisamente su rareza es lo que los hace “interesantes”: por ejemplo, estafas en operaciones bancarias o usuarios que hacen clic en anuncios. En otros términos, una de las clases de la variable objetivo es dominante, pero la clase minoritaria es la que presenta interés. Sin embargo, la mayoría de los algoritmos no funcionan bien con variables cuyas clases están desequilibradas. Hay varias técnicas para manejar este problema:
Downsampling: equilibra el conjunto de datos reduciendo el tamaño de las clases abundantes para que coincida con el de la clase menos prevalente. Este método es de utilidad cuando el tamaño del conjunto de datos es suficientemente grande para ser aplicado.
Upsampling: equilibra el conjunto de datos aumentando el tamaño de las clases más raras. En lugar de deshacerse de datos de las clases abundantes, se generan nuevos datos para las clases raras mediante repetición o bootstrapping. Este procedimiento es de utilidad cuando no hay suficientes datos en la clase (o clases) rara.
Creación de datos sintéticos: esta técnica consiste en equilibrar el conjunto de entrenamiento generando nuevos registros sintéticos, esto es, inventados, de la clase minoritaria. Existen diversos algoritmos que realizan esta tarea, siendo uno de los más conocidos la técnica de SMOTE
Otras técnicas: como que el algoritmo implemente mecanismos para dar mayor peso a los casos de la clase minoritaria, etc.
# Se especifica que el modelo se entrene con downsampling
<- trainControl(
ctrl method = "repeatedcv", repeats = 5,
classProbs = TRUE,
sampling = "down"
)<- train(price_bin ~ .,
Madrid_Sale_num_sample_bin_downsample data = Madrid_Sale_num_sample_bin,
method = "gbm",
preProcess = c("range"),
verbose = FALSE,
trControl = ctrl
)
<- read.csv("https://raw.githubusercontent.com/jesusturpin/curintel2324/main/data/bank.csv") bank
glimpse(bank)
Rows: 11,162
Columns: 17
$ age <int> 59, 56, 41, 55, 54, 42, 56, 60, 37, 28, 38, 30, 29, 46, 31, …
$ job <chr> "admin.", "admin.", "technician", "services", "admin.", "man…
$ marital <chr> "married", "married", "married", "married", "married", "sing…
$ education <chr> "secondary", "secondary", "secondary", "secondary", "tertiar…
$ default <chr> "no", "no", "no", "no", "no", "no", "no", "no", "no", "no", …
$ balance <int> 2343, 45, 1270, 2476, 184, 0, 830, 545, 1, 5090, 100, 309, 1…
$ housing <chr> "yes", "no", "yes", "yes", "no", "yes", "yes", "yes", "yes",…
$ loan <chr> "no", "no", "no", "no", "no", "yes", "yes", "no", "no", "no"…
$ contact <chr> "unknown", "unknown", "unknown", "unknown", "unknown", "unkn…
$ day <int> 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, …
$ month <chr> "may", "may", "may", "may", "may", "may", "may", "may", "may…
$ duration <int> 1042, 1467, 1389, 579, 673, 562, 1201, 1030, 608, 1297, 786,…
$ campaign <int> 1, 1, 1, 1, 2, 2, 1, 1, 1, 3, 1, 2, 4, 2, 2, 1, 3, 1, 2, 1, …
$ pdays <int> -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, …
$ previous <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ poutcome <chr> "unknown", "unknown", "unknown", "unknown", "unknown", "unkn…
$ deposit <chr> "yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes…
# Convertimos a factor las variables categóricas
.1 <- bank %>%
bankmutate_if(is.character, as.factor)
.1 %>%
bankselect_if(is.factor) %>%
summary()
job marital education default housing
management :2566 divorced:1293 primary :1500 no :10994 no :5881
blue-collar:1944 married :6351 secondary:5476 yes: 168 yes:5281
technician :1823 single :3518 tertiary :3689
admin. :1334 unknown : 497
services : 923
retired : 778
(Other) :1794
loan contact month poutcome deposit
no :9702 cellular :8042 may :2824 failure:1228 no :5873
yes:1460 telephone: 774 aug :1519 other : 537 yes:5289
unknown :2346 jul :1514 success:1071
jun :1222 unknown:8326
nov : 943
apr : 923
(Other):2217
Dividimos el conjunto en bank.training y bank.testing con la función createDataPartition de caret
(Validación simple)
# Proporción de clases en la variable objetivo
prop.table(table(bank.1$deposit))
no yes
0.5261602 0.4738398
set.seed(123)
# Generamos los índices que irán a entrenamiento (muestreo estratificado)
<- createDataPartition(bank.1$deposit, p = .8, list = FALSE, times = 1)
trainIndex
# Creamos los dos subconjuntos entrenamiento y test
<- bank.1[trainIndex, ]
bank.training <- bank.1[-trainIndex, ] bank.testing
A partir de aquí, entrenaremos el modelo con el conjunto bank.training
library(ranger)
Attaching package: 'ranger'
The following object is masked from 'package:randomForest':
importance
# Entrenar el modelo RandomForest con ranger
<- ranger(
ranger_model formula = deposit ~ ., # La fórmula indica que 'deposit' es la variable a predecir y el '.' incluye todas las otras variables como predictores
data = bank.training, # El conjunto de datos de entrenamiento
num.trees = 500, # Número de árboles a crecer en el bosque, ajustar este número según sea necesario
importance = 'impurity', # Tipo de importancia de la variable a calcular, puede ser 'none', 'impurity', o 'permutation'
mtry = floor(sqrt(ncol(bank.training) - 1)), # Número de variables a considerar en cada división, el predeterminado es la raíz cuadrada del número de variables para clasificación
min.node.size = 5, # Tamaño mínimo de nodos, ajusta según sea necesario para controlar el sobreajuste
probability = TRUE # Las predicciones son probabilidad y no la clase directamente
)
# Mostrar un resumen del modelo
print(ranger_model)
Ranger result
Call:
ranger(formula = deposit ~ ., data = bank.training, num.trees = 500, importance = "impurity", mtry = floor(sqrt(ncol(bank.training) - 1)), min.node.size = 5, probability = TRUE)
Type: Probability estimation
Number of trees: 500
Sample size: 8931
Number of independent variables: 16
Mtry: 4
Target node size: 5
Variable importance mode: impurity
Splitrule: gini
OOB prediction error (Brier s.): 0.1105526
Se evalúa con el conjunto de test
<- predict(ranger_model, data = bank.testing) # objeto predicciones testing
predicciones_obj $probs <- predicciones_obj$predictions # vectores probabilidades (yes, no)
bank.testing$probs <- bank.testing$probs[, 2] # quitamos columna "no"
bank.testing$fitted_class <- round(bank.testing$probs) # cutoff = 0.5 bank.testing
library(pROC)
Type 'citation("pROC")' for a citation.
Attaching package: 'pROC'
The following objects are masked from 'package:stats':
cov, smooth, var
<- roc(bank.testing$deposit, bank.testing$probs) ROC
Setting levels: control = no, case = yes
Setting direction: controls < cases
plot(ROC, col = "blue")
text(0.5, 0.2, paste("AUC =", round(auc(ROC), 3)))
library(yardstick)
Attaching package: 'yardstick'
The following objects are masked from 'package:caret':
precision, recall, sensitivity, specificity
The following object is masked from 'package:readr':
spec
<- table(1*(bank.testing$deposit == "yes"), bank.testing$fitted_class)
conf_matrix conf_matrix
0 1
0 947 227
1 128 929
<- conf_mat(conf_matrix)
confusion summary(confusion, event_level = "second") %>%
filter(.metric %in% c("accuracy", "sens", "spec"))
# A tibble: 3 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 accuracy binary 0.841
2 sens binary 0.804
3 spec binary 0.881
Rodrigo (2020)
Es el ejemplo previo que hemos visto. La validación simple divide las observaciones en dos grupos aleatorios para entrenar y evaluar el modelo, siendo un método fácil pero con limitaciones: alta variabilidad en la estimación del error (problema de varianza) y reducción de la capacidad del modelo por no usar todas las observaciones en el entrenamiento, llevando a una sobrestimación del error (problema de bias). La división suele ser 80-20 u otras combinaciones 70-30, 60-40.
El método LOOCV es iterativo, usa todas las observaciones menos una para el entrenamiento y la restante para validación, repitiendo el proceso hasta que cada observación ha sido usada para validación una vez, reduciendo la variabilidad al emplear todos los datos para entrenamiento y validación, garantizando reproducibilidad.
Aunque reduce la variabilidad, su desventaja radica en el alto coste computacional, requiriendo reajustes múltiples excepto en ciertos casos como la regresión por mínimos cuadrados. A pesar de su aplicabilidad general y su popularidad, existe el riesgo de overfitting al usar todas las observaciones para entrenamiento, por lo que se sugiere considerar alternativas como K-Fold Cross-Validation.
El método K-Fold Cross-Validation, un proceso iterativo que divide aleatoriamente los datos en k grupos de tamaño similar, utilizando k-1 para entrenamiento y uno para validación, repitiendo este proceso k veces para generar un promedio de k estimaciones de error como resultado final.
Ofrece ventajas sobre LOOCV en términos computacionales y en el equilibrio entre bias y varianza. Al elegir un k entre 5 y 10 (dependerá de las dimensiones del conjunto), reduce la necesidad de iteraciones en comparación con LOOCV, que iguala k al número total de observaciones.
Se minimiza la varianza al evitar el alto solapamiento en los datos de entrenamiento presentes en LOOCV, y manteniendo un bias controlado al utilizar un número significativo de observaciones para el entrenamiento. Esto conduce a una estimación de error más precisa y reduce el riesgo de overfitting, haciendo que K-Fold Cross-Validation con k en el rango de 5 a 10 sea una elección equilibrada para la validación de modelos.
Ejemplo: Problema de regresión:
<- read.csv("https://raw.githubusercontent.com/stedy/Machine-Learning-with-R-datasets/master/insurance.csv") insurance
glimpse(insurance)
Rows: 1,338
Columns: 7
$ age <int> 19, 18, 28, 33, 32, 31, 46, 37, 37, 60, 25, 62, 23, 56, 27, 1…
$ sex <chr> "female", "male", "male", "male", "male", "female", "female",…
$ bmi <dbl> 27.900, 33.770, 33.000, 22.705, 28.880, 25.740, 33.440, 27.74…
$ children <int> 0, 1, 3, 0, 0, 0, 1, 3, 2, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0…
$ smoker <chr> "yes", "no", "no", "no", "no", "no", "no", "no", "no", "no", …
$ region <chr> "southwest", "southeast", "southeast", "northwest", "northwes…
$ charges <dbl> 16884.924, 1725.552, 4449.462, 21984.471, 3866.855, 3756.622,…
<- createFolds(insurance$charges, k = 3) # crea una lista de 3 elementos
folds # vectores (índices)
<- insurance[-folds$Fold3,]
ins.train1 <- insurance[folds$Fold3,]
ins.test1
<- insurance[-folds$Fold2,]
ins.train2 <- insurance[folds$Fold2,]
ins.test2
<- insurance[-folds$Fold1,]
ins.train3 <- insurance[folds$Fold1, ] ins.test3
<- lm(formula = charges ~., data = ins.train1)
model1 <- lm(formula = charges ~., data = ins.train2)
model2 <- lm(formula = charges ~., data = ins.train3) model3
<- predict(model1, newdata = ins.test1)
predictions1 <- ins.test1$charges - predictions1
residuos1 #RMSE
<- sqrt(mean(residuos1^2))
rmse1
rmse1
[1] 6091.668
<- predict(model2, newdata = ins.test2)
predictions2 <- ins.test2$charges - predictions2
residuos2 #RMSE
<- sqrt(mean(residuos2^2))
rmse2
rmse2
[1] 5996.544
<- predict(model3, newdata = ins.test3)
predictions3
<- ins.test3$charges - predictions3
residuos3
#RMSE
<- sqrt(mean(residuos3^2))
rmse3
# Muestra el RMSE
rmse3
[1] 6186.614
<- mean(c(rmse1, rmse2, rmse3))
rmse_cv rmse_cv
[1] 6091.609
Es exactamente igual al método k-Fold-Cross-Validation pero repitiendo el proceso completo n veces. Por ejemplo, 10-Fold-Cross-Validation con 5 repeticiones implica a un total de 50 iteraciones ajuste-validación, pero no equivale a un 50-Fold-Cross-Validation.
Una muestra bootstrap es una muestra obtenida a partir de la muestra original por muestreo aleatorio con reposición, y del mismo tamaño que la muestra original. Muestreo aleatorio con reposición (resampling with replacement) significa que, después de que una observación sea extraída, se vuelve a poner a disposición para las siguientes extracciones. Como resultado de este tipo de muestreo, algunas observaciones aparecerán múltiples veces en la muestra bootstrap y otras ninguna. Las observaciones no seleccionadas reciben el nombre de out-of-bag (OOB). Por cada iteración de bootstrapping se genera una nueva muestra bootstrap, se ajusta el modelo con ella y se evalúa con las observaciones out-of-bag.
Obtener una nueva muestra del mismo tamaño que la muestra original mediante muestro aleatorio con reposición.
Ajustar el modelo empleando la nueva muestra generada en el paso 1.
Calcular el error del modelo empleando aquellas observaciones de la muestra original que no se han incluido en la nueva muestra. A este error se le conoce como error de validación.
Repetir el proceso n veces y calcular la media de los n errores de validación.
Finalmente, y tras las n repeticiones, se ajusta el modelo final empleando todas las observaciones de entrenamiento originales.