En este ejercicio usamos el Adult Income Dataset (Census Income) para predecir si una persona gana más o menos de 50,000 USD anuales. Aplicamos dos modelos de clasificación supervisada:
rpart.Al final comparamos ambos modelos y discutimos sus implicaciones desde una perspectiva de política pública.
library(tidyverse)
library(caret)
library(e1071)
library(rpart)
library(rpart.plot)
library(knitr)
# Carga del dataset
datos <- read.csv("ejercicio_final.csv", stringsAsFactors = TRUE)
# Quitar columna de índice
if (names(datos)[1] %in% c("X", "")) {
datos <- datos[, -1]
}## Dimensiones: 48598 filas x 15 columnas
## 'data.frame': 48598 obs. of 15 variables:
## $ age : int 25 38 28 44 18 34 29 63 24 55 ...
## $ workclass : Factor w/ 6 levels "?","Gov","Never-worked",..: 4 4 2 4 1 4 1 5 4 4 ...
## $ demogweight : int 226802 89814 336951 160323 103497 198693 227026 104626 369667 104996 ...
## $ education : Factor w/ 16 levels "10th","11th",..: 2 12 8 16 16 1 12 15 16 6 ...
## $ education_num : int 7 9 12 10 10 6 9 15 10 4 ...
## $ marital_status: Factor w/ 5 levels "Divorced","Married",..: 3 2 2 2 3 3 3 2 3 2 ...
## $ occupation : Factor w/ 15 levels "?","Adm-clerical",..: 8 6 12 8 1 9 1 11 9 4 ...
## $ relationship : Factor w/ 6 levels "Husband","Not-in-family",..: 4 1 1 1 4 2 5 1 5 1 ...
## $ race : Factor w/ 5 levels "Amer-Indian-Eskimo",..: 3 5 5 3 5 5 3 5 5 5 ...
## $ gender : Factor w/ 2 levels "Female","Male": 2 2 2 2 1 2 2 2 1 2 ...
## $ capital_gain : int 0 0 0 7688 0 0 0 3103 0 0 ...
## $ capital_loss : int 0 0 0 0 0 0 0 0 0 0 ...
## $ hours_per_week: int 40 50 40 40 30 30 40 32 40 10 ...
## $ native_country: Factor w/ 41 levels "?","Cambodia",..: 39 39 39 39 39 39 39 39 39 39 ...
## $ income : Factor w/ 2 levels "<=50K",">50K": 1 1 2 2 1 1 1 2 1 1 ...
## age workclass demogweight education
## Min. :17.0 ? : 2794 Min. : 12285 HS-grad :15750
## 1st Qu.:28.0 Gov : 6536 1st Qu.: 117551 Some-college:10860
## Median :37.0 Never-worked: 10 Median : 178215 Bachelors : 7962
## Mean :38.6 Private :33780 Mean : 189685 Masters : 2627
## 3rd Qu.:48.0 Self-emp : 5457 3rd Qu.: 237713 Assoc-voc : 2058
## Max. :90.0 Without-pay : 21 Max. :1490400 11th : 1812
## (Other) : 7529
## education_num marital_status occupation
## Min. : 1.00 Divorced : 6613 Craft-repair : 6096
## 1st Qu.: 9.00 Married :22847 Prof-specialty : 6071
## Median :10.00 Never-married:16096 Exec-managerial: 6019
## Mean :10.06 Separated : 1526 Adm-clerical : 5603
## 3rd Qu.:12.00 Widowed : 1516 Sales : 5470
## Max. :16.00 Other-service : 4920
## (Other) :14419
## relationship race gender
## Husband :19537 Amer-Indian-Eskimo: 470 Female:16156
## Not-in-family :12546 Asian-Pac-Islander: 1504 Male :32442
## Other-relative: 1506 Black : 4675
## Own-child : 7577 Other : 403
## Unmarried : 5118 White :41546
## Wife : 2314
##
## capital_gain capital_loss hours_per_week native_country
## Min. : 0.0 Min. : 0.00 Min. : 1.00 United-States:43613
## 1st Qu.: 0.0 1st Qu.: 0.00 1st Qu.:40.00 Mexico : 949
## Median : 0.0 Median : 0.00 Median :40.00 ? : 847
## Mean : 582.4 Mean : 87.94 Mean :40.37 Philippines : 292
## 3rd Qu.: 0.0 3rd Qu.: 0.00 3rd Qu.:45.00 Germany : 206
## Max. :41310.0 Max. :4356.00 Max. :99.00 Puerto-Rico : 184
## (Other) : 2507
## income
## <=50K:37155
## >50K :11443
##
##
##
##
##
El dataset tiene 48598 observaciones y 15 variables. Las variables
numéricas son age, demogweight,
education_num, capital_gain,
capital_loss y hours_per_week. Las categóricas
incluyen workclass, education,
marital_status, occupation,
relationship, race, gender,
native_country e income (variable
objetivo).
## NAs reales por columna:
## named numeric(0)
# Missings codificados como " ?"
for (v in names(datos)) {
if (is.factor(datos[[v]])) {
n_q <- sum(datos[[v]] %in% c(" ?", "?"))
if (n_q > 0) {
cat(sprintf(" %s: %d observaciones con '?' (%.1f%%)\n",
v, n_q, 100 * n_q / nrow(datos)))
}
}
}## workclass: 2794 observaciones con '?' (5.7%)
## occupation: 2804 observaciones con '?' (5.8%)
## native_country: 847 observaciones con '?' (1.7%)
# Las categorías " ?" en workclass, occupation y native_country se recodifican
# como "Unknown". Representan menos del 6% y pueden ser informativas
# (empleo informal, no declarado), así que se conservan en lugar de eliminar.
datos <- datos %>%
mutate(across(where(is.factor),
~ fct_recode(., "Unknown" = " ?", "Unknown" = "?")))
cat("Filas después de imputación:", nrow(datos), "\n")## Filas después de imputación: 48598
tabla_income <- table(datos$income)
prop_income <- prop.table(tabla_income) * 100
cat("Distribución de income:\n")## Distribución de income:
##
## <=50K >50K
## 37155 11443
##
## Porcentajes:
##
## <=50K >50K
## 76.45 23.55
ggplot(datos, aes(x = income, fill = income)) +
geom_bar(width = 0.5, show.legend = FALSE) +
geom_text(stat = "count",
aes(label = paste0(round(after_stat(count) / nrow(datos) * 100, 1), "%")),
vjust = -0.5, size = 4.5) +
scale_fill_manual(values = c("#2C7BB6", "#D7191C")) +
labs(title = "Distribución de la variable objetivo: income",
x = "Categoría de ingreso", y = "Frecuencia") +
theme_minimal(base_size = 13)Aproximadamente el 23.5% de las personas gana más de 50,000 USD. Este desbalance puede hacer que el modelo prediga casi siempre la clase mayoritaria (<=50K), lo que da una accuracy alta pero un recall bajo para el grupo de interés (>50K). Por eso es importante evaluar con Precision, Recall y F1, no solo con accuracy.
datos %>%
group_by(education, income) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(education) %>%
mutate(pct = n / sum(n) * 100) %>%
filter(income == ">50K") %>%
arrange(desc(pct)) %>%
ggplot(aes(x = reorder(education, pct), y = pct, fill = pct)) +
geom_col(show.legend = FALSE) +
coord_flip() +
scale_fill_gradient(low = "#fee8c8", high = "#b30000") +
labs(title = "% con ingreso >50K por nivel educativo",
x = "Nivel educativo", y = "% con ingreso >50K") +
theme_minimal(base_size = 12)Hay una relación clara y positiva entre nivel educativo e ingreso. Los posgrados (Doctorate, Prof-school, Masters) concentran las tasas más altas de ingreso >50K, lo cual es consistente con la teoría del capital humano.
datos %>%
group_by(occupation, income) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(occupation) %>%
mutate(pct = n / sum(n) * 100) %>%
filter(income == ">50K") %>%
arrange(desc(pct)) %>%
ggplot(aes(x = reorder(occupation, pct), y = pct, fill = pct)) +
geom_col(show.legend = FALSE) +
coord_flip() +
scale_fill_gradient(low = "#deebf7", high = "#08519c") +
labs(title = "% con ingreso >50K por ocupación",
x = "Ocupación", y = "% con ingreso >50K") +
theme_minimal(base_size = 12)Las ocupaciones ejecutivas y de especialidad técnica (Exec-managerial, Prof-specialty) presentan las tasas más altas, mientras que las tareas agrícolas y de servicios tienen las más bajas.
datos %>%
group_by(gender, income) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(gender) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = gender, y = pct, fill = income)) +
geom_col(position = "fill", width = 0.5) +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(values = c("#2C7BB6", "#D7191C")) +
labs(title = "Distribución de ingreso por sexo",
x = "Sexo", y = "Proporción", fill = "Income") +
theme_minimal(base_size = 13)La brecha de género es significativa. Los hombres tienen una tasa de ingreso >50K aproximadamente tres veces mayor que las mujeres.
# Variables numéricas
vars_num <- c("age", "education_num", "capital_gain",
"capital_loss", "hours_per_week", "demogweight")
# Variables categóricas (sin la variable objetivo)
vars_cat <- c("workclass", "marital_status", "occupation",
"relationship", "race", "gender", "native_country")
# One-hot encoding para variables categóricas
dummy_formula <- as.formula(paste("~", paste(vars_cat, collapse = " + ")))
dummies <- model.matrix(dummy_formula, data = datos)[, -1]
# Estandarizar variables numéricas
datos_num_scaled <- scale(datos[, vars_num])
# Dataset final para los modelos
datos_modelo <- data.frame(
datos_num_scaled,
dummies,
income = datos$income
)
cat("Dimensiones del dataset preprocesado:", dim(datos_modelo), "\n")## Dimensiones del dataset preprocesado: 48598 80
## Niveles de income: <=50K >50K
set.seed(2024)
idx_train <- createDataPartition(datos_modelo$income, p = 0.70, list = FALSE)
train_set <- datos_modelo[idx_train, ]
test_set <- datos_modelo[-idx_train, ]
cat("Entrenamiento:", nrow(train_set), "observaciones\n")## Entrenamiento: 34020 observaciones
## Prueba : 14578 observaciones
##
## Proporción en entrenamiento:
##
## <=50K >50K
## 76.45 23.55
## Proporción en prueba:
##
## <=50K >50K
## 76.46 23.54
La partición estratificada con createDataPartition
conserva la proporción original de clases en ambos conjuntos.
Se aplica una SVM con kernel lineal como primer modelo. Es un buen punto de partida porque es relativamente interpretable y eficiente computacionalmente.
# Pesos para compensar el desbalance de clases
n_neg <- sum(train_set$income == "<=50K")
n_pos <- sum(train_set$income == ">50K")
pesos <- c("<=50K" = 1, ">50K" = n_neg / n_pos)
set.seed(2024)
svm_lineal <- svm(
income ~ .,
data = train_set,
kernel = "linear",
cost = 1,
class.weights = pesos,
scale = FALSE # ya estandarizamos
)
summary(svm_lineal)##
## Call:
## svm(formula = income ~ ., data = train_set, kernel = "linear", cost = 1,
## class.weights = pesos, scale = FALSE)
##
##
## Parameters:
## SVM-Type: C-classification
## SVM-Kernel: linear
## cost: 1
##
## Number of Support Vectors: 14349
##
## ( 10962 3387 )
##
##
## Number of Classes: 2
##
## Levels:
## <=50K >50K
pred_svm <- predict(svm_lineal, newdata = test_set)
cm_svm <- confusionMatrix(pred_svm, test_set$income, positive = ">50K")
print(cm_svm)## Confusion Matrix and Statistics
##
## Reference
## Prediction <=50K >50K
## <=50K 8602 494
## >50K 2544 2938
##
## Accuracy : 0.7916
## 95% CI : (0.7849, 0.7982)
## No Information Rate : 0.7646
## P-Value [Acc > NIR] : 3.407e-15
##
## Kappa : 0.5203
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.8561
## Specificity : 0.7718
## Pos Pred Value : 0.5359
## Neg Pred Value : 0.9457
## Prevalence : 0.2354
## Detection Rate : 0.2015
## Detection Prevalence : 0.3760
## Balanced Accuracy : 0.8139
##
## 'Positive' Class : >50K
##
acc_svm <- cm_svm$overall["Accuracy"]
prec_svm <- cm_svm$byClass["Precision"]
rec_svm <- cm_svm$byClass["Recall"]
f1_svm <- cm_svm$byClass["F1"]
cat(sprintf("SVM Lineal — Accuracy: %.3f | Precision: %.3f | Recall: %.3f | F1: %.3f\n",
acc_svm, prec_svm, rec_svm, f1_svm))## SVM Lineal — Accuracy: 0.792 | Precision: 0.536 | Recall: 0.856 | F1: 0.659
# Submuestra estratificada para hacer tune más rápido
set.seed(2024)
idx_tune <- createDataPartition(train_set$income, p = 0.25, list = FALSE)
tune_data <- train_set[idx_tune, ]
tune_svm <- tune(
svm,
income ~ .,
data = tune_data,
kernel = "linear",
ranges = list(cost = c(0.01, 0.1, 1, 5, 10)),
tunecontrol = tune.control(cross = 5)
)
summary(tune_svm)##
## Parameter tuning of 'svm':
##
## - sampling method: 5-fold cross validation
##
## - best parameters:
## cost
## 0.1
##
## - best performance: 0.1487181
##
## - Detailed performance results:
## cost error dispersion
## 1 0.01 0.1523626 0.008391663
## 2 0.10 0.1487181 0.006652460
## 3 1.00 0.1503642 0.006622148
## 4 5.00 0.1503642 0.007641685
## 5 10.00 0.1500114 0.007432514
##
## Mejor cost: 0.1
best_cost <- tune_svm$best.parameters$cost
svm_opt <- svm(
income ~ .,
data = train_set,
kernel = "linear",
cost = best_cost,
class.weights = pesos,
scale = FALSE
)
pred_svm_opt <- predict(svm_opt, newdata = test_set)
cm_svm_opt <- confusionMatrix(pred_svm_opt, test_set$income, positive = ">50K")
print(cm_svm_opt)## Confusion Matrix and Statistics
##
## Reference
## Prediction <=50K >50K
## <=50K 8584 489
## >50K 2562 2943
##
## Accuracy : 0.7907
## 95% CI : (0.784, 0.7973)
## No Information Rate : 0.7646
## P-Value [Acc > NIR] : 2.598e-14
##
## Kappa : 0.5191
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.8575
## Specificity : 0.7701
## Pos Pred Value : 0.5346
## Neg Pred Value : 0.9461
## Prevalence : 0.2354
## Detection Rate : 0.2019
## Detection Prevalence : 0.3776
## Balanced Accuracy : 0.8138
##
## 'Positive' Class : >50K
##
acc_svm_opt <- cm_svm_opt$overall["Accuracy"]
prec_svm_opt <- cm_svm_opt$byClass["Precision"]
rec_svm_opt <- cm_svm_opt$byClass["Recall"]
f1_svm_opt <- cm_svm_opt$byClass["F1"]
cat(sprintf("SVM Lineal (opt) — Accuracy: %.3f | Precision: %.3f | Recall: %.3f | F1: %.3f\n",
acc_svm_opt, prec_svm_opt, rec_svm_opt, f1_svm_opt))## SVM Lineal (opt) — Accuracy: 0.791 | Precision: 0.535 | Recall: 0.858 | F1: 0.659
# Pesos del hiperplano separador (solo disponible para kernel lineal)
w_lineal <- t(svm_opt$coefs) %*% svm_opt$SV
importancia_svm <- sort(abs(w_lineal[1, ]), decreasing = TRUE)[1:15]
df_imp_svm <- data.frame(
Variable = names(importancia_svm),
Importancia = as.numeric(importancia_svm)
)
ggplot(df_imp_svm, aes(x = reorder(Variable, Importancia), y = Importancia)) +
geom_col(fill = "#2C7BB6") +
coord_flip() +
labs(title = "Top 15 variables — SVM Lineal",
subtitle = "Magnitud de pesos del hiperplano separador",
x = "Variable", y = "|w|") +
theme_minimal(base_size = 11)Las variables relationshipHusband,
marital_statusMarried-civ-spouse, capital_gain
y education_num aparecen entre las más importantes. Esto es
coherente con la literatura económica: el capital humano (educación) y
la acumulación de riqueza (capital gain) son predictores clave del
ingreso.
Como segundo modelo se aplica un árbol de decisión
con el paquete rpart. Este modelo tiene una ventaja
importante sobre la SVM: es completamente interpretable visualmente, lo
que facilita la comunicación de resultados a tomadores de
decisiones.
Para el árbol no es necesario el one-hot encoding ni la estandarización, así que se trabaja directamente con el dataset original (imputado).
# Para el árbol se usa el dataset original (imputado), sin escalar ni dummies
set.seed(2024)
idx_arbol <- createDataPartition(datos$income, p = 0.70, list = FALSE)
train_arbol <- datos[idx_arbol, ]
test_arbol <- datos[-idx_arbol, ]
cat("Entrenamiento:", nrow(train_arbol), "\n")## Entrenamiento: 34020
## Prueba : 14578
# Se usa class.weights a través de los pesos en rpart con parms
# El parámetro prior ajusta las probabilidades a priori para compensar el desbalance
prop_pos <- mean(train_arbol$income == ">50K")
prop_neg <- 1 - prop_pos
set.seed(2024)
arbol <- rpart(
income ~ .,
data = train_arbol,
method = "class",
parms = list(prior = c(prop_neg, prop_pos)),
control = rpart.control(
minsplit = 20,
minbucket = 7,
cp = 0.001,
maxdepth = 8
)
)
printcp(arbol)##
## Classification tree:
## rpart(formula = income ~ ., data = train_arbol, method = "class",
## parms = list(prior = c(prop_neg, prop_pos)), control = rpart.control(minsplit = 20,
## minbucket = 7, cp = 0.001, maxdepth = 8))
##
## Variables actually used in tree construction:
## [1] age capital_gain capital_loss education hours_per_week
## [6] native_country occupation relationship
##
## Root node error: 8011/34020 = 0.23548
##
## n= 34020
##
## CP nsplit rel error xerror xstd
## 1 0.1217701 0 1.00000 1.00000 0.0097690
## 2 0.0582948 2 0.75646 0.75646 0.0088095
## 3 0.0345775 3 0.69817 0.69817 0.0085336
## 4 0.0080514 4 0.66359 0.66359 0.0083601
## 5 0.0073649 6 0.64748 0.65410 0.0083111
## 6 0.0034952 8 0.63275 0.63737 0.0082232
## 7 0.0017476 11 0.62040 0.62339 0.0081482
## 8 0.0016727 12 0.61865 0.61840 0.0081211
## 9 0.0010000 19 0.60479 0.61740 0.0081157
# Seleccionar el cp que minimiza el error de validación cruzada
cp_opt <- arbol$cptable[which.min(arbol$cptable[, "xerror"]), "CP"]
cat("cp óptimo:", cp_opt, "\n")## cp óptimo: 0.001
arbol_podado <- prune(arbol, cp = cp_opt)
cat("\nNúmero de hojas árbol original:", sum(arbol$cptable[, "nsplit"]) + 1, "\n")##
## Número de hojas árbol original: 66
## Número de nodos terminales árbol podado: 20
rpart.plot(
arbol_podado,
type = 4,
extra = 104,
box.palette = c("#2C7BB6", "#D7191C"),
branch = 0.5,
shadow.col = "gray",
main = "Árbol de decisión podado — Adult Income"
)El árbol muestra las divisiones más importantes en orden jerárquico. Las primeras ramas corresponden a las variables con mayor poder discriminante.
pred_arbol <- predict(arbol_podado, newdata = test_arbol, type = "class")
cm_arbol <- confusionMatrix(pred_arbol, test_arbol$income, positive = ">50K")
print(cm_arbol)## Confusion Matrix and Statistics
##
## Reference
## Prediction <=50K >50K
## <=50K 10786 1752
## >50K 360 1680
##
## Accuracy : 0.8551
## 95% CI : (0.8493, 0.8608)
## No Information Rate : 0.7646
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.5319
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.4895
## Specificity : 0.9677
## Pos Pred Value : 0.8235
## Neg Pred Value : 0.8603
## Prevalence : 0.2354
## Detection Rate : 0.1152
## Detection Prevalence : 0.1399
## Balanced Accuracy : 0.7286
##
## 'Positive' Class : >50K
##
acc_arbol <- cm_arbol$overall["Accuracy"]
prec_arbol <- cm_arbol$byClass["Precision"]
rec_arbol <- cm_arbol$byClass["Recall"]
f1_arbol <- cm_arbol$byClass["F1"]
cat(sprintf("Árbol — Accuracy: %.3f | Precision: %.3f | Recall: %.3f | F1: %.3f\n",
acc_arbol, prec_arbol, rec_arbol, f1_arbol))## Árbol — Accuracy: 0.855 | Precision: 0.824 | Recall: 0.490 | F1: 0.614
imp_arbol <- arbol_podado$variable.importance
imp_arbol <- sort(imp_arbol, decreasing = TRUE)[1:12]
df_imp_arbol <- data.frame(
Variable = names(imp_arbol),
Importancia = as.numeric(imp_arbol)
)
ggplot(df_imp_arbol, aes(x = reorder(Variable, Importancia), y = Importancia)) +
geom_col(fill = "#D7191C") +
coord_flip() +
labs(title = "Top 12 variables — Árbol de Decisión",
subtitle = "Basado en reducción de impureza (Gini)",
x = "Variable", y = "Importancia") +
theme_minimal(base_size = 11)metricas <- data.frame(
Modelo = c("SVM Lineal (cost óptimo)", "Árbol de Decisión (podado)"),
Accuracy = round(c(acc_svm_opt, acc_arbol), 4),
Precision = round(c(prec_svm_opt, prec_arbol), 4),
Recall = round(c(rec_svm_opt, rec_arbol), 4),
F1 = round(c(f1_svm_opt, f1_arbol), 4),
Specificity = round(c(cm_svm_opt$byClass["Specificity"],
cm_arbol$byClass["Specificity"]), 4)
)
kable(metricas,
caption = "Comparación de métricas: SVM Lineal vs. Árbol de Decisión",
align = "c")| Modelo | Accuracy | Precision | Recall | F1 | Specificity |
|---|---|---|---|---|---|
| SVM Lineal (cost óptimo) | 0.7907 | 0.5346 | 0.8575 | 0.6586 | 0.7701 |
| Árbol de Decisión (podado) | 0.8551 | 0.8235 | 0.4895 | 0.6140 | 0.9677 |
Ambos modelos muestran desempeño similar en accuracy, pero difieren en el equilibrio entre Precision y Recall para la clase >50K. El modelo con mayor F1 tiene mejor balance entre detectar correctamente los casos positivos sin generar demasiados falsos positivos.
Ambos modelos coinciden en señalar variables relacionadas con
estado civil/relación familiar
(relationship, marital_status),
educación (education_num) y
ganancias de capital (capital_gain) como
los predictores más importantes del ingreso. Que los dos lleguen a
conclusiones similares nos da más confianza en que estos resultados no
son casualidad. |
| La relación entre estado civil e ingreso alto tiene sentido si pensamos que personas con mayor educación o ingresos tienden a estar en relaciones estables, y también porque en muchos hogares hay dos ingresos que se complementan. |
education_num sale como uno de los predictores más
fuertes en los dos modelos. Básicamente, entre más años de escolaridad
tiene una persona, más probable es que gane más de 50K. Esto apoya la
idea de que invertir en educación superior y formación técnica podría
ayudar a reducir la desigualdad de ingresos entre grupos.
Las gráficas muestran una diferencia muy marcada entre hombres y mujeres: los hombres tienen una probabilidad mucho mayor de estar en el grupo de ingresos altos. Esto puede deberse a varias cosas — las ocupaciones no están distribuidas igual entre géneros, hay interrupciones en la carrera laboral, y posiblemente también diferencias salariales directas. Políticas de igualdad salarial y programas de reincorporación laboral para mujeres serían relevantes aquí.
capital_gain aparece como predictor importante en ambos
modelos. Tiene sentido: quien ya tiene activos o inversiones genera más
ingresos de capital, lo que refuerza la desigualdad. Esto sugiere que
ampliar el acceso a instrumentos financieros para personas de bajos
ingresos (crédito, ahorro formal) podría tener un efecto
redistributivo.
Las diferencias entre ocupaciones son bastante grandes. Ocupaciones ejecutivas y técnicas tienen tasas de ingreso alto mucho más altas que las de servicios o agricultura. En ese sentido, programas de reconversión laboral hacia sectores mejor remunerados podrían tener impacto, sobre todo considerando que muchas ocupaciones de bajos ingresos están en riesgo por la automatización.
En general, los dos modelos funcionan de forma bastante similar en las métricas principales. La diferencia más importante no es tanto el desempeño sino lo que cada uno ofrece: el árbol de decisión da reglas claras y visuales que son fáciles de explicar, mientras que la SVM puede manejar mejor datasets con muchas variables (como este, después del one-hot encoding) sin sobreajustarse a patrones muy específicos.
Lo que nos parece más relevante es que ambos modelos coinciden en las mismas variables clave — educación, relación familiar y capital — lo que sugiere que estos patrones son reales y no dependen del método que se use para analizarlos.
Documento generado con R Markdown. Resultados reproducibles con
set.seed(2024).