Contexto del estudio: Este análisis explora las brechas en estado de salud, acceso a servicios y gasto de bolsillo entre el régimen contributivo y subsidiado en el Caribe colombiano, usando datos de la ECV 2024 (DANE). Es el análisis descriptivo y exploratorio previo a la estimación econométrica del costo esperado en cáncer de mama.
Modelo econométrico objetivo: \(Costos_i = \beta_0 + \beta_1 Regimen_i + \beta_2 Estadio_i + \beta_3 Ruralidad_i + \beta_4 Edad_i + \varepsilon_i\)
ruta_base <- file.path(
Sys.getenv("OneDrive"),
"OCSA 2026",
"ANALISIS ECO. SALUD",
"salud2024"
)
ruta_salud <- file.path(ruta_base, "salud.CSV")
ruta_compo <- file.path(ruta_base, "caracteristicas_composicion_hogar.CSV")
cat("¿Existe módulo Salud? ", file.exists(ruta_salud), "\n")## ¿Existe módulo Salud? TRUE
## ¿Existe módulo Composición? TRUE
salud <- read_delim(
ruta_salud,
delim = ";",
locale = locale(encoding = "latin1"),
show_col_types = FALSE
)
composicion <- read_delim(
ruta_compo,
delim = ";",
locale = locale(encoding = "latin1"),
show_col_types = FALSE
)
cat("Módulo Salud: ", nrow(salud), "filas x", ncol(salud), "columnas\n")## Módulo Salud: 228688 filas x 157 columnas
## Módulo Composicion: 228688 filas x 78 columnas
keys <- c("DIRECTORIO", "SECUENCIA_ENCUESTA", "SECUENCIA_P", "ORDEN")
vars_compo <- c(
keys,
"P6020", # sexo al nacer
"P6040", # edad en años
"P6080", # autorreconocimiento etnico
"P1897", # satisfaccion con la salud (0-10)
"P754", # ruralidad
"FEX_C" # factor de expansion
)
df <- salud %>%
left_join(
composicion %>% dplyr::select(all_of(vars_compo)),
by = keys
)
cat("Base combinada: ", nrow(df), "filas x", ncol(df), "columnas\n")## Base combinada: 228688 filas x 163 columnas
df_clean <- df %>%
filter(
P6020 == 2,
P6100 %in% c(1, 3),
P6040 >= 18,
P6040 <= 90
) %>%
mutate(
salud_ord = factor(P6127,
levels = 1:4,
labels = c("Muy bueno", "Bueno", "Regular", "Malo"),
ordered = TRUE),
satisfaccion = ifelse(P1897 %in% c(99, NA), NA_real_, P1897),
regimen = factor(ifelse(P6100 == 1, "Contributivo", "Subsidiado"),
levels = c("Subsidiado", "Contributivo")),
regimen_bin = ifelse(P6100 == 1, 1, 0),
edad = P6040,
edad2 = (P6040 - mean(P6040, na.rm = TRUE))^2,
rural = factor(ifelse(P754 == 2, "Rural", "Urbano"),
levels = c("Urbano", "Rural")),
rural_bin = ifelse(P754 == 2, 1, 0),
afro = factor(ifelse(P6080 == 5, "Afrodescendiente", "No afrodescendiente"),
levels = c("No afrodescendiente", "Afrodescendiente")),
afro_bin = ifelse(P6080 == 5, 1, 0),
cronica = factor(ifelse(P1930 == 1, "Con cronica", "Sin cronica"),
levels = c("Sin cronica", "Con cronica")),
cronica_bin = ifelse(P1930 == 1, 1, 0),
barrera = ifelse(!is.na(P6153), 1, 0),
razon_barrera = case_when(
P6153 == 1 ~ "Caso leve",
P6153 == 3 ~ "Centro queda lejos",
P6153 == 4 ~ "Falta de dinero",
P6153 == 5 ~ "Mal servicio / cita lejana",
P6153 == 10 ~ "No autorizaron atencion",
P6153 == 11 ~ "Espera excesiva",
!is.na(P6153) ~ "Otra razon",
TRUE ~ NA_character_
),
gasto_medicamentos = ifelse(is.na(P3182S1), 0, P3182S1),
gasto_transporte = ifelse(is.na(P3186S1), 0, P3186S1),
gasto_total = gasto_medicamentos + gasto_transporte,
peso = FEX_C.x
)
cat("Muestra final:", nrow(df_clean), "\n")## Muestra final: 83983
## Contributivo: 24287
## Subsidiado: 59696
resumen <- df_clean %>%
group_by(regimen) %>%
summarise(
N = n(),
`Edad media` = round(mean(edad, na.rm = TRUE), 1),
`% Rural` = round(mean(rural_bin, na.rm = TRUE) * 100, 1),
`% Afrodescendiente` = round(mean(afro_bin, na.rm = TRUE) * 100, 1),
`% Con cronica` = round(mean(cronica_bin, na.rm = TRUE) * 100, 1),
`% Barrera acceso` = round(mean(barrera, na.rm = TRUE) * 100, 1),
`Satisfaccion media` = round(mean(satisfaccion, na.rm = TRUE), 2),
`Gasto bolsillo mediana` = round(median(gasto_total[gasto_total > 0],
na.rm = TRUE), 0)
)
resumen %>%
kable(caption = "Tabla 1. Estadisticas descriptivas por regimen — ECV 2024") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE) %>%
column_spec(1, bold = TRUE)| regimen | N | Edad media | % Rural | % Afrodescendiente | % Con cronica | % Barrera acceso | Satisfaccion media | Gasto bolsillo mediana |
|---|---|---|---|---|---|---|---|---|
| Subsidiado | 59696 | 44.8 | 39.1 | 9.5 | 17.6 | 1.3 | 7.77 | 30000 |
| Contributivo | 24287 | 46.5 | 19.9 | 4.5 | 18.2 | 1.1 | 8.13 | 25000 |
La variable de resultado es el estado de salud autopercibido con cuatro categorias ordenadas:
\[\text{Muy bueno} < \text{Bueno} < \text{Regular} < \text{Malo}\]
El Ordered Logit modela la probabilidad acumulada de estar en cada categoria o peor:
\[\log\left(\frac{P(Y \leq j)}{P(Y > j)}\right) = \alpha_j - \mathbf{X}\boldsymbol{\beta}\]
| Modelo | Variable dependiente | Correcto aqui? |
|---|---|---|
| MCO lineal | Continua | No — salud es ordinal |
| Logit binario | 0/1 | No — pierde informacion |
| Ordered Logit | Ordinal k categorias | Si |
| MCO | Escala 0-10 continua | Si — para satisfaccion |
# Modelo 0: solo regimen
m0 <- polr(salud_ord ~ regimen,
data = df_clean %>% filter(!is.na(salud_ord)),
Hess = TRUE, method = "logistic")
# Modelo 1: con controles
m1 <- polr(salud_ord ~ regimen + edad + edad2 + rural + afro + cronica,
data = df_clean %>% filter(!is.na(salud_ord), !is.na(rural)),
Hess = TRUE, method = "logistic")
# Modelo 2: con barrera de acceso
m2 <- polr(salud_ord ~ regimen + edad + edad2 + rural + afro + cronica + barrera,
data = df_clean %>% filter(!is.na(salud_ord), !is.na(rural)),
Hess = TRUE, method = "logistic")
# Modelo 3: MCO satisfaccion
m3 <- lm(satisfaccion ~ regimen + edad + edad2 + rural + afro + cronica,
data = df_clean, weights = peso)
cat("Modelos estimados correctamente.\n")## Modelos estimados correctamente.
modelsummary(
list("M0 Solo regimen" = m0,
"M1 Con controles" = m1,
"M2 Barrera acceso" = m2),
exponentiate = TRUE,
stars = TRUE,
title = "Tabla 2. Ordered Logit — Estado de salud autopercibido (OR)",
notes = "OR > 1: mayor probabilidad de peor salud. Fuente: DANE ECV 2024.",
coef_rename = c(
"regimenContributivo" = "Regimen contributivo",
"edad" = "Edad (anos)",
"edad2" = "Edad2 (centrada)",
"ruralRural" = "Zona rural",
"afroAfrodescendiente" = "Afrodescendiente",
"cronicaCon cronica" = "Enfermedad cronica",
"barrera" = "Barrera de acceso"
)
)| M0 Solo regimen | M1 Con controles | M2 Barrera acceso | |
|---|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | |||
| OR > 1: mayor probabilidad de peor salud. Fuente: DANE ECV 2024. | |||
| Muy bueno|Bueno | 0.128*** | 0.529*** | 0.540*** |
| (0.002) | (0.026) | (0.026) | |
| Bueno|Regular | 3.941*** | 21.814*** | 22.632*** |
| (0.038) | (1.220) | (1.272) | |
| Regular|Malo | 72.729*** | 574.287*** | 609.935*** |
| (2.272) | (49.182) | (52.613) | |
| Regimen contributivo | 0.742*** | 0.668*** | 0.674*** |
| (0.012) | (0.023) | (0.023) | |
| Edad (anos) | 1.032*** | 1.032*** | |
| (0.001) | (0.001) | ||
| Edad2 (centrada) | 1.000 | 1.000 | |
| (0.000) | (0.000) | ||
| Zona rural | 1.109** | 1.113** | |
| (0.039) | (0.039) | ||
| Afrodescendiente | 0.832* | 0.843* | |
| (0.064) | (0.065) | ||
| Enfermedad cronica | 5.296*** | 5.275*** | |
| (0.232) | (0.232) | ||
| Barrera de acceso | 3.516*** | ||
| (0.404) | |||
| Num.Obs. | 83983 | 17786 | 17786 |
| AIC | 147069.8 | 29379.6 | 29266.0 |
| BIC | 147107.1 | 29449.7 | 29343.9 |
| RMSE | 1.94 | 1.98 | 1.98 |
coefs <- coef(m1)
zetas <- m1$zeta
betas <- coefs[!names(coefs) %in% names(zetas)]
vcov_betas <- vcov(m1)[names(betas), names(betas)]
OR_tabla <- data.frame(
Variable = names(betas),
OR = round(exp(betas), 3),
IC_inf = round(exp(betas - 1.96 * sqrt(diag(vcov_betas))), 3),
IC_sup = round(exp(betas + 1.96 * sqrt(diag(vcov_betas))), 3),
row.names = NULL
)
OR_tabla %>%
kable(caption = "Tabla 3. Odds Ratios — Modelo M1 con IC 95%",
col.names = c("Variable", "OR", "IC 2.5%", "IC 97.5%")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
row_spec(which(OR_tabla$IC_inf > 1 | OR_tabla$IC_sup < 1),
bold = TRUE, color = "#D85A30")| Variable | OR | IC 2.5% | IC 97.5% |
|---|---|---|---|
| regimenContributivo | 0.668 | 0.624 | 0.715 |
| edad | 1.032 | 1.030 | 1.034 |
| edad2 | 1.000 | 1.000 | 1.000 |
| ruralRural | 1.109 | 1.036 | 1.187 |
| afroAfrodescendiente | 0.832 | 0.715 | 0.968 |
| cronicaCon cronica | 5.296 | 4.860 | 5.770 |
modelsummary(
list("M3 MCO Satisfaccion" = m3),
stars = TRUE,
title = "Tabla 4. MCO — Satisfaccion con la salud (escala 0-10)",
notes = "Coeficientes: cambio en puntos de satisfaccion. Fuente: DANE ECV 2024.",
coef_rename = c(
"regimenContributivo" = "Regimen contributivo",
"edad" = "Edad (anos)",
"edad2" = "Edad2 (centrada)",
"ruralRural" = "Zona rural",
"afroAfrodescendiente" = "Afrodescendiente",
"cronicaCon cronica" = "Enfermedad cronica"
)
)| M3 MCO Satisfaccion | |
|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | |
| Coeficientes: cambio en puntos de satisfaccion. Fuente: DANE ECV 2024. | |
| (Intercept) | 8.950*** |
| (0.039) | |
| Regimen contributivo | 0.513*** |
| (0.027) | |
| Edad (anos) | -0.023*** |
| (0.001) | |
| Edad2 (centrada) | -0.000 |
| (0.000) | |
| Zona rural | 0.056 |
| (0.038) | |
| Afrodescendiente | -0.389*** |
| (0.063) | |
| Enfermedad cronica | -1.280*** |
| (0.037) | |
| Num.Obs. | 17786 |
| R2 | 0.155 |
| R2 Adj. | 0.154 |
| AIC | 85165.4 |
| BIC | 85227.7 |
| Log.Lik. | -42574.688 |
| F | 542.549 |
| RMSE | 1.78 |
perfil_base <- data.frame(
regimen = factor(c("Subsidiado", "Contributivo"),
levels = c("Subsidiado", "Contributivo")),
edad = mean(df_clean$edad, na.rm = TRUE),
edad2 = mean(df_clean$edad2, na.rm = TRUE),
rural = factor("Urbano", levels = c("Urbano", "Rural")),
afro = factor("No afrodescendiente",
levels = c("No afrodescendiente", "Afrodescendiente")),
cronica = factor("Sin cronica", levels = c("Sin cronica", "Con cronica"))
)
probs <- MASS:::predict.polr(m1, newdata = perfil_base, type = "probs")
data.frame(
Regimen = c("Subsidiado", "Contributivo"),
Muy_bueno = round(probs[, 1] * 100, 1),
Bueno = round(probs[, 2] * 100, 1),
Regular = round(probs[, 3] * 100, 1),
Malo = round(probs[, 4] * 100, 1)
) %>%
kable(caption = "Tabla 5. Probabilidades predichas (%) por regimen",
col.names = c("Regimen", "Muy bueno", "Bueno", "Regular", "Malo")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
column_spec(1, bold = TRUE) %>%
add_header_above(c(" " = 1, "Estado de salud autopercibido (%)" = 4))| Regimen | Muy bueno | Bueno | Regular | Malo |
|---|---|---|---|---|
| Subsidiado | 11.3 | 72.7 | 15.3 | 0.7 |
| Contributivo | 16.0 | 72.7 | 10.8 | 0.5 |
df_clean %>%
filter(!is.na(salud_ord)) %>%
count(regimen, salud_ord) %>%
group_by(regimen) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = salud_ord, y = pct, fill = regimen)) +
geom_col(position = "dodge", width = 0.7) +
geom_text(aes(label = paste0(round(pct, 1), "%")),
position = position_dodge(width = 0.7),
vjust = -0.4, size = 3.2, fontface = "bold") +
scale_fill_manual(values = colores) +
scale_y_continuous(labels = label_percent(scale = 1), limits = c(0, 82)) +
labs(title = "Estado de salud autopercibido por regimen",
subtitle = "Mujeres mayores de 18 anos — ECV 2024",
x = NULL, y = "Porcentaje (%)", fill = "Regimen") +
tema_obsdf_clean %>%
filter(!is.na(satisfaccion)) %>%
ggplot(aes(x = regimen, y = satisfaccion, fill = regimen)) +
geom_violin(alpha = 0.35, color = NA) +
geom_boxplot(width = 0.2, outlier.shape = NA, color = "#333333") +
stat_summary(fun = mean, geom = "point",
shape = 23, size = 3.5, fill = "white", color = "#333333") +
scale_fill_manual(values = colores) +
scale_y_continuous(breaks = 0:10) +
labs(title = "Satisfaccion con la salud por regimen",
subtitle = "Escala 0-10 · Punto blanco = media",
x = NULL, y = "Satisfaccion (0-10)", fill = "Regimen") +
tema_obs + theme(legend.position = "none")df_clean %>%
filter(!is.na(satisfaccion), !is.na(rural)) %>%
group_by(regimen, rural) %>%
summarise(media = mean(satisfaccion, na.rm = TRUE), .groups = "drop") %>%
ggplot(aes(x = rural, y = media, fill = regimen)) +
geom_col(position = "dodge", width = 0.6) +
geom_text(aes(label = round(media, 2)),
position = position_dodge(width = 0.6),
vjust = -0.5, size = 4, fontface = "bold") +
scale_fill_manual(values = colores) +
scale_y_continuous(limits = c(0, 10.5)) +
labs(title = "Doble brecha: regimen x ruralidad",
subtitle = "Ser subsidiado Y rural es peor que cualquiera de los dos por separado",
x = NULL, y = "Satisfaccion media (0-10)", fill = "Regimen") +
tema_obsdf_clean %>%
filter(!is.na(razon_barrera)) %>%
count(regimen, razon_barrera) %>%
group_by(regimen) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = reorder(razon_barrera, pct), y = pct, fill = regimen)) +
geom_col(position = "dodge", width = 0.7) +
geom_text(aes(label = paste0(round(pct, 1), "%")),
position = position_dodge(width = 0.7),
hjust = -0.1, size = 3) +
coord_flip() +
scale_fill_manual(values = colores) +
scale_y_continuous(labels = label_percent(scale = 1), limits = c(0, 55)) +
labs(title = "Razones de no atencion medica por regimen",
subtitle = "Solo personas que reportaron no recibir atencion",
x = NULL, y = "Porcentaje (%)", fill = "Regimen") +
tema_obsor_df <- data.frame(
variable = names(betas),
OR = exp(betas),
lower = exp(betas - 1.96 * sqrt(diag(vcov_betas))),
upper = exp(betas + 1.96 * sqrt(diag(vcov_betas))),
row.names = NULL
) %>%
mutate(
etiqueta = recode(variable,
"regimenContributivo" = "Regimen contributivo",
"edad" = "Edad (anos)",
"edad2" = "Edad2",
"ruralRural" = "Zona rural",
"afroAfrodescendiente" = "Afrodescendiente",
"cronicaCon cronica" = "Enfermedad cronica"),
significativo = ifelse(lower > 1 | upper < 1,
"Significativo", "No significativo")
)
ggplot(or_df, aes(x = OR, y = reorder(etiqueta, OR), color = significativo)) +
geom_vline(xintercept = 1, linetype = "dashed",
color = "#888888", linewidth = 0.8) +
geom_errorbarh(aes(xmin = lower, xmax = upper), height = 0.3, linewidth = 1) +
geom_point(size = 4) +
scale_color_manual(values = c("Significativo" = "#D85A30",
"No significativo" = "#AAAAAA"),
name = NULL) +
scale_x_log10(breaks = c(0.5, 0.75, 1, 1.5, 2, 3, 5)) +
labs(title = "Forest plot — Ordered Logit (Modelo M1)",
subtitle = "OR > 1: mayor probabilidad de peor salud · Linea = sin efecto",
x = "Odds Ratio (escala log)", y = NULL) +
tema_obs + theme(legend.position = "bottom")df_clean %>%
filter(gasto_total > 0) %>%
ggplot(aes(x = gasto_total / 1000, fill = regimen)) +
geom_histogram(bins = 35, alpha = 0.75, position = "identity", color = NA) +
scale_x_continuous(
labels = label_dollar(prefix = "$", suffix = "K", big.mark = "."),
limits = c(0, 300)) +
scale_fill_manual(values = colores) +
facet_wrap(~regimen, ncol = 1, scales = "free_y") +
labs(title = "Gasto de bolsillo en salud (ultimos 30 dias)",
subtitle = "Medicamentos + transporte · Excluye ceros",
x = "Gasto de bolsillo (miles COP)", y = "Frecuencia") +
tema_obs + theme(legend.position = "none")| Modelo | OR Regimen Contributivo | Interpretacion |
|---|---|---|
| M0 Sin controles | 0.742 | Brecha cruda |
| M1 Con controles | 0.668 | Brecha neta |
| M2 Con barrera | 0.674 | Brecha controlando mecanismo |
\[\underbrace{\text{Regimen}}_{\beta_1} \rightarrow \underbrace{\text{Acceso}}_{\text{barrera}} \rightarrow \underbrace{\text{Estadio}}_{\beta_2} \rightarrow \underbrace{\text{Costo}}_{\text{Y}}\]
El siguiente paso es obtener los datos clinicos (RIPS + ADRES) para estimar:
\[Costos_i = \beta_0 + \beta_1 Regimen_i + \beta_2 Estadio_i + \beta_3 Ruralidad_i + \beta_4 Edad_i + \varepsilon_i\]
## Analisis: R 4 . 5.3
## Fecha: 24/07/2026
## Observatorio de Economia de la Salud — Uninorte, Barranquilla