Este documento presenta un análisis exploratorio de datos (EDA) del conjunto de datos “BD SFC.csv”, que contiene información detallada sobre las carteras de microcréditos y otros rubros, de diversas entidades financieras vigiladas en Colombia.
Primero cargamos las librerías necesarias para nuestro análisis.
## [1] ".GlobalEnv" "package:stats" "package:graphics"
## [4] "package:grDevices" "package:utils" "package:datasets"
## [7] "package:methods" "Autoloads" "package:base"
library(tidyverse)
library(lubridate)
# Visualización
library(ggplot2)
library(scales)
library(RColorBrewer)
library(leaflet) # Para mapas
library(DT) # Para tablas interactivas
# Análisis espacial
library(sf)
#modelado
library(prophet)
library(plotly)
library(forecast)
library(lubridate)
library(tseries)
library(yardstick)Cargamos el dataset y examinamos su estructura.
# Carga de datos - ajusta la ruta según sea necesario
SFC_data <- read_csv("BD SFC.csv", locale = locale(encoding = "latin1")) # O "UTF-8" si es diferente
# Vista preliminar de los datos
head(SFC_data)## # A tibble: 6 × 11
## TIPO_ENTIDAD NOMBRE_TIPO_ENTIDAD CODIGO_ENTIDAD NOMBRE_ENTIDAD FECHA_CORTE
## <dbl> <chr> <dbl> <chr> <chr>
## 1 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 31/07/2024
## 2 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 30/06/2024
## 3 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 31/05/2024
## 4 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 30/04/2024
## 5 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 31/03/2024
## 6 1 ESTABLECIMIENTOS BANCA… 1 BANCO DE BOGO… 29/02/2024
## # ℹ 6 more variables: CUENTA <dbl>, NOMBRE_CUENTA <chr>, MONEDA <dbl>,
## # `NOMBRE MONEDA` <chr>, SIGNO_VALOR <chr>, VALOR <dbl>
## Rows: 16,286,348
## Columns: 11
## $ TIPO_ENTIDAD <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ NOMBRE_TIPO_ENTIDAD <chr> "ESTABLECIMIENTOS BANCARIOS", "ESTABLECIMIENTOS BA…
## $ CODIGO_ENTIDAD <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ NOMBRE_ENTIDAD <chr> "BANCO DE BOGOTA S.A.", "BANCO DE BOGOTA S.A.", "B…
## $ FECHA_CORTE <chr> "31/07/2024", "30/06/2024", "31/05/2024", "30/04/2…
## $ CUENTA <dbl> 1e+05, 1e+05, 1e+05, 1e+05, 1e+05, 1e+05, 1e+05, 1…
## $ NOMBRE_CUENTA <chr> "ACTIVO", "ACTIVO", "ACTIVO", "ACTIVO", "ACTIVO", …
## $ MONEDA <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ `NOMBRE MONEDA` <chr> "Total", "Total", "Total", "Total", "Total", "Tota…
## $ SIGNO_VALOR <chr> "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", …
## $ VALOR <dbl> 1.248434e+14, 1.237484e+14, 1.211837e+14, 1.205890…
## TIPO_ENTIDAD NOMBRE_TIPO_ENTIDAD CODIGO_ENTIDAD NOMBRE_ENTIDAD
## Min. : 1.00 Length:16286348 Min. : 1.00 Length:16286348
## 1st Qu.: 1.00 Class :character 1st Qu.: 6.00 Class :character
## Median : 11.00 Mode :character Median : 20.00 Mode :character
## Mean : 40.92 Mean : 29.64
## 3rd Qu.: 22.00 3rd Qu.: 46.00
## Max. :509.00 Max. :161.00
## FECHA_CORTE CUENTA NOMBRE_CUENTA MONEDA
## Length:16286348 Min. :100000 Length:16286348 Min. :0.000
## Class :character 1st Qu.:190000 Class :character 1st Qu.:0.000
## Mode :character Median :410238 Mode :character Median :1.000
## Mean :387658 Mean :0.637
## 3rd Qu.:515000 3rd Qu.:1.000
## Max. :840500 Max. :2.000
## NOMBRE MONEDA SIGNO_VALOR VALOR
## Length:16286348 Length:16286348 Min. :-7.485e+13
## Class :character Class :character 1st Qu.: 2.072e+07
## Mode :character Mode :character Median : 5.023e+08
## Mean : 1.247e+12
## 3rd Qu.: 8.635e+09
## Max. : 8.998e+15
Antes de profundizar en el análisis, vamos a filtrar, limpiar y transformar los datos.
# Filtrar los datos para el microcrédito (cuentas 141200-141299) y moneda = 0
SFC_filtrada <- subset(SFC_data, CUENTA >= 141200 & CUENTA <= 141299 & MONEDA == 0)
# Eliminar columnas innecesarias
columnas_a_eliminar <- c("TIPO_ENTIDAD", "NOMBRE_TIPO_ENTIDAD", "CODIGO_ENTIDAD", "MONEDA", "NOMBRE.MONEDA", "SIGNO_VALOR")
SFC_filtrada <- SFC_filtrada[, !(names(SFC_filtrada) %in% columnas_a_eliminar)]
# Convertir 'FECHA_CORTE' y establecer como índice (usando rownames)
SFC_filtrada$ID <- 1:nrow(SFC_filtrada)
rownames(SFC_filtrada) <- paste(SFC_filtrada$FECHA_CORTE, SFC_filtrada$ID, sep = "_")
# Asegurarse de que la columna 'VALOR' sea numérica
SFC_filtrada$VALOR <- as.numeric(as.character(SFC_filtrada$VALOR))
SFC_filtrada <- SFC_filtrada[!is.na(SFC_filtrada$VALOR), ]
# Asegurarse de que la columna 'FECHA_CORTE' sea de tipo Date
SFC_filtrada$FECHA_CORTE <- as.Date(SFC_filtrada$FECHA_CORTE, format = "%d/%m/%Y") # Ajusta el formato si es diferente
# Agrupar por fecha para obtener el total mensual de microcréditos
# Suponemos que 'VALOR' es el valor de microcréditos en un periodo específico.
data_series <- aggregate(VALOR ~ format(FECHA_CORTE, "%Y-%m"), SFC_filtrada, sum)
# Convertir la columna de fechas a Date y establecer como índice
data_series$FECHA_CORTE <- as.Date(paste0(data_series$`format(FECHA_CORTE, "%Y-%m")`, "-01"))
rownames(data_series) <- data_series$FECHA_CORTE
summary(SFC_filtrada)## NOMBRE_ENTIDAD FECHA_CORTE CUENTA NOMBRE_CUENTA
## Length:13243 Min. :2016-01-31 Min. :141200 Length:13243
## Class :character 1st Qu.:2017-12-31 1st Qu.:141205 Class :character
## Mode :character Median :2020-01-31 Median :141210 Mode :character
## Mean :2020-03-08 Mean :141212
## 3rd Qu.:2022-04-30 3rd Qu.:141220
## Max. :2024-07-31 Max. :141225
## NOMBRE MONEDA VALOR ID
## Length:13243 Min. :0.000e+00 Min. : 1
## Class :character 1st Qu.:6.073e+08 1st Qu.: 3312
## Mode :character Median :7.337e+09 Median : 6622
## Mean :2.166e+11 Mean : 6622
## 3rd Qu.:5.066e+10 3rd Qu.: 9932
## Max. :9.888e+12 Max. :13243
## Rows: 13,243
## Columns: 7
## $ NOMBRE_ENTIDAD <chr> "BANCO DE BOGOTA S.A.", "BANCO DE BOGOTA S.A.", "BANCO…
## $ FECHA_CORTE <date> 2024-07-31, 2024-06-30, 2024-05-31, 2024-04-30, 2024-…
## $ CUENTA <dbl> 141200, 141200, 141200, 141200, 141200, 141200, 141200…
## $ NOMBRE_CUENTA <chr> "CARTERA Y LEASING DE MICROCREDITOS", "CARTERA Y LEASI…
## $ `NOMBRE MONEDA` <chr> "Total", "Total", "Total", "Total", "Total", "Total", …
## $ VALOR <dbl> 268529045267, 266499058848, 264661426330, 261591646321…
## $ ID <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,…
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000e+00 6.073e+08 7.337e+09 2.166e+11 5.066e+10 9.888e+12
# Histograma de VALOR
ggplot(SFC_filtrada, aes(x = VALOR)) +
geom_histogram(fill = "lightblue", color = "black") +
labs(title = "Histograma de VALOR", x = "Valor", y = "Frecuencia")# Boxplot de VALOR
ggplot(SFC_filtrada, aes(y = VALOR)) +
geom_boxplot(fill = "lightgreen", color = "black") +
labs(title = "Boxplot de VALOR", y = "Valor") +
theme_minimal()# Gráfico de series de tiempo de VALOR agregado por mes
ggplot(data_series, aes(x = FECHA_CORTE, y = VALOR)) +
geom_line(color = "red") +
labs(title = "Valor total de microcréditos por mes", x = "Fecha", y = "Valor") +
theme_minimal()# Tabla de frecuencia de CUENTA
cuenta_freq <- as.data.frame(table(SFC_filtrada$CUENTA))
# Calcular porcentajes
cuenta_freq$Porcentaje <- cuenta_freq$Freq / sum(cuenta_freq$Freq) * 100
# Gráfico de barras de frecuencia de CUENTA
ggplot(cuenta_freq, aes(x = Var1, y = Freq)) +
geom_bar(stat = "identity", fill = "skyblue", color = "black") +
labs(title = "Frecuencia de CUENTA", x = "CUENTA", y = "Frecuencia") +
theme_minimal()# Diagrama de sectores de CUENTA con porcentajes
if (nrow(cuenta_freq) <= 10) { # Solo si hay pocas categorías
ggplot(cuenta_freq, aes(x = "", y = Freq, fill = Var1)) +
geom_bar(stat = "identity", width = 1) +
coord_polar("y", start = 0) +
labs(title = "Diagrama de sectores de CUENTA", fill = "CUENTA") +
geom_text(aes(label = paste0(round(Porcentaje, 1), "%")), position = position_stack(vjust = 0.5)) +
theme_void()
}# Cantidad de registros por NOMBRE_ENTIDAD
registros_entidad <- SFC_filtrada %>%
group_by(NOMBRE_ENTIDAD) %>%
summarise(Cantidad = n())
# Cantidad de registros por NOMBRE_ENTIDAD
registros_entidad <- SFC_filtrada %>%
group_by(NOMBRE_ENTIDAD) %>%
summarise(Cantidad = n()) %>%
arrange(desc(Cantidad)) %>% # Ordenar por cantidad descendente
head(30)
# Cantidad de registros por NOMBRE_ENTIDAD
registros_entidad <- SFC_filtrada %>%
group_by(NOMBRE_ENTIDAD) %>%
summarise(Cantidad = n()) %>%
arrange(desc(Cantidad)) %>% # Ordenar por cantidad descendente
head(30)
# Gráfico de barras de cantidad de registros por NOMBRE_ENTIDAD (primeras 30)
ggplot(registros_entidad, aes(x = reorder(NOMBRE_ENTIDAD, Cantidad), y = Cantidad)) +
geom_bar(stat = "identity", fill = "orange", color = "black") +
labs(title = "Cantidad de registros por NOMBRE_ENTIDAD (primeras 30 entidades de Microcredito)", x = "NOMBRE_ENTIDAD", y = "Cantidad de registros") +
theme_minimal() +
geom_text(aes(label = Cantidad), hjust = 1.2) + # Agregar etiquetas de texto dentro de las barras
coord_flip() # Para hacer el gráfico horizontal# Gráfico de la serie temporal completa
p <- ggplot(data_series, aes(x = FECHA_CORTE, y = VALOR)) +
geom_line(color = "blue") +
# *** Adapt these 'subset' calls to your actual data! ***
# Example: If you have a column 'EVENT_TYPE' with values "Promotion" and "Holiday"
# geom_point(data = subset(SFC_filtrada, EVENT_TYPE == "Promotion"),
# aes(color = "Promoción"), size = 2) +
# geom_point(data = subset(SFC_filtrada, EVENT_TYPE == "Holiday"),
# aes(color = "Día Festivo"), size = 2) +
# *** End of adaptation ***
labs(title = "Serie Temporal de VALOR TOTAL MICROCREDITOS",
subtitle = "Con indicación de eventos (adaptar)", # Modify subtitle
x = "FECHA_CORTE",
y = "VALOR",
color = "Eventos") + theme_minimal() +
theme(legend.position = "bottom")
ggplotly(p)SFC_filtrada$Month <- month(SFC_filtrada$FECHA_CORTE, label = TRUE, abbr = FALSE)
SFC_filtrada$Year <- year(SFC_filtrada$FECHA_CORTE)
patron_mensual <- SFC_filtrada %>%
group_by(Month, Year) %>%
summarize(ValorMensual = sum(VALOR), .groups = "drop")
ggplot(patron_mensual, aes(x = Month, y = ValorMensual, group = Year, color = as.factor(Year))) +
geom_line() +
geom_point(size = 2) +
labs(title = "Patrón Mensual de VALOR TOTAL MICROCREDITOS",
x = "Mes",
y = "Valor Mensual",
color = "Año") +
theme_minimal() +
theme(legend.position = "bottom") -> seasonal_plot
print(seasonal_plot)# Convertir a objeto ts para descomposiciC3n
ts_valor <- ts(SFC_filtrada$VALOR, frequency = 365)
# Attempt STL decomposition, fallback to decompose if it fails
descomposicion <-stl(ts_valor, s.window = "periodic")
# Visualizar la descomposiciC3n
autoplot(descomposicion) +
labs(title = "Descomposición STL de VALOR") +
theme_minimal() ###6.4 Pruebas de estacionariedad
##Antes de modelar, verificamos si nuestra serie temporal es estacionaria.
# Test de Dickey-Fuller Aumentado
adf_test <- adf.test(ts_valor)
print("ADF Test:")## [1] "ADF Test:"
##
## Augmented Dickey-Fuller Test
##
## data: ts_valor
## Dickey-Fuller = -6.7883, Lag order = 23, p-value = 0.01
## alternative hypothesis: stationary
## [1] "KPSS Test:"
##
## KPSS Test for Level Stationarity
##
## data: ts_valor
## KPSS Level = 2.9527, Truncation lag parameter = 13, p-value = 0.01
# 4. If Not Stationary, Difference
# - We difference the *time series object*, not the original column
if (adf_test$p.value > 0.05 || kpss_test$p.value < 0.05) {
ts_valor_diff <- diff(ts_valor)
# Check if differencing produced a valid time series
if(length(ts_valor_diff) > 0){
# Visualizar la serie diferenciada
ggplot(data.frame(x = 1:length(ts_valor_diff), y = ts_valor_diff), aes(x = x, y = y)) +
geom_line(color = "#0072B2") +
labs(title = "Serie Temporal de VALOR Diferenciada",
x = "Index", # Use "Index" since it's the difference order
y = "Diferencia en VALOR") +
theme_minimal() -> diff_plot
print(diff_plot)
# Test ADF en la serie diferenciada
adf_test_diff <- adf.test(ts_valor_diff)
print("ADF Test on Differenced Series:")
print(adf_test_diff)
} else {
print("Differencing resulted in an empty time series.")
}
} else {
print("Series is stationary.")
}## [1] "ADF Test on Differenced Series:"
##
## Augmented Dickey-Fuller Test
##
## data: ts_valor_diff
## Dickey-Fuller = -25.534, Lag order = 23, p-value = 0.01
## alternative hypothesis: stationary
# Primero, asegúrate de que tus datos estén ordenados por fecha
SFC_filtrada_monthly <- SFC_filtrada %>%
group_by(FECHA_CORTE = floor_date(FECHA_CORTE, "day")) %>% # Agrupa por día (puedes cambiar a "month" si lo prefieres)
summarise(VALOR = sum(VALOR, na.rm = TRUE)) %>%
ungroup() %>%
arrange(FECHA_CORTE) # Asegúrate de que esté ordenado por fecha después de la agrupación
# Ahora, utiliza SFC_filtrada_monthly para el resto del análisis
# Dividir los datos en entrenamiento y prueba
punto_corte <- floor(0.8 * nrow(SFC_filtrada_monthly))
SFC_filtrada_train <- SFC_filtrada_monthly[1:punto_corte, ]
SFC_filtrada_test <- SFC_filtrada_monthly[(punto_corte + 1):nrow(SFC_filtrada_monthly), ]
ggplot() +
geom_line(data = SFC_filtrada_train, aes(x = FECHA_CORTE, y = VALOR, color = "Entrenamiento"), size = 1) +
geom_line(data = SFC_filtrada_test, aes(x = FECHA_CORTE, y = VALOR, color = "Prueba"), size = 1) +
scale_x_date(labels = date_format("%Y-%m"),
date_breaks = "1 year") +
scale_color_manual(values = c("Entrenamiento" = "blue", "Prueba" = "red")) +
labs(title = "División de Datos: Entrenamiento y Prueba",
x = "Fecha de Corte",
y = "Valor",
color = "Conjunto") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))# Preparar los datos para Prophet
datos_prophet_train <- data.frame(
ds = SFC_filtrada_train$FECHA_CORTE,
y = SFC_filtrada_train$VALOR
)
# Ajustar modelo Prophet
modelo_prophet <- prophet()
modelo_prophet <- fit.prophet(modelo_prophet, datos_prophet_train)
# Preparar datos para predicción
datos_prophet_test <- data.frame(
ds = SFC_filtrada_test$FECHA_CORTE
)
# Generar predicciones
predicciones_prophet <- predict(modelo_prophet, datos_prophet_test)
# Visualizar predicciones
datos_pred <- bind_rows(
datos_prophet_train %>% select(ds, y) %>% mutate(tipo = "Datos históricos"),
data.frame(ds = datos_prophet_test$ds, y = SFC_filtrada_test$VALOR, tipo = "Datos reales"),
data.frame(ds = predicciones_prophet$ds, y = predicciones_prophet$yhat, tipo = "Predicción")
)
ggplot(datos_pred, aes(x = ds, y = y, color = tipo)) +
geom_line() +
scale_color_manual(values = c("Datos históricos" = "#0072B2",
"Datos reales" = "#009E73",
"Predicción" = "#D55E00")) +
labs(title = "Pronóstico con Prophet",
x = "Fecha",
y = "Valor",
color = "") +
theme_minimal() +
theme(legend.position = "bottom")# Evaluar rendimiento
if (nrow(SFC_filtrada_test) > 0) {
cat("Tipo de FECHA_CORTE en SFC_filtrada_test:", class(SFC_filtrada_test$FECHA_CORTE), "\n")
cat("Tipo de ds en predicciones_prophet:", class(predicciones_prophet$ds), "\n")
# Fusionar los valores reales del conjunto de prueba con las predicciones
predicciones_eval <- merge(SFC_filtrada_test %>% select(FECHA_CORTE, VALOR),
predicciones_prophet %>% select(ds, yhat),
by.x = "FECHA_CORTE",
by.y = "ds")
cat("Número de filas en predicciones_eval después de la fusión:", nrow(predicciones_eval), "\n")
# Verificar si hay datos coincidentes para evaluar
if (nrow(predicciones_eval) > 0) {
# Calcular MAPE (Error Porcentual Absoluto Medio)
mape_prophet <- mean(abs((predicciones_eval$VALOR - predicciones_eval$yhat) / predicciones_eval$VALOR), na.rm = TRUE) * 100
rmse_prophet <- sqrt(mean((predicciones_eval$VALOR - predicciones_eval$yhat)^2, na.rm = TRUE))
print(paste("Prophet MAPE:", round(mape_prophet, 2), "%"))
print(paste("Prophet RMSE:", round(rmse_prophet, 2)))
} else {
print("No hay datos coincidentes para evaluar el rendimiento.")
}
} else {
print("No hay datos en el conjunto de prueba para evaluar el rendimiento.")
}## Tipo de FECHA_CORTE en SFC_filtrada_test: Date
## Tipo de ds en predicciones_prophet: POSIXct POSIXt
## Número de filas en predicciones_eval después de la fusión: 0
## [1] "No hay datos coincidentes para evaluar el rendimiento."
Tendencia Histórica (Datos históricos - Línea azul): El gráfico muestra una tendencia general al alza en el valor de los microcréditos en Colombia desde aproximadamente 2016 hasta mediados de 2023. Se observan fluctuaciones y cierta volatilidad, pero la dirección predominante es de crecimiento. Se aprecian patrones estacionales o cíclicos, aunque no son tan pronunciados como la tendencia general.
Datos Reales (Datos reales - Línea verde): Esta línea representa los valores reales observados durante el período de prueba (posterior al conjunto de entrenamiento). Se superpone con la tendencia histórica y la predicción, permitiendo evaluar visualmente qué tan bien el modelo se ajusta a los datos no vistos.
Predicción (Predicción - Línea naranja): La línea naranja muestra la proyección del modelo Prophet para el futuro (más allá del último dato real conocido). La predicción parece continuar la tendencia alcista observada en los datos históricos recientes y en los datos reales del período de prueba.
Fuerte Tendencia de Crecimiento: Tanto los datos históricos como la predicción sugieren una tendencia continua de crecimiento en el valor total de los microcréditos en Colombia. Esto podría reflejar una mayor inclusión financiera, un aumento en la demanda de capital para pequeñas empresas y emprendimientos, o políticas gubernamentales que fomentan el acceso al crédito.
A pesar de que hay datos coincidentes entre los datos reales y la predicción no es posible medir el rendimiento del modelo, es probable que toque evaluar más a fondo un posible error en el codigo, en el manejo de los datos, el rango escogido, entre otros.
Pronóstico de Crecimiento Continuo: Basándonos en la predicción del modelo, se podría esperar que el valor total de los microcréditos en Colombia continúe aumentando en los próximos años (más allá de 2024 mostrado en el gráfico). La pendiente de la predicción parece similar a la tendencia observada en los últimos años.