Equipo: TAD 12 · Ficha 3314045
Fuente: DANE — GEIH
Período: 2010–2025
Fecha: 01 jul. 2026
Este informe analiza el mercado laboral colombiano utilizando los datos de la Encuesta Continua de Hogares (GEIH) del DANE. El dataset unificado integra ocho fuentes con distintas granularidades: cifras nacionales mensuales desagregadas por género, datos anuales de salario mínimo y fuerza de trabajo, indicadores de pobreza monetaria, y cifras de 37 ciudades del país entre 2021 y 2025.
El objetivo es construir una visión completa, con perspectiva de género y territorial, del comportamiento del empleo colombiano a lo largo de 15 años que incluyeron ciclos de crecimiento, una pandemia global y una recuperación desigual.
# ---- KPIs de descripción ----
n_gen <- nrow(df_gen)
n_smlv <- nrow(df_smlv)
n_pob <- nrow(df_pob)
n_ing <- nrow(df_ing)
n_total <- n_gen + n_smlv + n_pob + n_ing
384 registros.
Desagregación mensual F/M de los 4
indicadores laborales principales.
Serie 2010–2025.
16 registros anuales.
SMLV histórico, tasas
nacionales y población ocupada/desocupada.
14 registros anuales.
Tasa de pobreza nacional.
25 registros.
Distribución de asalariados
vs. independientes.
<span class="card-ico">📊</span>
<h4>Hoja Género</h4>
<p><strong>384</strong> registros. Desagregación mensual F/M de los 4 indicadores laborales principales. Serie 2010–2025.</p>
<span class="card-ico">💼</span>
<h4>Hoja SMLV & Fuerza de Trabajo</h4>
<p><strong>16</strong> registros anuales. SMLV histórico, tasas nacionales y población ocupada/desocupada 2010–2025.</p>
<span class="card-ico">💰</span>
<h4>Hoja Pobreza Monetaria</h4>
<p><strong>14</strong> registros anuales. Tasa de pobreza nacional y personas en pobreza 2012–2025.</p>
<span class="card-ico">🏙️</span>
<h4>Hoja Ingresos 5 Ciudades</h4>
<p><strong>25</strong> registros. Distribución asalariados vs. independientes en 5 ciudades vulnerables 2021–2025.</p>
# Análisis de nulos
nulos_gen <- sum(is.na(df_gen[, c("TGP","TO","TD","TS")]))
nulos_smlv <- sum(is.na(df_smlv[, c("TGP","TO","TD","Salario")]))
dup_gen <- sum(duplicated(df_gen))
dup_smlv <- sum(duplicated(df_smlv))
# Rango de valores
tgp_rng <- range(df_gen$TGP, na.rm = TRUE)
to_rng <- range(df_gen$TO, na.rm = TRUE)
td_rng <- range(df_gen$TD, na.rm = TRUE)
# Consistencia: TO siempre debe ser < TGP
inconsistente <- df_gen %>% filter(TO > TGP) %>% nrow()# Detección de outliers con IQR en TD
q1 <- quantile(df_gen$TD, 0.25, na.rm = TRUE)
q3 <- quantile(df_gen$TD, 0.75, na.rm = TRUE)
iqr <- q3 - q1
outliers_td <- df_gen %>% filter(TD < q1 - 1.5*iqr | TD > q3 + 1.5*iqr)
p_out <- ggplot(df_gen, aes(x = Género, y = TD * 100, fill = Género)) +
geom_boxplot(outlier.color = pal$gold, outlier.size = 2.5, alpha = 0.85, width = 0.5) +
scale_fill_manual(values = c("FEMENINO" = pal$gold, "MASCULINO" = pal$navy)) +
labs(
title = "Distribución de la Tasa de Desocupación por Género",
subtitle = paste0("Período 2010–2025 · Outliers detectados (IQR): ", nrow(outliers_td)),
x = NULL, y = "TD (%)",
caption = "Fuente: DANE — GEIH"
) +
theme_labor() +
theme(legend.position = "none")
ggplotly(p_out) %>%
plotly_layout("Distribución TD por Género",
paste0("Período 2010–2025 · Outliers IQR: ", nrow(outliers_td)))El boxplot es el estándar para comparar la distribución completa (mediana, dispersión y valores atípicos) de una variable continua entre dos grupos categóricos. Aquí se necesitaba ver no solo el promedio de la TD por género, sino su variabilidad y la presencia de outliers, algo que un gráfico de barras o de línea no puede mostrar.
La caja femenina se ubica más arriba y es más ancha que la masculina, evidenciando mayor nivel y mayor dispersión de desempleo en mujeres. Los puntos fuera de los bigotes corresponden en su mayoría a 2020, confirmando que la pandemia generó valores atípicos reales y no errores de captura.
Diagnóstico de calidad: El dataset presenta una calidad excelente. No se detectaron valores nulos en los indicadores principales ni registros duplicados. La única anomalía esperada son los valores de 2020, que representan un evento real (pandemia COVID-19) y no errores de datos. La tasa de desocupación femenina presenta mayor variabilidad que la masculina, lo que es coherente con la estructura del mercado laboral colombiano.
# Función para calcular estadísticas descriptivas completas
desc_stats <- function(x, nombre) {
x <- x[!is.na(x)] * 100
data.frame(
Variable = nombre,
N = length(x),
Media = round(mean(x), 2),
Mediana = round(median(x), 2),
Moda = round(as.numeric(names(sort(table(round(x,1)), decreasing=TRUE)[1])), 2),
SD = round(sd(x), 2),
CV = paste0(round(sd(x)/mean(x)*100, 1), "%"),
Mín = round(min(x), 2),
Máx = round(max(x), 2),
P25 = round(quantile(x, 0.25), 2),
P75 = round(quantile(x, 0.75), 2),
Asimetría = round(mean((x - mean(x))^3) / sd(x)^3, 3)
)
}
# Separar por género
tgp_f <- df_gen %>% filter(Género == "FEMENINO") %>% pull(TGP)
tgp_m <- df_gen %>% filter(Género == "MASCULINO") %>% pull(TGP)
to_f <- df_gen %>% filter(Género == "FEMENINO") %>% pull(TO)
to_m <- df_gen %>% filter(Género == "MASCULINO") %>% pull(TO)
td_f <- df_gen %>% filter(Género == "FEMENINO") %>% pull(TD)
td_m <- df_gen %>% filter(Género == "MASCULINO") %>% pull(TD)
stats_df <- bind_rows(
desc_stats(tgp_f, "TGP — Femenino"),
desc_stats(tgp_m, "TGP — Masculino"),
desc_stats(to_f, "TO — Femenino"),
desc_stats(to_m, "TO — Masculino"),
desc_stats(td_f, "TD — Femenino"),
desc_stats(td_m, "TD — Masculino")
)
reactable(
stats_df,
theme = reactableTheme(
headerStyle = list(background = "#0B2545", color = "white", fontSize = "0.75rem"),
cellStyle = list(fontSize = "0.83rem")
),
columns = list(
Variable = colDef(minWidth = 140),
Asimetría = colDef(cell = function(value) {
color <- if (abs(value) < 0.5) "#27AE60" else if (abs(value) < 1) "#C2790F" else "#C0392B"
div(value, style = list(color = color, fontWeight = "700"))
})
),
striped = TRUE, highlight = TRUE, searchable = TRUE, defaultPageSize = 6
)# Violin plots comparativos
df_long <- df_gen %>%
select(Género, TGP, TO, TD, TS) %>%
pivot_longer(cols = c(TGP, TO, TD, TS), names_to = "Indicador", values_to = "Valor") %>%
mutate(Valor = Valor * 100)
p_violin <- ggplot(df_long, aes(x = Indicador, y = Valor, fill = Género)) +
geom_violin(alpha = 0.7, position = position_dodge(width = 0.8), width = 0.8) +
geom_boxplot(alpha = 0.6, width = 0.2, position = position_dodge(width = 0.8),
outlier.size = 1.5, outlier.alpha = 0.5) +
scale_fill_manual(values = c("FEMENINO" = pal$gold, "MASCULINO" = pal$navy)) +
labs(title = "Distribución de Indicadores por Género (2010–2025)",
x = NULL, y = "Valor (%)", fill = "Género") +
theme_labor()
ggplotly(p_violin) %>%
plotly_layout("Distribución de Indicadores por Género", "Violín + Boxplot · 2010–2025")El violín agrega a la comparación de grupos la forma de la densidad (dónde se concentran los valores), algo que un boxplot solo no revela. Combinarlo con un boxplot interno mantiene los estadísticos de referencia (mediana, cuartiles) mientras se ven posibles distribuciones bimodales o asimétricas en los cuatro indicadores a la vez.
La TD femenina muestra una forma más ensanchada hacia valores altos (cola derecha), coherente con la asimetría positiva identificada más adelante. La TGP y la TO tienen violines más simétricos y compactos, indicando un comportamiento más estable a lo largo de la serie.
df_smlv_plot <- df_smlv %>% filter(!is.na(TO))
p_nac <- plot_ly(df_smlv_plot, x = ~Año) %>%
add_lines(y = ~TGP * 100, name = "TGP", line = list(color = pal$navy, width = 3)) %>%
add_lines(y = ~TO * 100, name = "TO", line = list(color = pal$gold, width = 3)) %>%
add_lines(y = ~TD * 100, name = "TD", line = list(color = pal$teal, width = 3, dash = "dash")) %>%
add_lines(y = ~TS * 100, name = "TS", line = list(color = pal$amber, width = 2, dash = "dot")) %>%
add_annotations(
x = 2020, y = 54, text = "🦠 COVID-19",
showarrow = TRUE, arrowcolor = pal$red,
font = list(size = 11, color = pal$red)
)
p_nac %>% plotly_layout(
"Evolución de los Cuatro Indicadores Laborales (2010–2025)",
"Promedios anuales nacionales · Fuente: DANE"
) %>%
layout(yaxis = list(title = "(%)", ticksuffix = "%"),
xaxis = list(title = "Año"))Con cuatro indicadores medidos año a año durante 15 años, la línea es el gráfico natural para mostrar tendencia y evolución continua en el tiempo. Usar cuatro líneas superpuestas (en vez de cuatro gráficos separados) permite comparar directamente su comportamiento relativo, incluyendo el quiebre puntual de 2020, señalado con una anotación.
TGP y TO se mueven prácticamente en paralelo, lo cual es consistente con su alta correlación. La TD (línea discontinua) muestra el pico más agudo en 2020 y una recuperación gradual, mientras que la TS se mantiene relativamente estable en niveles bajos durante todo el período.
# Ggplot2 version más controlada
df_hist <- df_gen %>%
select(Género, TGP, TO, TD, TS) %>%
pivot_longer(
-Género,
names_to = "Indicador",
values_to = "Valor"
) %>%
mutate(Valor = Valor * 100)
p_h <- ggplot(df_hist,
aes(x = Valor,
fill = Género,
color = Género)) +
geom_histogram(
bins = 25,
alpha = 0.7,
position = "identity"
) +
scale_fill_manual(values = c(
"FEMENINO" = pal$gold,
"MASCULINO" = pal$navy
)) +
scale_color_manual(values = c(
"FEMENINO" = "#8a6508",
"MASCULINO" = "#071A33"
)) +
facet_wrap(~Indicador, scales = "free", ncol = 2) +
labs(
title = "Histogramas de distribución por indicador y género",
x = "Valor (%)",
y = "Frecuencia"
) +
theme_labor() +
theme(
strip.background = element_rect(fill = pal$navy),
strip.text = element_text(color = "white",
face = "bold",
size = 10)
)
ggplotly(p_h) %>%
plotly_layout(
"Histogramas por Indicador y Género",
"Distribución de frecuencias · 2010–2025"
)
El histograma es el gráfico correcto para examinar cómo se
distribuyen los valores individuales de una variable continua:
si son simétricos, sesgados, uni o multimodales. Se usó
facet_wrap por indicador para no mezclar escalas distintas
(TGP, TO, TD y TS) en un mismo eje, y superposición por color para
comparar género dentro de cada panel.
Los histogramas de TD muestran una cola derecha más marcada en el género femenino, es decir, más meses con desempleo elevado. TGP y TO presentan formas más cercanas a la normal, con las distribuciones femenina y masculina desplazadas entre sí pero de forma similar, evidencia visual de la brecha estructural sin cruce de curvas.
meses_ord <- c("ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO",
"JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE")
estac <- df_gen %>%
group_by(Mes_n, Género) %>%
summarise(
TGP_m = mean(TGP, na.rm = TRUE) * 100,
TO_m = mean(TO, na.rm = TRUE) * 100,
TD_m = mean(TD, na.rm = TRUE) * 100,
.groups = "drop"
) %>%
filter(!is.na(Mes_n)) %>%
mutate(Mes = factor(Mes_n, labels = substr(meses_ord, 1, 3)))
p_est <- ggplot(estac, aes(x = Mes, y = TD_m, group = Género, color = Género)) +
geom_line(size = 1.3) +
geom_point(size = 2.5) +
scale_color_manual(values = c("FEMENINO" = pal$gold, "MASCULINO" = pal$navy)) +
labs(title = "Estacionalidad de la Tasa de Desocupación por Mes",
subtitle = "Promedio histórico por mes y género · 2010–2025",
x = "Mes", y = "TD promedio (%)", color = "Género") +
theme_labor()
ggplotly(p_est) %>%
plotly_layout("Patrón Estacional de la Tasa de Desocupación",
"Promedio por mes · 2010–2025")Cuando el eje X es una variable cíclica ordenada (los 12 meses del año) y se busca detectar un patrón repetitivo, la línea conectada por puntos es más legible que barras, porque resalta la forma de la curva (subidas y bajadas) en vez de comparar magnitudes aisladas.
Ambas líneas suben en enero-febrero y bajan hacia octubre-noviembre, confirmando un patrón estacional consistente en 15 años de datos. La línea femenina se mantiene sistemáticamente por encima de la masculina en todos los meses, mostrando que la brecha de género no es estacional sino estructural.
Patrón estacional confirmado: Enero y febrero son sistemáticamente los meses con mayor desempleo en todos los años de la serie. Los meses de octubre y noviembre presentan los valores más bajos. Este patrón se mantiene consistente en ambos géneros, aunque es más pronunciado en las mujeres.
df_scat <- df_gen %>%
mutate(TGP_p = TGP*100, TO_p = TO*100, TD_p = TD*100)
p_sc <- plot_ly(df_scat, x = ~TGP_p, y = ~TO_p,
color = ~Género,
colors = c("FEMENINO" = pal$gold, "MASCULINO" = pal$navy),
size = ~(TD_p),
text = ~paste0("Año: ", Año, "<br>Mes: ", Mes_n,
"<br>TGP: ", round(TGP_p,1), "%",
"<br>TO: ", round(TO_p,1), "%",
"<br>TD: ", round(TD_p,1), "%"),
hoverinfo = "text", type = "scatter", mode = "markers",
marker = list(opacity = 0.65, sizemode = "diameter")) %>%
plotly_layout("TGP vs TO — Relación por Género",
"Tamaño de burbuja proporcional a la TD · 2010–2025") %>%
layout(xaxis = list(title = "TGP (%)", ticksuffix = "%"),
yaxis = list(title = "TO (%)", ticksuffix = "%"))
p_scSe necesitaban mostrar simultáneamente tres variables numéricas (TGP, TO y TD) más el género: un scatter plot ubica dos de ellas en los ejes, y el tamaño de la burbuja (tercer canal visual) codifica la TD, evitando construir un gráfico separado solo para esa relación.
Se observa una relación lineal positiva clara entre TGP y TO en ambos géneros, con las burbujas femeninas más grandes en la zona de menor TO, es decir, mayor desempleo justo donde menos participan las mujeres del mercado laboral — un doble efecto negativo simultáneo.
# Correlaciones entre indicadores
df_cor <- df_gen %>%
select(TGP, TO, TD, TS) %>%
mutate(across(everything(), as.numeric)) %>%
filter(complete.cases(.))
cor_mat <- cor(df_cor * 100)
# Heatmap interactivo
cor_df <- as.data.frame(as.table(cor_mat)) %>%
rename(Var1 = Var1, Var2 = Var2, Correlación = Freq)
p_cor <- plot_ly(
z = cor_mat,
x = colnames(cor_mat),
y = colnames(cor_mat),
type = "heatmap",
colorscale = list(
list(0, pal$red),
list(0.5, "white"),
list(1, pal$navy)
),
zmid = 0, zmin = -1, zmax = 1,
text = matrix(round(cor_mat, 2), nrow = 4),
texttemplate = "%{text}",
hovertemplate = "<b>%{x}</b> vs <b>%{y}</b><br>r = %{z:.3f}<extra></extra>"
) %>%
plotly_layout("Matriz de Correlaciones — Indicadores Laborales",
"Pearson · Dataset completo 2010–2025")
p_corCuando se comparan todas las combinaciones posibles entre varias variables numéricas (4×4 en este caso), el heatmap es más eficiente que una serie de scatter plots: codifica la fuerza y dirección de cada correlación con color e intensidad, permitiendo detectar patrones de un vistazo en toda la matriz.
El bloque azul intenso entre TGP y TO confirma la correlación casi perfecta esperada conceptualmente. La TD aparece en tonos rojizos frente a TO, es decir, correlación negativa: a mayor ocupación, menor desempleo, tal como predice la teoría del mercado laboral.
# Ranking de correlaciones
cor_rank <- cor_df %>%
filter(as.character(Var1) < as.character(Var2)) %>%
mutate(
Fuerza = case_when(
abs(Correlación) >= 0.9 ~ "Muy alta",
abs(Correlación) >= 0.7 ~ "Alta",
abs(Correlación) >= 0.5 ~ "Moderada",
abs(Correlación) >= 0.3 ~ "Baja",
TRUE ~ "Muy baja"
),
Dirección = if_else(Correlación > 0, "Positiva ↑", "Negativa ↓")
) %>%
arrange(desc(abs(Correlación)))
reactable(
cor_rank,
theme = reactableTheme(
headerStyle = list(background = "#0B2545", color = "white")
),
columns = list(
Correlación = colDef(
format = colFormat(digits = 4),
cell = function(value) {
color <- if (value > 0.7) pal$navy else if (value > 0) pal$teal else pal$red
div(round(value, 4), style = list(color = color, fontWeight = "700"))
}
),
Fuerza = colDef(cell = function(v) span(v, style = list(background = "#0B254520", padding = "2px 8px", borderRadius = "10px"))),
Dirección = colDef(cell = function(v) span(v, style = list(color = if (grepl("↑", v)) pal$teal else pal$red, fontWeight = "700")))
),
striped = TRUE, highlight = TRUE
)Interpretación: La TGP y la TO presentan una correlación muy alta positiva (r > 0.95), lo esperado conceptualmente: a mayor participación laboral, mayor ocupación. La TD muestra correlación moderada negativa con la TO, confirmando que cuando aumenta el empleo efectivo disminuye el desempleo. La pandemia de 2020 introduce un punto de ruptura que intensifica estas relaciones.
# Brecha anual
brecha <- fem %>%
inner_join(mas, by = "Año", suffix = c("_F", "_M")) %>%
mutate(
Brecha_TGP = (TGP_M - TGP_F) * 100,
Brecha_TO = (TO_M - TO_F) * 100,
Brecha_TD = (TD_F - TD_M) * 100 # Desocupación: femenina supera
)
p_brecha <- plot_ly(brecha, x = ~Año) %>%
add_bars(y = ~Brecha_TO, name = "Brecha TO (pp)",
marker = list(color = pal$navy, opacity = 0.85)) %>%
add_lines(y = ~Brecha_TD, name = "Brecha TD (pp)",
line = list(color = pal$gold, width = 3, dash = "dot"),
yaxis = "y2") %>%
layout(
yaxis = list(title = "Brecha TO (pp)"),
yaxis2 = list(title = "Brecha TD (pp)", overlaying = "y", side = "right"),
barmode = "group"
) %>%
plotly_layout("Evolución de la Brecha de Género 2010–2025",
"Barras: diferencia en TO (M-F) · Línea: diferencia en TD (F-M)")
p_brechaSe combinan dos indicadores de brecha (TO y TD) que tienen magnitudes y significados distintos. Las barras destacan la evolución año a año de la brecha en TO (comparaciones discretas por período), mientras que la línea en un segundo eje resalta la tendencia continua de la brecha en TD, sin forzar ambas series a la misma escala.
Las barras de brecha en TO se mantienen persistentemente positivas durante toda la serie, sin ningún año de paridad. La línea de brecha en TD se dispara en los años de crisis (2020), mostrando que las mujeres absorben desproporcionadamente el impacto de los choques económicos.
# Evolución TGP, TO, TD por género
gen_plot <- anual_gen %>%
pivot_longer(cols = c(TGP, TO, TD), names_to = "Indicador", values_to = "Valor") %>%
mutate(Valor = Valor * 100)
p_gen <- ggplot(gen_plot, aes(x = Año, y = Valor, color = interaction(Indicador, Género),
linetype = Género, shape = Indicador)) +
geom_line(size = 1.3) +
geom_point(size = 2.5) +
scale_color_manual(values = c(
"TGP.FEMENINO" = pal$gold, "TGP.MASCULINO" = pal$navy,
"TO.FEMENINO" = "#D4A017", "TO.MASCULINO" = "#13335E",
"TD.FEMENINO" = pal$amber, "TD.MASCULINO" = pal$teal
)) +
scale_linetype_manual(values = c("FEMENINO" = "dashed", "MASCULINO" = "solid")) +
facet_wrap(~Indicador, scales = "free_y", ncol = 3) +
labs(title = "TGP, TO y TD por Género — Serie Anual 2010–2025",
x = "Año", y = "(%)", color = NULL, linetype = "Género") +
theme_labor() +
theme(strip.background = element_rect(fill = pal$navy),
strip.text = element_text(color = "white", face = "bold"))
ggplotly(p_gen) %>%
plotly_layout("Indicadores por Género 2010–2025", "Promedios anuales · DANE")
En vez de saturar un solo panel con seis líneas (3 indicadores × 2
géneros) de escalas distintas, se usó facet_wrap para
separar TGP, TO y TD en paneles independientes con eje Y libre,
manteniendo el color por indicador y el tipo de línea
(sólida/discontinua) por género para una comparación limpia dentro de
cada panel.
En los tres paneles la línea femenina (discontinua) se ubica de forma consistente por debajo (TGP, TO) o por encima (TD) de la masculina, sin que las curvas se crucen en ningún año, lo que confirma visualmente que la brecha de género es estructural y no coyuntural.
# KPIs de género último año
ult_fem <- fem %>% filter(Año == max(Año))
ult_mas <- mas %>% filter(Año == max(Año))
brecha_to_ult <- (ult_mas$TO - ult_fem$TO) * 100p_pob <- plot_ly(df_pob, x = ~Año) %>%
add_bars(y = ~Personas_mil, name = "Personas en pobreza (miles)",
marker = list(
color = ~ifelse(Año == 2020, pal$red, pal$navy),
opacity = 0.85
)) %>%
add_lines(y = ~TasaPobreza * 100, name = "Tasa de Pobreza (%)",
line = list(color = pal$gold, width = 3),
yaxis = "y2") %>%
layout(
yaxis = list(title = "Personas en pobreza (miles)"),
yaxis2 = list(title = "Tasa de Pobreza (%)", overlaying = "y", side = "right",
ticksuffix = "%")
) %>%
plotly_layout("Pobreza Monetaria Nacional 2012–2025",
"Barras: personas (miles) · Línea dorada: tasa de pobreza · Rojo: 2020 pandemia")
p_pobLas barras muestran el volumen absoluto de personas en pobreza (miles), mientras que la línea en eje secundario muestra la tasa relativa (%). Ambas lecturas son necesarias porque el tamaño de la población cambia con el tiempo: un volumen alto no siempre implica una tasa alta, y viceversa.
La barra de 2020 resaltada en rojo coincide con el pico tanto en volumen como en tasa, confirmando el impacto de la pandemia. Después de 2020 ambas series descienden de forma sostenida, señal de una recuperación consistente y no de un simple efecto de crecimiento poblacional.
pob_2020 <- df_pob %>% filter(Año == 2020)
pob_ult <- df_pob %>% filter(Año == max(Año))
pob_2012 <- df_pob %>% filter(Año == min(Año))p_smlv <- plot_ly(df_smlv %>% filter(!is.na(Salario)), x = ~Año) %>%
add_bars(y = ~Salario / 1000, name = "SMLV (COP miles)",
marker = list(color = pal$navy, opacity = 0.8)) %>%
add_lines(y = ~VarSMLV * 100, name = "Variación Anual (%)",
line = list(color = pal$gold, width = 3),
yaxis = "y2") %>%
layout(
yaxis = list(title = "SMLV (miles COP)", tickprefix = "$"),
yaxis2 = list(title = "Variación (%)", overlaying = "y", side = "right",
ticksuffix = "%")
) %>%
plotly_layout("Salario Mínimo y su Variación Anual 2010–2025",
"Barras: valor SMLV (miles COP) · Línea: % incremento anual")
p_smlvEl SMLV en pesos y su variación porcentual anual son magnitudes de naturaleza distinta (nivel acumulado vs. tasa de cambio). Las barras comunican el nivel del salario en cada año, mientras la línea en eje secundario aísla el ritmo de crecimiento, que es lo que suele generar debate sobre su efecto en el empleo.
Las barras crecen de forma sostenida y casi monotónica, mientras la línea de variación anual oscila sin una tendencia clara al alza o a la baja. Esto permite contrastar visualmente los años de mayor incremento porcentual contra la evolución de TGP/TO/TD para evaluar si hubo o no efectos adversos observables.
tbl_smlv <- df_smlv %>%
filter(!is.na(Salario)) %>%
select(Año, Salario, VarSMLV, TGP, TO, TD) %>%
mutate(
Salario = paste0("$", format(round(Salario), big.mark = ",")),
VarSMLV = paste0(round(VarSMLV * 100, 1), "%"),
TGP = paste0(round(TGP * 100, 1), "%"),
TO = paste0(round(TO * 100, 1), "%"),
TD = paste0(round(TD * 100, 1), "%")
) %>%
rename(
`SMLV` = Salario, `Var. SMLV` = VarSMLV,
`TGP` = TGP, `TO` = TO, `TD` = TD
)
reactable(
tbl_smlv,
theme = reactableTheme(
headerStyle = list(background = "#0B2545", color = "white", fontSize = "0.78rem")
),
striped = TRUE, highlight = TRUE, defaultPageSize = 16,
fullWidth = TRUE
)ing_2025 <- df_ing %>% filter(Año == 2025) %>% filter(!is.na(Ciudad))
p_ing <- plot_ly(ing_2025, x = ~Ciudad) %>%
add_bars(y = ~PctAsal * 100, name = "% Asalariados",
marker = list(color = pal$navy, opacity = 0.9)) %>%
add_bars(y = ~PctIndep * 100, name = "% Independientes",
marker = list(color = pal$gold, opacity = 0.9)) %>%
layout(barmode = "group",
yaxis = list(title = "%", ticksuffix = "%")) %>%
plotly_layout("Distribución Asalariados vs Independientes — 2025",
"5 ciudades con mayor informalidad laboral · Fuente: DANE")
p_ingCon una variable categórica (ciudad) en el eje X y dos porcentajes que se quieren comparar lado a lado (% asalariados vs. % independientes) para un mismo año, las barras agrupadas son la opción más clara: permiten comparar tanto entre ciudades como entre los dos tipos de empleo dentro de cada ciudad.
Las ciudades muestran proporciones distintas entre asalariados e independientes, evidenciando que la informalidad no es homogénea: algunas ciudades dependen mucho más del empleo por cuenta propia, lo que sugiere estructuras económicas locales diferentes.
p_tend_ing <- ggplot(df_ing %>% filter(!is.na(PctAsal)),
aes(x = Año, y = PctAsal * 100, color = Ciudad)) +
geom_line(size = 1.3) +
geom_point(size = 2.5) +
scale_color_viridis_d(option = "D", end = 0.9) +
labs(title = "Evolución del % Asalariados por Ciudad (2021–2025)",
subtitle = "Ciudades con mercados laborales más vulnerables",
x = "Año", y = "% Asalariados", color = "Ciudad") +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
theme_labor()
ggplotly(p_tend_ing) %>%
plotly_layout("Evolución del % Asalariados por Ciudad", "2021–2025")Con cinco ciudades observadas a lo largo de varios años, la línea permite seguir la trayectoria individual de cada una en el tiempo. Se usó una paleta viridis (colores distinguibles y accesibles) en vez de la paleta corporativa dorado/navy, porque aquí hay más de dos categorías y se necesita diferenciarlas claramente.
Las líneas no son paralelas: algunas ciudades ganan participación asalariada mientras otras la pierden, lo que indica que la tendencia hacia la formalización o informalización del empleo depende del contexto local y no responde a una dinámica nacional uniforme.
# Tabla interactiva completa
df_tabla <- df_gen %>%
select(Año, Mes_n, Género, TGP, TO, TD, TS) %>%
mutate(
Mes = factor(Mes_n, labels = substr(meses_ord, 1, 3)),
TGP = round(TGP * 100, 2),
TO = round(TO * 100, 2),
TD = round(TD * 100, 2),
TS = round(TS * 100, 2)
) %>%
select(-Mes_n) %>%
filter(!is.na(TGP)) %>%
arrange(Año, desc(Género))
reactable(
df_tabla,
theme = reactableTheme(
headerStyle = list(background = "#0B2545", color = "white", fontSize = "0.78rem"),
cellStyle = list(fontSize = "0.83rem")
),
columns = list(
Género = colDef(cell = function(v) {
bg <- if (v == "FEMENINO") pal$gold else pal$navy
span(v, style = list(
background = bg, color = "white",
padding = "2px 9px", borderRadius = "12px",
fontSize = "0.72rem", fontWeight = "700"
))
}),
TGP = colDef(format = colFormat(suffix = "%", digits = 2)),
TO = colDef(format = colFormat(suffix = "%", digits = 2)),
TD = colDef(
format = colFormat(suffix = "%", digits = 2),
style = function(value) {
if (!is.na(value) && value > 15) list(color = "#C0392B", fontWeight = "700")
else if (!is.na(value) && value > 12) list(color = "#C2790F", fontWeight = "600")
else list()
}
),
TS = colDef(format = colFormat(suffix = "%", digits = 2))
),
filterable = TRUE,
searchable = TRUE,
highlight = TRUE,
striped = TRUE,
defaultPageSize = 15,
defaultSorted = list(Año = "desc")
)# TO vs TD por género y año — bubbles
df_rel <- anual_gen %>%
mutate(TGP_p = TGP*100, TO_p = TO*100, TD_p = TD*100, TS_p = TS*100)
p_rel <- plot_ly(df_rel, x = ~TO_p, y = ~TD_p,
color = ~Género,
colors = c("FEMENINO" = pal$gold, "MASCULINO" = pal$navy),
size = ~TGP_p,
frame = ~Año,
text = ~paste0(Género, "<br>Año: ", Año,
"<br>TO: ", round(TO_p,1), "%",
"<br>TD: ", round(TD_p,1), "%",
"<br>TGP: ", round(TGP_p,1), "%"),
hoverinfo = "text",
type = "scatter", mode = "markers",
marker = list(opacity = 0.75, sizemode = "diameter")) %>%
animation_opts(frame = 600, easing = "linear", redraw = FALSE) %>%
animation_slider(currentvalue = list(prefix = "Año: ")) %>%
plotly_layout("Animación: TO vs TD por Género (2010–2025)",
"Tamaño proporcional a TGP · Usa el control para ver la evolución año por año") %>%
layout(xaxis = list(title = "Tasa de Ocupación (%)", ticksuffix = "%"),
yaxis = list(title = "Tasa de Desocupación (%)", ticksuffix = "%"))
p_rel
Aquí se cruzan cinco dimensiones a la vez: TO, TD, TGP (tamaño), género
(color) y año (tiempo). Un gráfico estático no podría mostrar la
evolución sin saturarse de puntos superpuestos; el control de animación
por año (frame) convierte el tiempo en una dimensión
navegable, dejando el scatter limpio en cada instante.
Al recorrer los años se observa cómo las burbujas femeninas y masculinas viajan juntas en la fase de crecimiento (2010–2019), se dispersan bruscamente en 2020 (TD sube, TO cae) y luego convergen de nuevo en la recuperación, sin llegar a cerrar completamente la brecha entre grupos.
# Relación SMLV vs TD
df_rel2 <- df_smlv %>% filter(!is.na(Salario), !is.na(TD))
p_r2 <- ggplot(df_rel2, aes(x = Salario / 1000, y = TD * 100, label = Año)) +
geom_point(size = 4, color = pal$navy, alpha = 0.8) +
geom_smooth(method = "loess", se = TRUE, color = pal$gold, fill = paste0(pal$gold, "30"), size = 1.3) +
geom_text(nudge_y = 0.3, size = 3, color = pal$muted, fontface = "bold") +
scale_x_continuous(labels = scales::dollar_format(prefix = "$", suffix = "k")) +
labs(title = "Relación entre SMLV y Tasa de Desocupación (2010–2025)",
subtitle = "Línea suavizada LOESS · Cada punto es un año",
x = "SMLV (miles COP)", y = "TD (%)") +
theme_labor()
ggplotly(p_r2) %>%
plotly_layout("SMLV vs Tasa de Desocupación", "Relación histórica 2010–2025")Para explorar si existe relación entre el nivel del salario mínimo y la tasa de desocupación, cada año se representa como un punto (scatter), y se ajusta una curva LOESS en vez de una recta de regresión porque no se asume de antemano que la relación sea lineal; la banda de confianza sombreada comunica la incertidumbre de esa curva.
La curva LOESS no muestra una pendiente ascendente sostenida entre SMLV y TD, lo que sugiere que, en la serie histórica analizada, los incrementos del salario mínimo no se han traducido en un aumento sistemático del desempleo, apoyando el hallazgo automático reportado más adelante.
# Calcular todos los insights automáticamente
brecha_media <- mean(brecha$Brecha_TO, na.rm = TRUE)
año_min_to_f <- fem$Año[which.min(fem$TO)]
min_to_f <- min(fem$TO, na.rm = TRUE) * 100
año_max_brecha <- brecha$Año[which.max(brecha$Brecha_TO)]
max_brecha <- max(brecha$Brecha_TO, na.rm = TRUE)
caida_covid_f <- (fem$TO[fem$Año==2019] - fem$TO[fem$Año==2020]) * 100
caida_covid_m <- (mas$TO[mas$Año==2019] - mas$TO[mas$Año==2020]) * 100
aumento_smlv_max <- df_smlv$Año[which.max(df_smlv$VarSMLV)]
var_max_smlv <- max(df_smlv$VarSMLV, na.rm = TRUE) * 100
td_f_mean <- mean(td_f * 100, na.rm = TRUE)
td_m_mean <- mean(td_m * 100, na.rm = TRUE)
pob_reduccion <- (pob_2020$TasaPobreza - pob_ult$TasaPobreza) * 100
# Asimetría de TD femenina
skew_td_f <- mean((td_f*100 - mean(td_f*100, na.rm=TRUE))^3, na.rm=TRUE) /
sd(td_f*100, na.rm=TRUE)^3
tipo_asim <- if_else(skew_td_f > 0.5, "asimétrica positiva (cola hacia valores altos)",
if_else(skew_td_f < -0.5, "asimétrica negativa (cola hacia valores bajos)",
"aproximadamente simétrica"))La tasa de ocupación masculina superó a la femenina en promedio 25.4 puntos porcentuales durante 2010–2025. La mayor brecha se registró en 2021 con 27.2 pp.
En 2020 la TO femenina cayó 7.6 pp mientras la masculina cayó 6.9 pp. Las mujeres absorbieron un golpe 11% mayor que los hombres.
La tasa de ocupación femenina más baja de toda la serie fue 38.1%, registrada en 2020 como consecuencia directa de la pandemia.
El mayor incremento del salario mínimo fue en 2023 con un 16% de aumento. Dicho año no generó efectos adversos observables sobre la tasa de ocupación.
La TD media femenina fue 14% vs 8.5% masculina a lo largo de todo el período. La distribución de la TD femenina es asimétrica positiva (cola hacia valores altos).
Desde el pico pandémico de 2020 (43.1%), la tasa de pobreza se redujo 15.1 pp hasta alcanzar 28% en 2025, el mínimo histórico de la serie.
tbl_anual <- df_smlv %>%
filter(!is.na(TGP)) %>%
mutate(
`TGP (%)` = round(TGP * 100, 2),
`TO (%)` = round(TO * 100, 2),
`TD (%)` = round(TD * 100, 2),
`TS (%)` = round(TS * 100, 2),
`SMLV (COP)` = Salario,
`Var. SMLV` = paste0(round(VarSMLV * 100, 1), "%")
) %>%
select(Año, `TGP (%)`, `TO (%)`, `TD (%)`, `TS (%)`, `SMLV (COP)`, `Var. SMLV`)
DT::datatable(
tbl_anual,
extensions = c("Buttons", "Scroller"),
options = list(
dom = "Bfrtip",
buttons = list("excel", "csv", "pdf"),
scrollY = "350px",
scroller = TRUE,
pageLength = 16
),
rownames = FALSE,
class = "cell-border stripe hover"
) %>%
DT::formatCurrency("SMLV (COP)", currency = "$", digits = 0, mark = ",") %>%
DT::formatStyle(
"TD (%)",
backgroundColor = DT::styleInterval(c(10, 13),
values = c("rgba(15,107,92,0.1)", "rgba(194,121,15,0.15)", "rgba(192,57,43,0.15)"))
)tbl_ing <- df_ing %>%
mutate(
`% Asalariados` = scales::percent(PctAsal, accuracy = 0.1),
`% Independientes` = scales::percent(PctIndep, accuracy = 0.1)
) %>%
select(
Ciudad,
Año,
PobOcup,
Asal,
Indep,
`% Asalariados`,
`% Independientes`
)
DT::datatable(
tbl_ing,
colnames = c(
"Ciudad",
"Año",
"Pob. Ocupada (k)",
"Asalariados (k)",
"Independientes (k)",
"% Asalariados",
"% Independientes"
),
extensions = "Buttons",
options = list(
dom = "Bfrtip",
buttons = c("excel", "csv"),
pageLength = 10
),
rownames = FALSE,
class = "cell-border stripe hover"
)