Análisis de costos de contratos públicos en base a factores asociados al valor de contratación por partido político
Proyecto integrador con datos de Mexicanos Contra la Corrupción y la Impunidad (MCCI)
Autores/as
Alejandro Flores Hernandez
Emilio Lopez Juarez
Ruben Arturo Valerio Garcia
Salvador Gutierrez
Profesores: Jorge Juvenal Campos y Jose Manuel Toral Cruz.
Fecha de publicación
12 de junio de 2026
1 Resumen ejecutivo
Este proyecto analiza informacion de contratos publicos provenientes de MCCI con el objetivo de identificar variables asociadas al monto de contratacion. El análisis se enfoca en contratos vinculados con partidos politicos, proveedores, tipos de contrato, duracion y años de firma. La pregunta central es si estas variables ayudan a explicar o predecir el valor esperado de un contrato.
El flujo de trabajo incluyo carga de datos, limpieza, creacion de variables, analisis exploratorio, visualizaciones y comparacion de cuatro enfoques de modelado, regresion lineal OLS, Ridge Regression, Lasso Regression y XGBoost. Los resultados disponibles muestran que los modelos regularizados Ridge y Lasso mejoran frente a una regresion lineal tradicional, aunque su capacidad explicativa en pesos sigue siendo limitada. Esto sugiere que el monto de los contratos depende de factores adicionales que no estan completamente capturados en la base.
El modelo Lasso obtuvo el mejor resultado disponible entre los modelos ejecutados, con un RMSE en pesos de $2,964,510, MAE de $633,907 y R2 en pesos de 0.0517. El modelo Ridge obtuvo resultados muy similares. XGBoost aparece como una alternativa mas flexible para relaciones no lineales, pero sus metricas quedan como pendiente porque el script disponible requiere corregir un error de sintaxis antes de ejecutarse.
La conclusion principal es que el analisis permite detectar patrones relevantes y construir una base tecnica para un sistema de alertas, pero no permite afirmar irregularidades ni causalidad. Los hallazgos deben interpretarse como evidencia exploratoria para priorizar revisiones, no como acusaciones.
2 Contexto del problema
La contratacion publica es un espacio central para estudiar transparencia, rendicion de cuentas y uso de recursos publicos. A traves de los contratos es posible observar que proveedores concentran mayor participacion, que partidos aparecen con determinados patrones de gasto, que tipos de contrato se repiten y como evolucionan los montos a lo largo del tiempo.
Sin embargo, el analisis requiere cautela. Un contrato de monto alto, una concentracion de proveedores o una diferencia entre partidos no implica por si misma una irregularidad. Los datos permiten identificar asociaciones y puntos que merecen revision, pero no sustituyen auditorias, expedientes administrativos ni evidencia legal.
El problema especifico de este proyecto consiste en analizar si variables como tipo de contrato, proveedor, duracion y anio ayudan a explicar el monto de un contrato. Este enfoque se alinea con el Canvas LM del proyecto, donde la tarea de prediccion se define como estimar el costo esperado de un contrato individual emitido por un partido politico.
3 Propuesta de valor
La propuesta de valor consiste en transformar una base de contratos en informacion util para analistas, periodistas, organizaciones civiles y ciudadania interesada en transparencia. El proyecto busca apoyar la deteccion temprana de contratos cuyo costo real se aleje de un valor esperado estimado por modelos estadisticos.
Con base en el Canvas LM, el modelo podría integrarse en un tablero de auditoria ciudadana. En ese escenario, el usuario cargaría datos de contratos, observaría estimaciones del modelo y compararía los monto reales contra valores esperados. Los casos con desviaciones mayores se priorizarían para investigación documental o auditoria.
El valor no esta en afirmar automáticamente que existe corrupción, sino en ordenar información dispersa y producir alertas razonables para enfocar mejor los recursos de revisión.
4 Pregunta de investigación
Pregunta principal:
Que variables de los contratos públicos se asocian con un mayor valor de contratación y que modelo permite explicar o predecir mejor esta diferencia?
Preguntas secundarias:
Que proveedores concentran mayor numero de contratos?
Como se distribuyen los montos de contratación?
Que diferencias se observan por tipo de contrato?
Que papel tienen la duración del contrato y el año?
Ridge y Lasso mejoran frente a una regresión lineal tradicional?
XGBoost podría capturar patrones no lineales que los modelos lineales no capturan?
5 Datos
La fuente principal de datos es Mexicanos Contra la Corrupción y la Impunidad (MCCI). El proyecto contiene dos archivos principales:
datos/contratos.csv
datos/contratos_montos.csv
Para el modelado se utilizo principalmente contratos_montos.csv, porque contiene la variable partido y la columna costo, usada como monto del contrato.
Código
datos_raw <- readr::read_csv( datos_path,show_col_types =FALSE,locale = readr::locale(encoding ="UTF-8"))tibble(archivo ="contratos_montos.csv",filas =nrow(datos_raw),columnas =ncol(datos_raw),partidos =n_distinct(datos_raw$partido, na.rm =TRUE),tipos_contrato =n_distinct(datos_raw$tipo_contrato, na.rm =TRUE),anio_min =min(datos_raw$ano, na.rm =TRUE),anio_max =max(datos_raw$ano, na.rm =TRUE),costo_min =min(datos_raw$costo, na.rm =TRUE),costo_max =max(datos_raw$costo, na.rm =TRUE)) |>kable(caption ="Resumen general de la base de datos utilizada")
Resumen general de la base de datos utilizada
archivo
filas
columnas
partidos
tipos_contrato
anio_min
anio_max
costo_min
costo_max
contratos_montos.csv
15118
30
7
3
2019
2025
1
262275000
Los datos disponibles en el archivo contratos_montos.csv contienen 15,118 filas y 30 columnas. En la preparación usada por los scripts del proyecto quedaron 15,114 contratos limpios, después de filtrar registros sin monto, año, tipo de contrato, proveedor o duración.
Variables principales:
costo: monto del contrato.
partido: partido político asociado al archivo.
tipo_contrato: clasificación del contrato.
tipo_persona: persona física o moral.
moral_razon, moral_razon_homo, nom_fisica, nom_fisica_homo: campos usados para construir el proveedor.
fecha_firma, fecha_inicio_vigencia, fecha_fin_vigencia: fechas usadas para crear duración.
ano: año del contrato.
6 Limpieza y preparación de datos
La limpieza se realizo en los scripts del proyecto sin modificar los archivos originales dentro de datos/. El flujo seguido por los modelos fue:
Cargar contratos_montos.csv.
Convertir fechas con lubridate::ymd().
Crear monto_contrato = costo.
Calcular duracion_dias como diferencia entre fecha de fin e inicio de vigencia.
Completar ano con el año de fecha_firma cuando fuera necesario.
Construir una variable de proveedor unificada usando razón social homologada o nombre de persona física.
Limpiar tipo_contrato y reemplazar vacíos por “Sin especificar”.
Crear log_monto = log1p(monto_contrato) para reducir simetría.
El análisis exploratorio muestra una distribución de montos altamente asimétrica. La mediana del monto es mucho menor que el promedio, lo que indica presencia de contratos con valores extremadamente altos. Por esta razón, los modelos utilizan log_monto como variable objetivo.
Código
datos |>count(tipo_contrato, sort =TRUE) |>kable(caption ="Numero de contratos por tipo de contrato")
Numero de contratos por tipo de contrato
tipo_contrato
n
Prestación
10543
Adquisición
3798
Arrendamiento
749
Sin especificar
24
Código
proveedores_resumen <- datos |>group_by(proveedor) |>summarise(n_contratos =n(),monto_total =sum(monto_contrato, na.rm =TRUE),.groups ="drop" ) |>arrange(desc(n_contratos))proveedores_resumen |>slice_head(n =10) |>mutate(monto_total =dollar(monto_total, prefix ="$", big.mark =",")) |>kable(caption ="Top 10 proveedores por numero de contratos")
Top 10 proveedores por numero de contratos
proveedor
n_contratos
monto_total
MANUEL EDUARDO AVILA VEGA
468
$11,929,104
LA COVACHA GABINETE DE COMUNICACION SA DE CV
254
$75,796,804
JCDECAUX OUT OF HOME MEXICO SA DE CV
219
$12,085,272
ERIKA SANTILLAN SANCHEZ
210
$23,220,297
INDATCOM SA DE CV
210
$157,871,329
ELEMENT MEDIA SA DE CV
169
$92,604,449
FUNDACION RAFAEL PRECIADO HERNANDEZ
169
$101,015,039
IMPACTOS FRECUENCIA Y COBERTURA EN MEDIOS SA DE CV
156
$41,700,693
GUILLERMO DIAZ HERNANDEZ
151
$2,350,157
1984 COMUNICACION ESTRATEGICA SA DE CV
149
$21,295,880
7.1 Visualizaciones exploratorias
Código
ggplot(datos, aes(x = log_monto)) +# Barras continuas sin bordes blancos pesados y un tono sutilgeom_histogram(bins =50, fill ="#4A6FA5", color =NA, alpha =0.85) +labs(title ="Distribución del logaritmo del monto de contratos",subtitle ="Transformación log1p para reducir el efecto de valores extremos",x ="log1p(monto)",y ="Frecuencia",caption ="Fuente: elaboración propia con datos de MCCI" ) +# Estilo minimalista purotheme_minimal(base_size =11) +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(), # Tufte: No se necesitan cuadrículas verticales en histogramaspanel.grid.major.y =element_line(color ="#eaeaea", linewidth =0.5),plot.title =element_text(face ="bold", size =13, hjust =0),plot.subtitle =element_text(face ="plain", size =10, color ="gray30"),axis.title =element_text(face ="italic"),plot.caption =element_text(hjust =0, face ="italic", size =8, color ="gray40"),axis.line.x =element_line(color ="black", linewidth =0.5) )
La distribución confirma que la mayoría de contratos se concentra en montos relativamente bajos, mientras que existen contratos atípicos de valor muy alto. Esto justifica usar transformaciones logarítmicas y comparar modelos con distinto nivel de flexibilidad.
Código
ggplot(datos, aes(x = log_monto)) +# Barras continuas sin bordes blancos pesados y un tono sutilgeom_histogram(bins =50, fill ="#4A6FA5", color =NA, alpha =0.85) +labs(title ="Distribución del logaritmo del monto de contratos",subtitle ="Transformación log1p para reducir el efecto de valores extremos",x ="log1p(monto)",y ="Frecuencia",caption ="Fuente: elaboración propia con datos de MCCI" ) +# Estilo minimalista purotheme_minimal(base_size =11) +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(), # Tufte: No se necesitan cuadrículas verticales en histogramaspanel.grid.major.y =element_line(color ="#eaeaea", linewidth =0.5),plot.title =element_text(face ="bold", size =13, hjust =0),plot.subtitle =element_text(face ="plain", size =10, color ="gray30"),axis.title =element_text(face ="italic"),plot.caption =element_text(hjust =0, face ="italic", size =8, color ="gray40"),axis.line.x =element_line(color ="black", linewidth =0.5) )
7.2 Gráficas disponibles en el proyecto
Ademas de las visualizaciones generadas en este reporte, la carpeta del proyecto contiene gráficas exportadas por modelo. A continuación se incluyen algunas figuras representativas cuando los archivos existen en la ruta local.
Placeholder: no se encontraron graficas exportadas en las rutas esperadas.
Código
monto_anio <- datos |>group_by(ano) |>summarise(monto_total =sum(monto_contrato, na.rm =TRUE), .groups ="drop")ggplot(monto_anio, aes(x = ano, y = monto_total)) +# Línea y puntos finos en un tono café elegantegeom_line(color ="#8b4513", linewidth =0.8) +geom_point(color ="#8b4513", size =1.8) +scale_y_continuous(labels = scales::label_dollar(scale =1e-9, suffix =" MMM"),breaks =seq(0, max(monto_anio$monto_total), by =0.5e9) ) +scale_x_continuous(breaks =seq(min(monto_anio$ano), max(monto_anio$ano), by =1)) +labs(title ="Monto total contratado por año",x ="Año",y ="Monto total",caption ="Fuente: elaboración propia con datos de MCCI" ) +theme_minimal(base_size =11) +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(), # Remueve líneas verticalespanel.grid.major.y =element_line(color ="#f0f0f0", linewidth =0.5), # Líneas horizontales muy tenuesplot.title =element_text(face ="bold", size =13, hjust =0),axis.title =element_text(face ="italic"),plot.caption =element_text(hjust =0, face ="italic", size =8, color ="gray30"),axis.line =element_line(color ="black", linewidth =0.5) )
8 Modelos utilizados
La variable objetivo de los modelos de regresión fue log_monto = log1p(monto_contrato). Esta transformación ayuda a reducir el efecto de valores extremos y permite comparar modelos de manera mas estable.
Las variables predicadoras principales fueron:
tipo_contrato
tamano o clasificación del proveedor por monto total contratado
duracion_dias
ano
proveedor en el caso de la regresión lineal OLS
Los datos se dividieron en entrenamiento y prueba con una proporción de 75% y 25%, respectivamente. En los rescripto ejecutados quedaron 11,334 contratos para entrenamiento y 3,780 para prueba.
8.1 Modelo 1: Regresión lineal OLS
La regresión lineal OLS busca explicar el monto del contrato mediante una relación lineal entre variables predicadoras y log_monto. Su principal ventaja es la interpretable, pero es sensible a valores extremos, alta cardinal de proveedores y relaciones no lineales.
Interpretación: el modelo lineal tradicional tiene baja capacidad explicativa en pesos. Esto indica que las variables incluidas explican solo una parte pequen de la variación real del monto contractual. Aun así, sirve como linea base para comparar contra modelos regularizados.
8.2 Modelo 2: Ridge Regression
Ridge Regression usa nacionalización L2 para reducir la magnitud de los coeficientes y estabilizar el modelo cuando existen predicadores cor relacionados o categorías múltiples. En este proyecto se utilizo glmnet con mixture = 0.
Interpretación: Ridge mejora frente al predictor simple por tamaño de proveedor y frente al modelo OLS en RMSE y R2 en pesos. La variable tamano_Grande aparece asociada con aumentos en el monto esperado, mientras que tamano_Pequeno se asocia con reducciones.
8.3 Modelo 3: Lasso Regression
Lasso Regression usa penalización L1 y puede eliminar predictores al llevar algunos coeficientes exactamente a cero. En este proyecto se utilizo glmnet con mixture = 1.
Interpretación: Lasso obtuvo el mejor desempeño disponible por una diferencia pequeña frente a Ridge. Su ventaja adicional es la seleccion de variables, ya que elimino dos predictores con aporte bajo. Esto facilita una lectura mas compacta del modelo.
8.4 Modelo 4: XGBoost
XGBoost es un modelo de boosting basado en arboles que puede capturar relaciones no lineales e interacciones entre variables. En el proyecto existe el script xgboost_contratos1.R, que define tuning de hiperparámetros, validación cruzada, predicciones, importancia de variables y análisis de residuos.
Estado de resultados: pendiente de ejecucion final. El script disponible contiene un error de sintaxis en la creacion de ano:
ano =coalesce(as.integer(ano), lubridate::year(fecha_firma)),,
Debe corregirse a:
ano =coalesce(as.integer(ano), lubridate::year(fecha_firma)),
Por esta razon, no se reportan metricas numericas de XGBoost en este documento. Placeholder claro:
RMSE log: PENDIENTE
MAE log: PENDIENTE
R2 log: PENDIENTE
RMSE en pesos: PENDIENTE
MAE en pesos: PENDIENTE
R2 en pesos: PENDIENTE
9 Explicacion del codigo
9.1 Bloque 1: Carga de librerias
Los scripts cargan tidyverse para manipulacion y visualizacion de datos, tidymodels para el flujo de modelado, glmnet para Ridge y Lasso, y en el caso de XGBoost se prepara un modelo con boost_tree().
Los datos se cargan desde datos/contratos_montos.csv usando readr::read_csv() con codificacion UTF-8. Despues se revisan filas, columnas y tipos de variables.
La limpieza convierte fechas, calcula duracion, homologa proveedor, limpia tipo de contrato y filtra registros incompletos. Esta etapa es clave porque los modelos requieren variables numericas limpias y categorias consistentes.
Se crean monto_contrato, duracion_dias, proveedor, log_monto y tamano. La variable tamano clasifica proveedores en pequeno, mediano y grande segun monto total contratado.
Las visualizaciones exploran distribucion de montos, proveedores con mas contratos, contratos por tipo, monto por anio, metricas de validacion y predicciones frente a valores reales.
ggplot(datos, aes(x = log_monto)) +geom_histogram(bins =50, fill ="steelblue", color ="white") +labs(title ="Distribucion del logaritmo del monto de contratos",x ="log1p(monto)",y ="Frecuencia" )ggplot(predicciones, aes(x = monto_real, y = monto_predicho)) +geom_point(alpha =0.3, color ="steelblue") +geom_abline(slope =1, intercept =0, linetype ="dashed", color ="red") +scale_x_log10(labels = scales::label_dollar(prefix ="$", big.mark =",")) +scale_y_log10(labels = scales::label_dollar(prefix ="$", big.mark =","))
9.6 Bloque 6: Preparacion para modelos
Los scripts dividen la base en entrenamiento y prueba, aplican imputacion, codificacion dummy de variables categoricas, normalizacion y filtros de varianza cero.
OLS usa linear_reg() con motor lm. Ridge y Lasso usan linear_reg() con motor glmnet, ajustando la penalizacion mediante validacion cruzada. XGBoost usa boost_tree() con grilla de hiperparametros.
Las métricas usadas son RMSE, MAE y R2. RMSE y MAE se interpretan como errores de predicción; R2 mide proporción de variación explicada. Se reportan métricas tanto en escala log como en pesos cuando estuvieron disponibles.
Baja capacidad explicativa y sensibilidad a valores extremos
Ridge Regression
Regularizar coeficientes
log_monto
2,966,351 pesos
634,035 pesos
0.0514
Estable ante predictores correlacionados
No elimina variables y sigue con R2 bajo en pesos
Lasso Regression
Regularizar y seleccionar variables
log_monto
2,964,510 pesos
633,907 pesos
0.0517
Mejor resultado disponible y seleccion de variables
Puede eliminar variables utiles si la penalizacion no se ajusta bien
XGBoost
Capturar relaciones no lineales
log_monto
PENDIENTE
PENDIENTE
PENDIENTE
Flexible y adecuado para interacciones
Script requiere correccion antes de ejecutar
11 Interpretacion de resultados
Los resultados muestran que los modelos regularizados tienen mejor desempeno que la regresion lineal OLS. Lasso y Ridge reducen el RMSE en pesos y elevan el R2 frente a OLS. Sin embargo, el R2 en pesos se mantiene bajo, lo que indica que todavia existe mucha variacion no explicada por las variables disponibles.
La clasificacion del proveedor por tamano aporta informacion relevante. En Ridge y Lasso, tamano_Grande aparece asociado con montos esperados mas altos, mientras que tamano_Pequeno aparece asociado con montos menores. La duracion del contrato tambien aporta senal positiva: contratos mas largos tienden a asociarse con montos mayores.
La diferencia entre Ridge y Lasso es pequena. En este caso, Lasso tiene una ligera ventaja en metricas y ademas elimina dos predictores, por lo que puede considerarse el mejor modelo disponible hasta corregir y ejecutar XGBoost.
12 Conclusiones de los modelos
La regresion lineal OLS funciona como referencia inicial, pero su desempeno es limitado.
Ridge mejora la estabilidad y reduce el error frente a OLS.
Lasso ofrece el mejor resultado disponible y simplifica el modelo mediante seleccion de variables.
XGBoost es prometedor para capturar relaciones no lineales, pero sus metricas no deben reportarse hasta corregir el script.
Ningun modelo disponible permite hacer afirmaciones causales o acusaciones de irregularidad.
13 Discusion etica
Este proyecto analiza datos relacionados con partidos politicos, proveedores y contratos publicos. Por ello, la interpretacion debe ser cuidadosa. Una asociacion estadistica entre una variable y un monto alto no demuestra corrupcion, sobreprecio ni conducta indebida.
El uso responsable del modelo implica:
No confundir correlacion con causalidad.
No senalar culpables sin evidencia documental o legal.
Reconocer que la base puede contener valores faltantes, errores de captura o falta de contexto.
Usar las predicciones como alertas para priorizar revision, no como veredictos.
Evitar que el modelo reproduzca sesgos contra proveedores o partidos por diferencias historicas en la informacion disponible.
Desde el Canvas LM, el costo de un falso positivo es revisar un contrato que podria estar justificado. El costo de un falso negativo es mayor, porque una posible irregularidad podria pasar desapercibida. Por ello, el sistema debe privilegiar transparencia, trazabilidad y revision humana.
14 Areas de oportunidad
Corregir y ejecutar completamente el script de XGBoost.
Guardar metricas finales en archivos .csv para evitar depender solo de salida en consola.
Incorporar variables contextuales como entidad federativa, objeto del contrato, etapa electoral o inflacion.
Mejorar la homologacion de proveedores.
Analizar contratos atipicos por partido y proveedor.
Desarrollar un tablero interactivo de auditoria ciudadana.
Validar hallazgos con fuentes oficiales y expedientes documentales.
Explorar modelos de texto si las descripciones de contratos se usan como variables predictoras.
15 Conclusion general
La investigacion muestra que los datos de MCCI permiten construir un analisis tecnico sobre montos de contratos publicos y factores asociados. Los hallazgos principales son:
La base contiene 15,118 registros en contratos_montos.csv, con contratos entre 2019 y 2025.
Despues de limpieza, los scripts modelan 15,114 contratos.
Los montos presentan fuerte asimetria, con mediana de $114,359 y maximo de $262,275,000.
Ridge y Lasso mejoran frente a OLS, pero el poder explicativo en pesos sigue siendo limitado.
Lasso es el mejor modelo disponible hasta corregir y ejecutar XGBoost.
En respuesta a la pregunta de investigacion, variables como tamano de proveedor, tipo de contrato, duracion y anio si se asocian con el valor esperado de contratacion, pero no explican por completo la variacion de los montos. El proyecto aporta una base util para generar alertas y preguntas de investigacion, siempre bajo una interpretacion etica y no acusatoria.
15.1Fuente de datos
Mexicanos Contra la Corrupcion y la Impunidad (MCCI).
15.2Archivos principales del proyecto
contratos.csv, contratos_montos.csv, regresion_lineal_contratos.R, regresion_ridge_proveedores.R, regresion_lasso_proveedores.R, xgboost_contratos1.R y CANVAS LM.pdf.
15.3 Disclaimer de uso de IA
Para la elaboracion de este reporte se utilizo inteligencia artificial como apoyo en la organizacion de ideas, redaccion academica, estructuracion del documento Quarto e integracion de resultados disponibles en los scripts del proyecto.