Análisis Predictivo del Índice de Corrupción

Un Estudio Basado en Modelos de Machine Learning

Autor/a

Nicolás Rodríguez Gianoni

Fecha de publicación

9 de septiembre de 2024

Introducción

El presente reporte representa la última entrega de la Certificación Profesional en Ciencia de Datos de la UDELAR. En el mismo, presentaré y demostraré los conocimientos aprehendidos a lo largo del programa.

Para ello he seleccionado datos provenientes de la plataforma Kaggle. Estos datos pueden ser obtenidos a través del siguiente link World Economic Data. Tras realizar una investigación en Kaggle fue posible concluir que la fuente original es World Data Info.

Se cuenta con información para 110 países con relación a sus indicadores de corrupción, turismo, desempleo y costo de vida.

Adicionalmente, se han agregando columnas que indican el continente al que pertenece el país y su respectiva población como parte del proceso de minería de datos.

Preguntas guía

En este reporte me propongo analizar la realción existente entre el índice de corrupción y diversas variables socioeconómicas y demográficas.

Como se ha mencionado anteriormente, se implementan técnicas de minería de datos como imputación de valores faltantes y enriquecimiento de la base original.

Las preguntas que guían mi análisis son:

  • ¿Qué relación existe entre las variables socioeconómicas y demográficas incluidas en la base de datos y el índice de corrupción en diferentes países?

  • ¿Se pueden clasificar los países como “corruptos” o “no corruptos” basándonos en características socioeconómicas y demográficas?

Para responderlas se aplican algoritmos de Machine Learning sobre la variable que mide el Índice de Corrupción.

La primer pregunta es abordada con un Modelo de Regresión Lineal que explicar los determinantes del índice referido.

Una vez aplicada la lógica de regresión, se pasa a la de clasificación para responder la segunda pregunta. Antes, se realiza una transformación sobre la variable que mide el Índice de Corrupción para convertirla en una de tipo binomial. Así, se logra cumplir las condiciones necesarias para aplicar modelos de Regresión Logística, Decision Tree y Random Forest.

Descripción y Minería de Datos

En esta sección se presentan los datos originales, un detallada descripción del proceso de minería de datos y finalmente, un análisis exploratorio a partir de gráficos relevantes.

Descripción de Datos

Para la elaboración del análisis se han utilizado cinco conjuntos de datos en formato csv. que brindan información a nivel de diversas características socioeconómicas y demográficas de países alrededor del mundo.

A continuación, se describen las fuentes y las variables más importantes de cada conjunto de datos:

  • corruption.csv: Este conjunto de datos proporciona el Índice de Corrupción para cada país, una medida cuantitativa que refleja el nivel percibido de corrupción en el sector público. Cuanto mas alto sea su valor, mayor será el nivel de corrupción percibido en el país.

  • unemployment.csv: Contiene información sobre la Tasa de Desempleo por país, es decir, el porcentaje de la población económicamente activa que se encuentra desempleada.

  • tourism.csv: Este archivo incluye variables relacionadas con el turismo internacional, tales como el número de turistas que visitan cada país y los ingresos generados por el turismo. Sin embargo, debido a la presencia de numerosos valores faltantes para la gran mayoría de los países se optó por excluirlas en el análisis final.

  • richest_countries.csv: Proporciona datos sobre el Ingreso Anual per cápita de varios países, una métrica que mide el ingreso promedio de un ciudadano de cada país.

  • cost_of_living.csv: Incluye un Índice de Costo de Vida, que mide los costos relativos de bienes y servicios en cada país en comparación con otros países. Esto incluye factores como la vivienda, el transporte y los alimentos.

Minería de Datos

Proceso de integración de datos

Dado que la información para cada país se encontraba dispersa en los cinco archivos mencionados anteriormente, fue necesario consolidarla en un solo conjunto de datos. Para ello se aplicó un left join en cada archivo utilizando la variable country como primary key.

Código
# Uno todos los datasets en uno solo con la función left_join

merged_df <- corruption_df %>%
  left_join(unemployment_df, by = "country") %>%
  left_join(tourism_df, by = "country") %>%
  left_join(richest_countries_df, by = "country") %>%
  left_join(cost_of_living_df, by = "country")

Manejo de valores faltantes

Durante el proceso de minería de datos, se indentificaron varias columnas que contenían contenían valores faltantes. Por ejemplo, muchas variables vinculadas al turismo, como el número de turistas y los ingresos generados, sólo tenían información para un conjunto acotado de países. Por esta razón se decidió eliminarlas.

Para otras variables con un número de datos faltantes relativamente menor, se realizó la imputación de la mediana. A fin de conservarlas como variables explicativas del Índice de Corrupción.

Código
# Creo un vector con los nombres de todas las columnas para visualizar la cantidad de NA´s presentes en mi dataframe

columnas <- c("country", "annual_income", "corruption_index","unemployment_rate", "tourists_in_millions",
              "receipts_in_billions", "receipts_per_tourist", "percentage_of_gdp","gdp_per_capita", "cost_index",
              "monthly_income", "purchasing_power_index")

valores_nulos <- c(sum(is.na(merged_df$country)),
                   sum(is.na(merged_df$annual_income)),
                   sum(is.na(merged_df$corruption_index)),
                   sum(is.na(merged_df$unemployment_rate)),
                   sum(is.na(merged_df$tourists_in_millions)),
                   sum(is.na(merged_df$receipts_in_billions)),
                   sum(is.na(merged_df$receipts_per_tourist)),
                   sum(is.na(merged_df$percentage_of_gdp)),
                   sum(is.na(merged_df$gdp_per_capita)),
                   sum(is.na(merged_df$cost_index)),
                   sum(is.na(merged_df$monthly_income)),
                   sum(is.na(merged_df$purchasing_power_index))
                   )


# Elimino las columnas donde la cantidad de NA´s es altamente significativa (con relación al total de observaciones de mi dataset)

merged_df <- select(merged_df, -tourists_in_millions, 
-receipts_in_billions, 
-receipts_per_tourist, 
-percentage_of_gdp,
-gdp_per_capita)


# Imputo valores faltantes con la mediana de cada columna

merged_df <- merged_df %>%
  mutate(across(where(is.numeric), ~ ifelse(is.na(.), median(., na.rm = TRUE), .)))

Resumen de Valores Faltantes y Variables Consideradas

Variables conservadas y descartadas para el análisis

Variable Cantidad de NA's ¿Considerada para Análisis?
country 0 Conservada
annual_income 0 Conservada
corruption_index 0 Conservada
unemployment_rate 60 Conservada
tourists_in_millions 82 Descartada
receipts_in_billions 82 Descartada
receipts_per_tourist 82 Descartada
percentage_of_gdp 82 Descartada
gdp_per_capita 76 Descartada
cost_index 39 Conservada
monthly_income 39 Conservada
purchasing_power_index 39 Conservada

Variables utilizadas

Las variables consideradas en este análisis se presentan en la siguiente tabla:

Resumen de Variables Utilizadas en el Análisis

Descripción de las principales variables empleadas en el modelo

Nombre de la Variable Descripción Tipo de Variable
corruption_index Índice de corrupción (variable objetivo) Numérica
annual_income Ingreso anual per cápita Numérica
unemployment_rate Tasa de desempleo Numérica
cost_index Índice de costo de vida Numérica
monthly_income Ingreso mensual per cápita Numérica
purchasing_power_index Índice de poder adquisitivo Numérica
continent Continente al que pertenece el país Categórica
population Población total del país Numérica

Generación de variables adicionales

Como se puede observar en la tabla anterior, se generó una variable categórica llamada continent, que clasifica a cada país en función del continente al que pertenece.

Por otro lado, se incluyó la variable population. Que corresponde a la población total de cada país, obtenida de la base de datos del Banco Mundial.

Código
# Creo un data frame con la lista de países que conforman el dataset

countries <- c("Denmark", "Finland", "New Zealand", "Norway", "Singapore", "Sweden", 
               "Switzerland", "Netherlands", "Luxembourg", "Germany", "United Kingdom", 
               "Hong Kong", "Austria", "Canada", "Estonia", "Iceland", "Ireland", 
               "Australia", "Belgium", "Japan", "France", "United Arab Emirates", 
               "United States", "Qatar", "Portugal", "South Korea", "Spain", "Israel", 
               "Italy", "Poland", "Saudi Arabia", "Greece", "Malaysia", "China", 
               "Romania", "South Africa", "India", "Vietnam", "Argentina", "Brazil", 
               "Indonesia", "Turkey", "Sri Lanka", "Ecuador", "Thailand", "El Salvador", 
               "Sierra Leone", "Algeria", "Egypt", "Nepal", "Philippines", "Zambia", 
               "Eswatini", "Ukraine", "Gabon", "Mexico", "Niger", "Papua New Guinea", 
               "Azerbaijan", "Bolivia", "Djibouti", "Dominican Republic", "Kenya", 
               "Laos", "Paraguay", "Togo", "Angola", "Liberia", "Mali", "Russia", 
               "Burma", "Mauritania", "Pakistan", "Uzbekistan", "Cameroon", "Kyrgyzstan", 
               "Uganda", "Bangladesh", "Madagascar", "Mozambique", "Guatemala", "Guinea", 
               "Iran", "Tajikistan", "Central Africa", "Lebanon", "Nigeria", "Cambodia", 
               "Honduras", "Iraq", "Zimbabwe", "Eritrea", "Cape Verde", "Congo", 
               "Chad", "Comoros", "Haiti", "Nicaragua", "Sudan", "Burundi", 
               "Congo (Dem. Republic)", "Turkmenistan", "Equatorial Guinea", "Libya", 
               "Afghanistan", "Yemen", "Venezuela", "Somalia", "Syria", "South Sudan")

# Defino una función que clasifique cada país según su continente

get_continent <- function(country) {
  if (country %in% c("Denmark", "Finland", "Norway", "Sweden", "Switzerland", 
                     "Netherlands", "Luxembourg", "Germany", "United Kingdom", 
                     "Austria", "Estonia", "Iceland", "Ireland", "Belgium", 
                     "France", "Portugal", "Spain", "Italy", "Poland", "Greece", 
                     "Romania", "Ukraine", "Russia")) {
    return("Europe")
  } else if (country %in% c("Canada", "United States", "Mexico", "Guatemala", 
                            "Honduras", "El Salvador", "Nicaragua","Haiti","Dominican Republic")) {
    return("North America")
  } else if (country %in% c("Argentina", "Brazil", "Ecuador", "Bolivia", 
                            "Paraguay", "Venezuela")) {
    return("South America")
  } else if (country %in% c("Singapore", "Hong Kong", "Japan", "South Korea", 
                            "Israel", "Turkey", "Malaysia", "China", "India", 
                            "Vietnam", "Indonesia", "Sri Lanka", "Thailand", 
                            "Nepal", "Philippines", "Azerbaijan", "Laos", 
                            "Burma", "Pakistan", "Uzbekistan", "Kyrgyzstan", 
                            "Bangladesh", "Iran", "Tajikistan", "Lebanon", 
                            "Cambodia", "Iraq", "Afghanistan", "Yemen", "Syria","Turkmenistan","Saudi Arabia"
                            ,"United Arab Emirates", "Qatar")) {
    return("Asia")
  } else if (country %in% c("South Africa", "Sierra Leone", "Algeria", "Egypt", 
                            "Zambia", "Eswatini", "Gabon", "Niger", "Djibouti", 
                            "Kenya", "Togo", "Angola", "Liberia", "Mali", 
                            "Cameroon", "Uganda", "Madagascar", "Mozambique", 
                            "Guinea", "Central Africa", "Nigeria", "Zimbabwe", 
                            "Eritrea", "Cape Verde", "Congo", "Chad", "Comoros", 
                            "Sudan", "Burundi", "Congo (Dem. Republic)", 
                            "Equatorial Guinea", "Libya", "Somalia", "South Sudan","Mauritania")) {
    return("Africa")
  } else if (country %in% c("Australia", "New Zealand", "Papua New Guinea")) {
    return("Oceania")
  } else {
    return("Unknown")
  }
}

# Aplico la función para clasificar los países según continentes

continent <- sapply(countries, get_continent)

# Creo el dataset final con la nueva columna de continentes

continent_df <- data.frame(country = countries, continent = continent)
row.names(continent_df) <- continent_df$country

# Uno este último dataset al merged_df por la columna "country

merged_df <- merged_df %>%
  left_join(continent_df, by = "country")

# Cambio el formato de las columnas según corresponda

merged_df <- merged_df %>%
  mutate_if(is.character, as.factor)

# Ahora voy a crear una columna que contenga la población para cada país listado

# Obtengo los códigos ISO-3 de los países

iso_codes <- countrycode(sourcevar = countries,
                         origin = "country.name",
                         destination = "iso3c")

# Obtengo la población para los países de la lista para el año 2020

poblacion <- WDI(country = iso_codes,
                 indicator = "SP.POP.TOTL",
                 start = 2020,
                 end = 2020)

# Renombro la columna "SP.POP.TOTL" a "population" y reduzco el número de columnas del dataset

population_df <- select(poblacion, country, population = SP.POP.TOTL)

# Merge con el dataframe de población

merged_df <- merged_df %>%
  left_join(population_df, by = "country") 

# En el merge anterior quedaron filas sin completar, por tanto filtro los países con valores nulos en la columna "population" y creo una lista

null_countries <- merged_df %>%
  filter(is.na(population)) %>%
  select(country)

# Crear un data frame que resuma la información de país y población a partir de datos de internet

null_countries_df <- data.frame(country = c("South Korea", "Vietnam", "Turkey", "Egypt", "Laos", "Russia", "Burma", "Kyrgyzstan", "Iran", "Central Africa", "Cape Verde", "Congo", "Congo (Dem. Republic)", "Yemen", "Venezuela", "Syria","Hong Kong"),
                         population = c(51700000, 97300000, 83600000, 102300000, 7200000, 145900000, 54000000, 6500000, 83900000, 4700000, 555987, 86800000, 86800000, 29800000, 28500000, 17500000,7500000),
                         stringsAsFactors = FALSE)

# Completo la columna population de mi dataframe "merged_df"

merged_df$population <- ifelse(is.na(merged_df$population), null_countries_df$population[match(merged_df$country, null_countries_df$country)], merged_df$population)

Análisis Exploratorio de Datos

El análisis realizado en esta sección tiene como propósito conocer las distribuciones de las variables mas relevantes para responder las preguntas guía. También busca transmitir de manera clara las relaciones existentes entre las variables consideradas en la tabla anterior.

Distribución del Índice de Corrupción

El Índice de Corrupción será la variable objetivo en este análisis y por lo tanto, resulta importante conocer su distribución a lo largo de la muestra.

Código
# Distribución del índice de corrupción mejorado
ggplot(merged_df, aes(x = corruption_index)) +
  geom_histogram(aes(y = ..density..), binwidth = 1, fill = "darkblue", color = "white", alpha = 0.7) +
  geom_density(color = "darkblue", fill = "lightblue", alpha = 0.3) +
  labs(
    x = "Índice de Corrupción",
    y = "Densidad"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    axis.title.x = element_text(size = 12, face = "bold"),
    axis.title.y = element_text(size = 12, face = "bold"),
    axis.text = element_text(size = 10),
    panel.grid.major = element_line(color = "gray80"),
    panel.grid.minor = element_blank()
  )
Figura 1: Si bien hay cierta concentración, la muestra persenta gran variabilidad en el Índice de Corrupción.

De la Figura 1 podemos notar que las observaciones tienden a concentrarse en valores altos del Índice, es posible inferir esto por dos razones. Esto puede tener efectos significativos sobre la etapa de clasificación de este reporte, dado que el punto de corte para definir si un país es “corrupto” o “no corrupto” es la mediana del Índice. Por lo tanto, si tengo una muestra altamente desbalanceada hacia valores altos del Índice, estaría sesgando los eventuales pronósticos de mis modelos.

Boxplot sobre el Índice de Corrupción por Continente

Los gráficos Boxplot son súmamente útiles para observar la distribución del Índice de Corrupción en función de variables categóricas como el Continente. A partir de ellos podemos ver claramente donde se ubica la mediana del Índice para cada Continente, así como también la dispersión existente dentro de cada grupo y la existencia de outliers.

A continuación de presenta el gráfico mencionado:

Código
# Creo un gráfico boxplot que muestra el nivel de corrupción en función del continente

ggplot(merged_df, aes(x = continent, y = corruption_index, fill = continent)) +
  geom_boxplot(outlier.color = "red", outlier.shape = 16, outlier.size = 2) +
  scale_fill_brewer(palette = "Set3") +
  xlab("") + 
  ylab("") +
  labs(y = "Índice de Corrupción",
  fill = "Continent") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    axis.title.x = element_text(size = 12, face = "bold"),
    axis.title.y = element_text(size = 12, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )
Figura 2: Se observa la presencia de mayor dispersión en continentes como Asia, Europa y Oceanía. Donde las cajas son mas amplias.

Distribución del Ingreso Anual

Sería esperable que el Ingreso Anual también influyera sobre los niveles del Índice de Corrupción. A continuación se presenta un histograma que permite visualizar cómo se distribuye esta variable en los países de la muestra.

Código
# Distribución del ingreso anual mejorado
ggplot(merged_df, aes(x = annual_income)) +
  geom_histogram(aes(y = ..density..), binwidth = 5000, fill = "darkgreen", color = "white", alpha = 0.7) +
  geom_density(color = "darkgreen", fill = "lightgreen", alpha = 0.3) +
  labs(
    x = "Ingreso Anual per Cápita",
    y = "Densidad"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    axis.title.x = element_text(size = 12, face = "bold"),
    axis.title.y = element_text(size = 12, face = "bold"),
    axis.text = element_text(size = 10),
    panel.grid.major = element_line(color = "gray80"),
    panel.grid.minor = element_blank()
  )
Figura 3: La muestra está mayoritariamente conformada por países de ingreso anual bajo

La Figura 3 muestra que la mayoría de los países presentan un Ingreso Anual concentrado en los tramos bajos y medios del recorrido de la variable. Siendo muy pocos los países que presentan Ingreso Anual alto.

Relación entre el Ingreso Anual y el Índice de Corrupción

Los diagramas de puntos nos permiten contrastar de manera preliminar las afirmaciones hechas en los comentarios del Figura 3. Retomando, sería esperable obtener una relación entre el Ingreso Anual y el Índice de Corrupción.

Código
# Scatter plot mejorado de ingresos anuales vs corrupción
ggplot(merged_df, aes(x = annual_income, y = corruption_index)) + 
  geom_point(color = "darkblue", alpha = 0.6, size = 3) +  # Puntos en azul oscuro, ligeramente transparentes
  geom_smooth(method = "lm", se = FALSE, color = "red", size = 1.2) +  # Línea de tendencia en rojo
  labs(
    x = "Ingresos Anuales per Cápita (USD)",
    y = "Índice de Corrupción"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    axis.title.x = element_text(size = 12, face = "bold"),
    axis.title.y = element_text(size = 12, face = "bold"),
    axis.text = element_text(size = 10),
    panel.grid.major = element_line(color = "gray80"),
    panel.grid.minor = element_blank()
  )
Figura 4: Parece haber una relación lineal decreciente entre el Índice de Corrupción y el Ingreso Anual

Como bien se muestra en el Figura 4 , a medida que el Ingreso Anual crece, los niveles del Índice de Corrupción observados tienden a disminuir. Si bien esta relación es clara, no es suficiente para afirmar una relación estadísticamente significativa. Para ello será necesaria la aplicación de algoritmos de Machine Learning.

Mapa de Correlaciones

Los mapas de calor de correlaciones son una herramienta gráfica súmamente útiles para detectar relaciones lineales entre variables numéricas que forman parte del análisis. A continuación se presenta el gráfico aplicado a los datos utilizados:

Código
correlation_matrix <- cor(merged_df %>% select(where(is.numeric)))

corrplot(
  correlation_matrix, 
  method = "color",            # Usamos colores para las correlaciones
  type = "upper",              # Mostrar solo la mitad superior
  col = colorRampPalette(c("#d73027", "#fdae61", "#ffffbf", "#a6d96a", "#1a9850"))(200),  # Paleta de colores elegante
  tl.col = "black",            # Color de los nombres de las variables
  tl.cex = 0.8,                # Tamaño de los nombres de las variables
  cl.cex = 0.8,                # Tamaño de la leyenda
  addCoef.col = "black",       # Mostrar los valores de correlación
  number.cex = 0.7,            # Tamaño de los valores
  mar = c(0, 0, 1, 0)          # Márgenes ajustados
)
Figura 5: Las variables monetarias parecen estar altamente correlacionadas con el Índice de Corrupción

De la Figura 5 es posible concluir que hay una correlación negativa entre el Índice de Corrupción y el Ingreso Anual. Esto nos indicaría que en la medida que el Ingreso Anual crezca, sería esperable que el Índice de Corrupción descendiera.

Modelos Predictivos

Modelo de Regresión Lineal Múltiple

Especificación


Call:
lm(formula = corruption_index ~ annual_income + unemployment_rate + 
    cost_index + monthly_income + purchasing_power_index + continent + 
    population, data = train_data)

Residuals:
     Min       1Q   Median       3Q      Max 
-16.8348  -6.0593  -0.6743   4.8300  25.1628 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)             8.436e+01  5.484e+00  15.385  < 2e-16 ***
annual_income          -1.670e-03  3.336e-04  -5.007 3.42e-06 ***
unemployment_rate      -1.836e-01  1.899e-01  -0.967  0.33674    
cost_index             -2.192e-01  1.071e-01  -2.046  0.04414 *  
monthly_income          1.815e-02  5.823e-03   3.116  0.00258 ** 
purchasing_power_index -2.629e-01  1.123e-01  -2.341  0.02182 *  
continentAsia          -1.548e+00  2.967e+00  -0.522  0.60332    
continentEurope        -7.151e+00  3.546e+00  -2.017  0.04720 *  
continentNorth America  3.575e+00  3.503e+00   1.021  0.31061    
continentOceania       -1.081e+01  5.627e+00  -1.920  0.05851 .  
continentSouth America  4.292e+00  4.253e+00   1.009  0.31608    
population             -3.803e-09  1.538e-08  -0.247  0.80537    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 8.315 on 77 degrees of freedom
Multiple R-squared:  0.8784,    Adjusted R-squared:  0.8611 
F-statistic: 50.59 on 11 and 77 DF,  p-value: < 2.2e-16

Resultados

El Modelo de Regresión Lineal Múltiple muestra la relación entre las variables socioeconómicas (ingreso, tasa de desempleo, índice de costo de vida, etc.) y el índice de corrupción. Es decir que se intenta explicar y posteriormente predecir dicho índice con base a las variables listadas previamente.

En cuanto a resultados, el R^2 del modelo fue de 0.88 , lo que indica que aproximadamente el 87.84% de la variabilidad en el índice de corrupción está explicada por las variables del modelo. Esto sugiere una capacidad aceptable de predicción del modelo, que podría ser mejorada con la inclusión de nuevas variables a la especificación.

El MSE (error cuadrático medio), que indica el promedio del error cuadrático entre los valores predichos y observados, fue de 38.23 .

Cabe resaltar que en este modelo las variables que resultaron significativas para un p-value menores a 0.10 fueron:

  • annual_income

  • cost_index

  • monthly_income

  • purchasing_power_index

  • continentEurope

  • continentOceania

Código
# Crear el gráfico con ggplot2

ggplot(lm_results, aes(x = Observado, y = Predicho)) +
  geom_point(color = "blue") +  # Puntos de observación
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +  # Línea de referencia
  labs(title = "Valores Observados vs Predichos en el Conjunto de Prueba",
       x = "Valor Observado",
       y = "Valor Predicho") +
  theme_minimal()

La Figura 6 muestra la relación entre los valores observados y los predichos. En la medida que los puntos se alinean con la diagonal punteada, el modelo estará haciendo buenas predicciones. Este pareciera ser el caso para los datos analizados.

Modelo de Regresión Logística

Especificación


Call:
glm(formula = corruption_binary ~ annual_income + unemployment_rate + 
    cost_index + monthly_income + purchasing_power_index + continent + 
    population, family = binomial, data = train_data)

Coefficients:
                         Estimate Std. Error z value Pr(>|z|)
(Intercept)            -4.185e+00  4.339e+00  -0.965    0.335
annual_income          -2.079e-04  1.373e-04  -1.514    0.130
unemployment_rate      -7.275e-02  5.887e-02  -1.236    0.217
cost_index              1.809e-01  1.202e-01   1.505    0.132
monthly_income         -1.522e-02  1.292e-02  -1.178    0.239
purchasing_power_index  2.941e-01  2.629e-01   1.119    0.263
continentAsia           2.894e-01  9.581e-01   0.302    0.763
continentEurope         1.080e+00  2.014e+00   0.536    0.592
continentNorth America  7.851e-02  1.204e+00   0.065    0.948
continentOceania       -1.757e+01  1.771e+03  -0.010    0.992
continentSouth America  9.482e-02  1.267e+00   0.075    0.940
population             -4.097e-09  5.451e-09  -0.752    0.452

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 123.099  on 88  degrees of freedom
Residual deviance:  58.895  on 77  degrees of freedom
AIC: 82.895

Number of Fisher Scoring iterations: 16

Con esta especificación se pretende clasificar a los países pertenecientes al grupo de entrenamiento en función de la variable corruption_binary. Para ello se plantea una regresión logística de esta variable en función de las variables previamente mencionadas.

Resultados

El Modelo de Regresión Logística tiene una exactitud de 85.71 %, lo que sugiere que el modelo estimado es capaz de clasificar correctamente la mayoría de los países como corruptos o no corruptos.

El F1 Score, que equilibra precisión y recall, es de 0.87, lo que indica un buen balance entre ambos.

A continuación se presenta una tabla con los resultados obtenidos para esta especificación:

Resultados del Modelo de Regresión Logística

Precisión, Recall y F1 Score (sobre corruption_binary=1)

Métrica Valor
Precisión 0.77
Recall 1.00
F1 Score 0.87

Modelo de Arbol de Decisión

Especificación

En términos gráficos, sería posible definir el proceso de clasificación entre país “corrupto” y “no corrupto” a partir del siguiente diagrama.

Código
tree <- rpart(corruption_binary ~ annual_income + unemployment_rate + cost_index + monthly_income + purchasing_power_index + continent + population,
              data = train_data, 
              method = "class", 
              control = rpart.control(maxdepth = 17))


# Predecir en los datos de prueba
predictions_tree <- predict(tree, test_data, type = "class")
confusion_matrix_tree <- table(predictions_tree, test_data$corruption_binary)

# Calcular la precisión
accuracy_tree <- sum(diag(confusion_matrix_tree)) / sum(confusion_matrix_tree)

# Precisión, Recall y F1 Score
precision_tree <- confusion_matrix_tree[2, 2] / (confusion_matrix_tree[2, 1] + confusion_matrix_tree[2, 2])
recall_tree <- confusion_matrix_tree[2, 2] / (confusion_matrix_tree[2, 2] + confusion_matrix_tree[1, 2])
f1_tree <- 2 * (precision_tree * recall_tree) / (precision_tree + recall_tree)

Resultados

Según el diagrama anterior, las variables que han resultado significativas para clasificar a los países de la muestra de entrenamiento entre “corrupto” y “no corrupto” son annual_income y population .

Además, podemos decir que el Modelo de Árbol de Decisión tiene una exactitud de 80.95 %. Su simplicidad interpretativa lo hacen un método de clasificación útil, aunque su F1 Score de 0.82 sugiere que podría ser menos eficaz que otros modelos como Regresión Logística.

A continuación se presenta una tabla con los resultados obtenidos para esta especificación:

Código
# Crear tabla con gt
tree_results_df <- data.frame(
  Métrica = c("Precisión", "Recall", "F1 Score"),
  Valor = c(precision_tree, recall_tree, f1_tree)
)

# Generar tabla con gt
gt(tree_results_df) %>%
  tab_header(
    title = md("**Resultados del Modelo de Árbol de Decisión**"),
    subtitle = md("Precisión, Recall y F1 Score (sobre corruption_binary=1)")
  ) %>%
  cols_label(Métrica = "Métrica", Valor = "Valor")%>% 
  fmt_number(
    columns = c(Valor),  # Seleccionar la columna 'Valor' para formatear
    decimals = 2         # Mostrar 2 decimales
  )

Resultados del Modelo de Árbol de Decisión

Precisión, Recall y F1 Score (sobre corruption_binary=1)

Métrica Valor
Precisión 0.75
Recall 0.90
F1 Score 0.82

Modelo Random Forest

Especificación

El Modelo Random Forest combina múltiples árboles de decisión para mejorar la precisión y estabilidad de las predicciones.

Cada árbol se entrena con una muestra aleatoria de datos y utiliza diferentes características para tomar decisiones. Luego, las predicciones de los árboles individuales se combinan para producir una predicción final. Esto permite que el modelo sea más resistente al sobreajuste y más preciso que un solo árbol de decisión.

Código
# Entrenar el modelo de Random Forest
rf_model_clf <- randomForest(as.factor(corruption_binary) ~ annual_income + unemployment_rate + cost_index + monthly_income + purchasing_power_index + continent + population, 
                             data = train_data, ntree = 500, mtry = 3, importance = TRUE)

# Predicciones en el conjunto de prueba
predictions_rf <- predict(rf_model_clf, test_data)
confusion_matrix_rf <- table(predictions_rf, test_data$corruption_binary)

# Calcular la precisión
accuracy_rf <- sum(diag(confusion_matrix_rf)) / sum(confusion_matrix_rf)

# Precisión, Recall y F1 Score
precision_rf <- confusion_matrix_rf[2, 2] / (confusion_matrix_rf[2, 1] + confusion_matrix_rf[2, 2])
recall_rf <- confusion_matrix_rf[2, 2] / (confusion_matrix_rf[2, 2] + confusion_matrix_rf[1, 2])
f1_rf <- 2 * (precision_rf * recall_rf) / (precision_rf+recall_rf)

Resultados

Las métricas de rendimiento para este modelo coinciden con el de Arbol de Decisión. A pesar de ello, a continuación se presenta una tabla con los resultados obtenidos para esta especificación:

Resultados del Modelo de Árbol de Decisión

Precisión, Recall y F1 Score (sobre corruption_binary=1)

Métrica Valor
Precisión 0.75
Recall 0.90
F1 Score 0.82

Conclusiones

En este proyecto, se analizaron datos socioeconómicos y demográficos de 110 países para investigar la relación entre diferentes variables y el índice de corrupción. Los modelos predictivos, como la Regresión Lineal Múltiple, la Regresión Logística, el Árbol de Decisión, y el Random Forest, permitieron explorar tanto la predicción del índice de corrupción como la clasificación de los países en “corruptos” o “no corruptos”. A continuación, se destacan las principales conclusiones:

  1. Relación entre ingreso y corrupción:

    • El análisis realizado permitió fundamentar de manera estadística que el ingreso anual per cápita y el índice de poder adquisitivo son variables clave para predecir el índice de corrupción. Aquellos países con mayor nivel de ingreso anual tienden a tener menores niveles de corrupción. En esta línea nos inducía a pensar la Figura 4.
  2. Desempeño de los modelos:

    • El modelo de Regresión Logística mostró el mejor desempeño, con una precisión del 86% y un F1 Score de 0.87, lo que indica que este modelo es eficaz para clasificar a los países según su nivel de corrupción.

    • Los modelos de Random Forest y Árbol de Decisión también obtuvieron buenos resultados, aunque con una leve disminución en la precisión en comparación con Regresión Logística.

  3. Importancia de las variables geográficas:

    • La inclusión de la variable continente permitió identificar diferencias significativas en los niveles de corrupción entre regiones geográficas, que pueden ser visualizadas en la Figura 2. Hecho que refuerza la hipótesis de que las características socioeconómicas y políticas regionales influyen en la percepción y el nivel real de corrupción en un país.
  4. Implicaciones socioeconómicas:

    • Los resultados obtenidos podrían ser de utilidad para responsables de políticas públicas que busquen reducir los niveles de corrupción. Enfocar sus esfuerzos en la mejora del bienestar económico parecería ser un camino que contribuye a la disminución del Índice de Corrupción a nivel mundial.

Limitaciones

A pesar de los resultados obtenidos, el presente estudio tiene varias limitaciones que deben tenerse en cuenta:

  1. Valores faltantes e imputación de datos:

    • Durante el proceso de minería de datos, fue necesario realizar imputaciones manuales en las celdas con valores faltantes. El método empleado, que fue el de la imputación por la mediana de las variables, permitió evitar la pérdida de datos. A pesar de ello, puede haber introducido sesgos sobre las observaciones con valores faltantes, al tratarse de una imputación arbitraria.

    • En adición a ello, algunas variables como el turismo fueron descartadas debido a la gran cantidad de datos faltantes. Esta y otras variables descartadas podrían haber aportado mayor poder explicativo a los modelos estimados.

  2. Simplificación de la variable de corrupción:

    • El índice de corrupción se trató como una variable continua y posteriormente como una variable binaria para simplificar la clasificación de los países. Esta simplificación puede haber reducido la capacidad del modelo para captar toda la complejidad de los factores que influyen en la corrupción.
  3. Desbalance de clases:

    • La clasificación de los países como corruptos o no corruptos se basó en la mediana del índice de corrupción. Ello puede haber generado un desbalance en las clases que afectara el rendimiento de los modelos.
  4. Factores externos no considerados:

    • Aunque se incluyeron variables socioeconómicas importantes, existen factores políticos, históricos y culturales que no se consideraron en este análisis y que podrían tener un impacto significativo en los niveles de corrupción de un país. La ausencia de estas variables puede haber limitado la capacidad predictiva de los modelos.

Glosario de Métricas de Rendimiento

En este apartado se presentan las definiciones de las métricas de rendimiento utilizadas para evaluar los modelos predictivos en el análisis:

1. Precisión (Precision)

La precisión mide la proporción de predicciones correctas positivas (verdaderos positivos) en relación con el total de predicciones positivas (verdaderos positivos más falsos positivos). Es una métrica clave para evaluar qué tan preciso es un modelo a la hora de identificar correctamente los ejemplos positivos.

\[ \text{Precisión} = \frac{\text{Verdaderos Positivos}}{\text{Verdaderos Positivos} + \text{Falsos Positivos}} \]

  • Un valor de precisión cercano a 1 indica que el modelo comete pocos falsos positivos.
  • Un valor bajo sugiere que el modelo predice incorrectamente muchos falsos positivos.

2. Recall (Sensibilidad o Tasa de Verdaderos Positivos)

El recall mide la capacidad del modelo para identificar todos los ejemplos positivos reales. Es la proporción de verdaderos positivos detectados por el modelo en relación con el total de ejemplos positivos reales (verdaderos positivos más falsos negativos).

\[ \text{Recall} = \frac{\text{Verdaderos Positivos}}{\text{Verdaderos Positivos} + \text{Falsos Negativos}} \]

  • Un recall alto significa que el modelo detecta la mayoría de los casos positivos.
  • Un recall bajo indica que el modelo pasa por alto muchos ejemplos positivos (es decir, comete muchos falsos negativos).

3. F1 Score

El F1 Score es la media armónica entre la precisión y el recall. Se utiliza como una métrica equilibrada que toma en cuenta tanto los falsos positivos como los falsos negativos, y es especialmente útil cuando existe un desbalance en las clases o cuando es necesario un equilibrio entre precisión y recall.

\[ F1 = 2 \cdot \left(\frac{\text{Precisión} \cdot \text{Recall}}{\text{Precisión} + \text{Recall}}\right) \]

  • Un F1 Score cercano a 1 indica un buen equilibrio entre precisión y recall.
  • Un valor bajo de F1 Score sugiere que el modelo no logra un buen equilibrio entre falsos positivos y falsos negativos.

4. Matriz de Confusión

Una matriz de confusión es una tabla que permite evaluar el desempeño de un modelo de clasificación, mostrando las verdaderas etiquetas frente a las predicciones realizadas por el modelo. La matriz incluye:

  • Verdaderos Positivos (TP): Casos positivos correctamente predichos.
  • Falsos Positivos (FP): Casos negativos que fueron incorrectamente predichos como positivos.
  • Verdaderos Negativos (TN): Casos negativos correctamente predichos.
  • Falsos Negativos (FN): Casos positivos que fueron incorrectamente predichos como negativos.

5. Exactitud (Accuracy)

La exactitud mide la proporción total de predicciones correctas (positivas y negativas) en relación con el total de predicciones realizadas por el modelo.

\[ \text{Exactitud} = \frac{\text{Verdaderos Positivos} + \text{Verdaderos Negativos}}{\text{Total de Predicciones}} \]

  • Un valor alto de exactitud indica que el modelo predice correctamente la mayoría de los ejemplos, pero puede no ser confiable si las clases están desbalanceadas.

6. Especificidad (Specificity)

La especificidad mide la proporción de verdaderos negativos en relación con el total de ejemplos negativos. Esta métrica es relevante cuando es importante evitar falsos positivos.

\[ \text{Especificidad} = \frac{\text{Verdaderos Negativos}}{\text{Verdaderos Negativos} + \text{Falsos Positivos}} \]

Matrices de Confusión

Modelo de Regresión Logística

Modelo Arbol de Decisión

Modelo Random Forest