Este artículo presenta un marco completo de Análisis Funcional de Datos (AFD) para modelar e interpretar trayectorias de densidad de cotizaciones a lo largo del ciclo de vida laboral. Se diseña una simulación Monte Carlo ampliada con 600 trayectorias (200 por perfil) para ilustrar: (i) la construcción de objetos funcionales mediante suavizado B‑spline; (ii) la extracción de modos dominantes a través del Análisis de Componentes Principales Funcionales (FPCA); (iii) la agrupación no supervisada de individuos en un espacio reducido, y (iv) métricas y visualizaciones diagnósticas. Cada paso se fundamenta matemáticamente y se acompaña de código R reproducible. El prototipo sirve de base metodológica para futuras aplicaciones a los datos administrativos del sistema de seguridad social paraguayo.
Palabras clave: Análisis Funcional de Datos; Densidad de Cotizaciones; B‑splines; FPCA; Clustering; Simulación.
La densidad de cotizaciones —la proporción de períodos (mensuales o anuales) en que un trabajador aporta efectivamente— es un indicador crítico para evaluar la elegibilidad a la pensión y la sostenibilidad de los sistemas previsionales. Los enfoques tradicionales estiman proporciones puntuales o trayectorias discretas (Ferrari and Cribari-Netto 2004; Simas, Barreto-Souza, and Rocha 2010; Elmer, Jones, and Nagin 2018), ignorando la naturaleza funcional de la carrera completa. En Paraguay, estudios recientes muestran densidades promedio inferiores al 30 % y fuertes brechas entre quintiles (Bai and Zelko 2022; Cañiza Zaldívar and Meza 2023).
Indicador | Evidencia clave | Fuente |
---|---|---|
Cobertura antes de la COVID‑19 | 24,5 % de ocupados | (Montt, Schmidlin, and Recalde 2021) |
Densidad media de aportes | 28 % | (Bai and Zelko 2022) |
Cotizantes regulares (2009‑2018) | 23 % | (Cañiza Zaldívar and Meza 2023) |
Cotizantes activos / jubilados (2024) | 726 972 / 54 993 | (“Boletín Estadístico de Seguridad Social 2024” 2024) |
Informalidad | > 60 % de ocupados | (Bai and Zelko 2022) |
Densidad quintil 1 vs quintil 5 | 10 % vs 45 % | (Bai and Zelko 2022) |
Sistema fragmentado | 8 cajas con reglas dispares | (Montt, Schmidlin, and Recalde 2021; Cañiza Zaldívar and Meza 2023) |
Los estudios citados emplean análisis discretos; ninguno aborda las trayectorias como objetos funcionales, lo que motiva la presente contribución.
Sea \(D\_{i,a}\in(0,1)\) la densidad para el individuo \(i\) a la edad \(a\). Se modela la trayectoria mediante una expansión B‑spline:
\[ f_i(a)=\sum_{k=1}^{K} c_{ik}\,\phi_k(a),\qquad a\in[20,64], \]
donde \({\phi\_k}\) son funciones base de orden 4. Los coeficientes \(c\_{ik}\) se obtienen minimizando
\[ \sum_{a}(D_{i,a}-f_i(a))^2 + \lambda\int_{20}^{64}[f_i''(a)]^2\,da. \]
El operador de covarianza $C$ genera el problema de autovalor
\[ \int_{20}^{64} C(a,a')\,\psi_m(a')\,da' = \rho_m\,\psi_m(a), \]
donde \(\psi\_m\) son armónicos funcionales y \(\rho\_m\) sus varianzas. Los scores se calculan como \(\xi\_{im}=\langle f\_i-\bar f,\psi\_m\rangle\).
Se aplica k-medias sobre \((\xi\_{i1},\xi\_{i2})\) para identificar perfiles latentes.
Se generaron tres perfiles \(\text{A},\text{B},\text{C}\) con 200 trayectorias cada uno (total \(N=600\)), empleando distribuciones Beta parametrizadas según la literatura (Schiltz 2009; Sanabria 2018).
ages <- 20:64
n_per_type <- 200
params <- tibble(Profile=c("A","B","C"),
Alpha =c(6,2,2),
Beta =c(2,6,2))
sim_traj <- function(a,b) matrix(rbeta(n_per_type*length(ages), a, b),
nrow=n_per_type, byrow=TRUE)
Y_mat <- rbind(sim_traj(6,2), sim_traj(2,6), sim_traj(2,2))
Y <- as_tibble(Y_mat) %>%
set_names(as.character(ages)) %>%
mutate(Profile = rep(params$Profile, each = n_per_type))
params %>% mutate(Media=round(Alpha/(Alpha+Beta),2)) %>%
knitr::kable(caption="Parámetros de simulación y medias teóricas")
Profile | Alpha | Beta | Media |
---|---|---|---|
A | 6 | 2 | 0.75 |
B | 2 | 6 | 0.25 |
C | 2 | 2 | 0.50 |
Y_long <- Y %>%
mutate(id=row_number()) %>%
pivot_longer(-c(Profile,id), names_to="Age", values_to="Density") %>%
mutate(Age = as.integer(Age))
mean_df <- Y_long %>%
group_by(Profile, Age) %>%
summarise(Density = mean(Density), .groups="drop")
ggplot() +
geom_line(data=Y_long,
aes(Age, Density, group=interaction(Profile,id), color=Profile),
alpha=0.1) +
geom_line(data=mean_df,
aes(Age, Density, color=Profile),
size=1.2) +
theme_minimal() +
labs(x="Edad", y="Densidad", title="Trayectorias simuladas con medias")
Trayectorias simuladas y curvas medias
basis <- create.bspline.basis(range(ages), nbasis=20, norder=4)
fdY <- Data2fd(argvals=ages,
y=t(as.matrix(select(Y,-Profile))),
basisobj=basis)
tribble(~Base,~Orden,~Nbasis,~Rango,
"B‑spline",4,20,"20–64") %>%
knitr::kable(caption="Especificaciones de la base B‑spline")
Base | Orden | Nbasis | Rango |
---|---|---|---|
B‑spline | 4 | 20 | 20–64 |
pca_out <- pca.fd(fdY, nharm=5, centerfns=TRUE)
var_df <- tibble(PC=paste0("PC",1:5),
Varianza=round(100*pca_out$varprop[1:5],1))
knitr::kable(var_df, caption="Varianza explicada por las primeras 5 PC")
PC | Varianza |
---|---|
PC1 | 77.1 |
PC2 | 1.9 |
PC3 | 1.6 |
PC4 | 1.6 |
PC5 | 1.6 |
plot(pca_out, harm=1:3, title="FPCA clásica: media y armónicos")
Media funcional y tres primeros armónicos
Media funcional y tres primeros armónicos
Media funcional y tres primeros armónicos
Ly <- split(as.vector(as.matrix(select(Y,-Profile))),
rep(1:nrow(Y), each=length(ages)))
Lt <- split(rep(ages, nrow(Y)),
rep(1:nrow(Y), each=length(ages)))
fpca_ir <- FPCA(Ly=Ly, Lt=Lt)
head(fpca_ir$xiEst) %>%
knitr::kable(caption="Scores FPCA (fdapace): primeras filas")
1.7163547 | 0.0411261 | -0.1865101 | -0.2301044 | 0.1897177 | -0.1208660 | 0.0755251 | 0.1315236 | 0.2764278 | 0.4282355 | 0.0282817 | -0.0835197 | 0.0602830 | 0.1967590 | 0.1051451 | 0.1080268 |
1.8739012 | -0.2487772 | 0.2311072 | -0.0051528 | -0.2009157 | -0.0017996 | 0.1314672 | 0.0152276 | 0.1999320 | 0.0336583 | -0.0931056 | 0.0147370 | 0.0521715 | -0.0704253 | -0.2723440 | 0.1987085 |
1.8353527 | 0.0050588 | 0.1092575 | -0.1990250 | -0.3659918 | 0.2621127 | -0.0232874 | 0.0366550 | -0.0851312 | -0.1853544 | -0.2941880 | 0.0552238 | -0.0040451 | 0.2008286 | 0.0509249 | -0.0246902 |
1.8268080 | 0.1611087 | 0.0340382 | -0.2475508 | -0.1429781 | 0.1497716 | 0.0000441 | 0.2768541 | 0.0136040 | 0.0953319 | -0.1638197 | -0.1030284 | 0.1870140 | 0.0577216 | 0.0278206 | 0.1413451 |
-0.2734408 | -1.3551735 | 0.3127402 | -0.4902280 | -0.0529330 | -0.2355899 | 0.1170904 | 0.0523433 | -0.1379548 | 0.0538305 | 0.0171026 | 0.1486815 | 0.1199583 | -0.0505976 | 0.1881767 | -0.2647483 |
-1.6067365 | 0.2051626 | 0.1864003 | 0.2306246 | 0.0598636 | 0.0518165 | -0.1948131 | -0.2494142 | -0.0054913 | -0.1899262 | 0.1733464 | -0.1825616 | -0.0731680 | 0.1053991 | -0.4120037 | 0.2060711 |
score_tbl <- tibble(PC1=pca_out$scores[,1], PC2=pca_out$scores[,2]) %>%
mutate(cluster=factor(kmeans(select(.,PC1,PC2), centers=3, nstart=50)$cluster))
centroids <- score_tbl %>% group_by(cluster) %>% summarise(across(c(PC1,PC2), mean))
knitr::kable(centroids, caption="Centroides por cluster en espacio PC")
cluster | PC1 | PC2 |
---|---|---|
1 | 1.6559913 | 0.0010221 |
2 | -1.6574574 | -0.0031700 |
3 | 0.0014661 | 0.0021479 |
ggplot(score_tbl, aes(PC1, PC2, color=cluster)) +
geom_point(alpha=0.6) +
theme_minimal() +
labs(title="Agrupamiento de scores FPCA")
Clusters en el plano PC1–PC2
El experimento confirma que el AFD:
Se ofrece un pipeline AFD reproducible y adaptable a grandes bases de datos previsionales. Próximos pasos:
Trabajo financiado por el programa doctoral de CONACYT.