Una entidad financiera desea clasificar empresas según su estado de liquidez utilizando información financiera. En este ejercicio se empleará una regresión logística binaria para estimar la probabilidad de que una empresa presente una buena liquidez.
Este análisis tiene como objetivo construir un modelo de regresión logística para predecir el estado de liquidez (Buena/Mala) de empresas comerciales en función de indicadores financieros y sectoriales. Se sigue:
Los datos incluyen información de 60 empresas, con las siguientes variables:
Estado_Liquidez: Variable respuesta binaria (Buena/Mala)
Sector: Comercio por menor o por mayor
Liquidez_corriente: Ratio de liquidez corriente
Liquidez_Inmediata: Ratio de liquidez inmediata
library(ggplot2)
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.5.3
##
## Adjuntando el paquete: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
Estado_Liquidez <- c('Buena', 'Buena', 'Mala', 'Mala', 'Buena',
'Mala', 'Buena', 'Mala', 'Buena', 'Buena',
'Buena', 'Mala', 'Buena', 'Buena', 'Buena',
'Buena', 'Buena', 'Buena', 'Mala', 'Buena',
'Mala', 'Buena', 'Mala', 'Buena', 'Mala',
'Mala', 'Mala', 'Buena', 'Buena', 'Buena',
'Mala', 'Buena', 'Buena', 'Buena', 'Mala',
'Mala', 'Buena', 'Mala', 'Buena', 'Buena',
'Buena', 'Mala', 'Mala', 'Mala', 'Mala',
'Buena', 'Buena', 'Buena', 'Buena', 'Mala',
'Buena', 'Buena', 'Mala', 'Buena', 'Mala',
'Mala', 'Buena', 'Mala', 'Mala', 'Mala')
Liquidez_corriente <- c(1.0358, 3.2875, 0.0002, 0.8437, 2.7978, 0.6157,
4.9147, 0.4726, 3.1383, 0.9312, 4.595, 0.2562,
3.4223, 4.093, 3.5588, 1.2303, 1.9699, 4.807,
0.8519, 1.2456, 0.2369, 4.5516, 0.0241, 3.7404,
0.1364, 0.6531, 0.826, 1.9248, 3.2492, 1.1111,
0.6016, 2.5071, 0.9356, 0.9142, 0.0904, 0.3628,
4.3654, 0.3341, 4.9013, 2.6205, 5.238, 0.5354,
0.7142, 0.4904, 0.5807, 3.1327, 1.8576, 5.1645,
4.17, 0.5712, 1.1563, 5.0202, 0.2783, 4.4937,
0.3096, 0.2283, 0.9292, 0.6686, 0.4791, 0.3898)
Liquidez_Inmediata <- c(0.592, 0.693, 0.083, 0.426, 0.633, 0.362, 0.952,
0.315, 0.66, 0.586, 0.905, 0.109, 0.708, 0.76,
0.712, 0.604, 0.621, 0.931, 0.453, 0.62, 0.107,
0.831, 0.089, 0.756, 0.096, 0.377, 0.413, 0.621,
0.687, 0.592, 0.354, 0.626, 0.588, 0.573, 0.095,
0.287, 0.776, 0.285, 0.952, 0.63, 0.998, 0.335,
0.397, 0.332, 0.347, 0.642, 0.62, 0.976, 0.775,
0.343, 0.596, 0.976, 0.175, 0.828, 0.253, 0.097,
0.579, 0.386, 0.328, 0.313)
Sector <- c('Comercio por menor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por menor',
'Comercio por menor', 'Comercio por menor', 'Comercio por menor',
'Comercio por menor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por mayor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por mayor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por mayor',
'Comercio por menor', 'Comercio por menor', 'Comercio por mayor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por mayor',
'Comercio por menor', 'Comercio por menor', 'Comercio por mayor',
'Comercio por menor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por mayor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por mayor', 'Comercio por mayor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por menor',
'Comercio por menor', 'Comercio por menor', 'Comercio por menor',
'Comercio por mayor', 'Comercio por menor', 'Comercio por menor')
# Crear factor y variable binaria
Estado_Liquidez_factor <- factor(Estado_Liquidez,
levels = c("Mala", "Buena"))
Estado_Liquidez_binaria <- as.numeric(Estado_Liquidez_factor) - 1
# Crear dataframe
Datos <- data.frame(
Estado_Liquidez,
Estado_Liquidez_factor,
Estado_Liquidez_binaria,
Sector = as.factor(Sector),
Liquidez_corriente,
Liquidez_Inmediata
)
# Mostrar estructura de los datos
kable(head(Datos, 10), caption = "Primeras 10 observaciones de los datos") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Estado_Liquidez | Estado_Liquidez_factor | Estado_Liquidez_binaria | Sector | Liquidez_corriente | Liquidez_Inmediata |
|---|---|---|---|---|---|
| Buena | Buena | 1 | Comercio por menor | 1.0358 | 0.592 |
| Buena | Buena | 1 | Comercio por menor | 3.2875 | 0.693 |
| Mala | Mala | 0 | Comercio por menor | 0.0002 | 0.083 |
| Mala | Mala | 0 | Comercio por mayor | 0.8437 | 0.426 |
| Buena | Buena | 1 | Comercio por menor | 2.7978 | 0.633 |
| Mala | Mala | 0 | Comercio por menor | 0.6157 | 0.362 |
| Buena | Buena | 1 | Comercio por menor | 4.9147 | 0.952 |
| Mala | Mala | 0 | Comercio por menor | 0.4726 | 0.315 |
| Buena | Buena | 1 | Comercio por menor | 3.1383 | 0.660 |
| Buena | Buena | 1 | Comercio por menor | 0.9312 | 0.586 |
# Tabla de frecuencias
table_liquidez <- table(Datos$Estado_Liquidez)
kable(table_liquidez, caption = "Frecuencia del Estado de Liquidez") %>%
kable_styling()
| Var1 | Freq |
|---|---|
| Buena | 34 |
| Mala | 26 |
# Gráfico de barras
ggplot(Datos, aes(x = Estado_Liquidez, fill = Estado_Liquidez)) +
geom_bar() +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5, size = 5) +
scale_fill_manual(values = c("Mala" = "darkred", "Buena" = "darkblue")) +
labs(
title = "Distribución del Estado de Liquidez",
x = "Estado de Liquidez",
y = "Frecuencia"
) +
theme_minimal() +
theme(legend.position = "none")
# Tabla de frecuencias
table_sector <- table(Datos$Sector)
kable(table_sector, caption = "Frecuencia por Sector") %>%
kable_styling()
| Var1 | Freq |
|---|---|
| Comercio por mayor | 28 |
| Comercio por menor | 32 |
# Gráfico de barras
ggplot(Datos, aes(x = Sector, fill = Sector)) +
geom_bar() +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5, size = 5) +
scale_fill_manual(values = c("Comercio por mayor" = "lightseagreen",
"Comercio por menor" = "lightgoldenrod2")) +
labs(
title = "Distribución de Empresas por Sector",
x = "Sector",
y = "Frecuencia"
) +
theme_minimal() +
theme(legend.position = "none")
# Histogramas y boxplots para Liquidez Corriente
p1 <- ggplot(Datos, aes(x = Liquidez_corriente)) +
geom_histogram(fill = "deepskyblue1", bins = 15, color = "black", alpha = 0.7) +
labs(title = "Histograma de Liquidez Corriente", x = "Valor", y = "Frecuencia") +
theme_minimal()
p2 <- ggplot(Datos, aes(y = Liquidez_corriente)) +
geom_boxplot(fill = "deepskyblue1", color = "black") +
labs(title = "Boxplot de Liquidez Corriente", y = "Valor") +
theme_minimal()
# Histogramas y boxplots para Liquidez Inmediata
p3 <- ggplot(Datos, aes(x = Liquidez_Inmediata)) +
geom_histogram(fill = "powderblue", bins = 15, color = "black", alpha = 0.7) +
labs(title = "Histograma de Liquidez Inmediata", x = "Valor", y = "Frecuencia") +
theme_minimal()
p4 <- ggplot(Datos, aes(y = Liquidez_Inmediata)) +
geom_boxplot(fill = "powderblue", color = "black") +
labs(title = "Boxplot de Liquidez Inmediata", y = "Valor") +
theme_minimal()
# Combinar gráficos
library(gridExtra)
##
## Adjuntando el paquete: 'gridExtra'
## The following object is masked from 'package:dplyr':
##
## combine
grid.arrange(p1, p2, p3, p4, ncol = 2)
# Estadísticas por estado de liquidez - Liquidez Corriente
stats_corriente <- Datos %>%
group_by(Estado_Liquidez_factor) %>%
summarise(
n = n(),
Min = round(min(Liquidez_corriente), 3),
Q1 = round(quantile(Liquidez_corriente, 0.25), 3),
Mediana = round(median(Liquidez_corriente), 3),
Media = round(mean(Liquidez_corriente), 3),
Q3 = round(quantile(Liquidez_corriente, 0.75), 3),
Max = round(max(Liquidez_corriente), 3),
SD = round(sd(Liquidez_corriente), 3)
)
kable(stats_corriente,
caption = "Estadísticas Descriptivas - Liquidez Corriente por Estado") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Estado_Liquidez_factor | n | Min | Q1 | Mediana | Media | Q3 | Max | SD |
|---|---|---|---|---|---|---|---|---|
| Mala | 26 | 0.000 | 0.262 | 0.476 | 0.444 | 0.612 | 0.852 | 0.247 |
| Buena | 34 | 0.914 | 1.399 | 3.194 | 3.030 | 4.462 | 5.238 | 1.510 |
# Estadísticas por estado de liquidez - Liquidez Inmediata
stats_inmediata <- Datos %>%
group_by(Estado_Liquidez_factor) %>%
summarise(
n = n(),
Min = round(min(Liquidez_Inmediata), 3),
Q1 = round(quantile(Liquidez_Inmediata, 0.25), 3),
Mediana = round(median(Liquidez_Inmediata), 3),
Media = round(mean(Liquidez_Inmediata), 3),
Q3 = round(quantile(Liquidez_Inmediata, 0.75), 3),
Max = round(max(Liquidez_Inmediata), 3),
SD = round(sd(Liquidez_Inmediata), 3)
)
kable(stats_inmediata,
caption = "Estadísticas Descriptivas - Liquidez Inmediata por Estado") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Estado_Liquidez_factor | n | Min | Q1 | Mediana | Media | Q3 | Max | SD |
|---|---|---|---|---|---|---|---|---|
| Mala | 26 | 0.083 | 0.126 | 0.322 | 0.275 | 0.360 | 0.453 | 0.124 |
| Buena | 34 | 0.573 | 0.620 | 0.673 | 0.724 | 0.815 | 0.998 | 0.139 |
# Boxplot de Liquidez Corriente por Estado
ggplot(Datos, aes(x = Estado_Liquidez_factor, y = Liquidez_corriente, fill = Estado_Liquidez_factor)) +
geom_boxplot() +
scale_fill_manual(values = c("Mala" = "#1C86EE", "Buena" = "lightgreen")) +
labs(
title = "Liquidez Corriente según Estado de Liquidez",
x = "Estado de Liquidez",
y = "Liquidez Corriente"
) +
theme_minimal() +
theme(legend.position = "none")
# Boxplot de Liquidez Inmediata por Estado
ggplot(Datos, aes(x = Estado_Liquidez_factor, y = Liquidez_Inmediata, fill = Estado_Liquidez_factor)) +
geom_boxplot() +
scale_fill_manual(values = c("Mala" = "lightblue", "Buena" = "lightgreen")) +
labs(
title = "Liquidez Inmediata según Estado de Liquidez",
x = "Estado de Liquidez",
y = "Liquidez Inmediata"
) +
theme_minimal() +
theme(legend.position = "none")
# Gráfico de dispersión con colores por estado
ggplot(Datos, aes(x = Liquidez_corriente, y = Liquidez_Inmediata,
color = Estado_Liquidez_factor)) +
geom_point(size = 3, alpha = 0.8) +
scale_color_manual(values = c("Mala" = "darkred", "Buena" = "darkblue")) +
labs(
title = "Relación entre Liquidez Corriente e Inmediata",
subtitle = "Coloreado por Estado de Liquidez",
x = "Liquidez Corriente",
y = "Liquidez Inmediata",
color = "Estado de Liquidez"
) +
theme_minimal()
# Tabla de contingencia
tabla_cruzada <- table(Datos$Estado_Liquidez_factor, Datos$Sector)
colnames(tabla_cruzada) <- c("Comercio por mayor", "Comercio por menor")
rownames(tabla_cruzada) <- c("Mala", "Buena")
kable(tabla_cruzada,
caption = "Tabla de Contingencia: Estado de Liquidez vs Sector") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Comercio por mayor | Comercio por menor | |
|---|---|---|
| Mala | 8 | 18 |
| Buena | 20 | 14 |
# Prueba Chi-cuadrado sin corrección de Yates
chi_test <- chisq.test(tabla_cruzada, correct = FALSE)
# Resultados en formato tabla
chi_results <- data.frame(
Estadístico = chi_test$statistic,
gl = chi_test$parameter,
p_valor = chi_test$p.value
)
kable(chi_results,
caption = "Resultados de la Prueba Chi-Cuadrado") %>%
kable_styling()
| Estadístico | gl | p_valor | |
|---|---|---|---|
| X-squared | 4.659018 | 1 | 0.0308909 |
Sin la corrección de Yates, el test muestra una relación estadísticamente significativa entre el sector y el estado de liquidez (El valor p = 0.0309 < 0.05). Esto refuerza la idea de que el tipo de comercio podría estar asociado con la situación de liquidez de las empresas.
# Extraer frecuencias
buena_menor <- tabla_cruzada["Buena", "Comercio por menor"]
mala_menor <- tabla_cruzada["Mala", "Comercio por menor"]
buena_mayor <- tabla_cruzada["Buena", "Comercio por mayor"]
mala_mayor <- tabla_cruzada["Mala", "Comercio por mayor"]
# Calcular odds
odds_menor <- mala_menor / buena_menor
odds_mayor <- mala_mayor / buena_mayor
odds_ratio <- odds_menor / odds_mayor
# Crear tabla de resultados
odds_data <- data.frame(
Sector = c("Comercio por menor", "Comercio por mayor"),
`Mala Liquidez` = c(mala_menor, mala_mayor),
`Buena Liquidez` = c(buena_menor, buena_mayor),
Odds = c(odds_menor, odds_mayor)
)
kable(odds_data,
caption = "Cálculo de Odds por Sector",
digits = 2) %>%
kable_styling()
| Sector | Mala.Liquidez | Buena.Liquidez | Odds |
|---|---|---|---|
| Comercio por menor | 18 | 14 | 1.29 |
| Comercio por mayor | 8 | 20 | 0.40 |
# Mostrar Odds Ratio
cat("Odds Ratio (Comercio por menor / Comercio por mayor):", round(odds_ratio, 2), "\n")
## Odds Ratio (Comercio por menor / Comercio por mayor): 3.21
# Modelo con todas las variables
modelo_completo <- glm(Estado_Liquidez_binaria ~
Liquidez_corriente +
Liquidez_Inmediata +
Sector,
data = Datos,
family = binomial(link = "logit"))
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
summary(modelo_completo)
##
## Call:
## glm(formula = Estado_Liquidez_binaria ~ Liquidez_corriente +
## Liquidez_Inmediata + Sector, family = binomial(link = "logit"),
## data = Datos)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -165.393 157881.195 -0.001 0.999
## Liquidez_corriente -8.186 35107.343 0.000 1.000
## Liquidez_Inmediata 333.232 353879.418 0.001 0.999
## SectorComercio por menor 20.850 120578.874 0.000 1.000
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 8.2108e+01 on 59 degrees of freedom
## Residual deviance: 2.1014e-09 on 56 degrees of freedom
## AIC: 8
##
## Number of Fisher Scoring iterations: 25
# Modelo solo con Liquidez Corriente
modelo_x1 <- glm(Estado_Liquidez_binaria ~ Liquidez_corriente,
data = Datos,
family = binomial(link = "logit"))
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Modelo solo con Liquidez Inmediata
modelo_x2 <- glm(Estado_Liquidez_binaria ~ Liquidez_Inmediata,
data = Datos,
family = binomial(link = "logit"))
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Modelo solo con Sector
modelo_x3 <- glm(Estado_Liquidez_binaria ~ Sector,
data = Datos,
family = binomial(link = "logit"))
# Comparar modelos usando AIC
modelos <- list(
"Liquidez Corriente" = modelo_x1,
"Liquidez Inmediata" = modelo_x2,
"Sector" = modelo_x3
)
comparacion <- data.frame(
Modelo = names(modelos),
AIC = sapply(modelos, AIC)
)
kable(comparacion,
caption = "Comparación de Modelos por AIC",
digits = 2) %>%
kable_styling()
| Modelo | AIC | |
|---|---|---|
| Liquidez Corriente | Liquidez Corriente | 4.00 |
| Liquidez Inmediata | Liquidez Inmediata | 4.00 |
| Sector | Sector | 81.36 |
# Modelo seleccionado (por Sector)
modelo_seleccionado <- modelo_x3
# Resumen del modelo
summary(modelo_seleccionado)
##
## Call:
## glm(formula = Estado_Liquidez_binaria ~ Sector, family = binomial(link = "logit"),
## data = Datos)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.9163 0.4183 2.190 0.0285 *
## SectorComercio por menor -1.1676 0.5495 -2.125 0.0336 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 82.108 on 59 degrees of freedom
## Residual deviance: 77.363 on 58 degrees of freedom
## AIC: 81.363
##
## Number of Fisher Scoring iterations: 4
# Probabilidades estimadas por sector
nuevos_datos <- data.frame(Sector = unique(Datos$Sector))
nuevos_datos$Probabilidad_Buena <- predict(modelo_seleccionado,
newdata = nuevos_datos,
type = "response")
kable(nuevos_datos,
caption = "Probabilidad de Buena Liquidez por Sector",
digits = 4) %>%
kable_styling()
| Sector | Probabilidad_Buena |
|---|---|
| Comercio por menor | 0.4375 |
| Comercio por mayor | 0.7143 |
ggplot(nuevos_datos, aes(x = Sector, y = Probabilidad_Buena, fill = Sector)) +
geom_bar(stat = "identity", width = 0.5) +
geom_text(aes(label = paste0(round(Probabilidad_Buena * 100, 1), "%")),
vjust = -0.5, size = 5) +
scale_fill_manual(values = c("lightseagreen", "lightgoldenrod2")) +
labs(
title = "Probabilidad de Buena Liquidez por Sector",
subtitle = "Modelo de Regresión Logística",
y = "Probabilidad estimada",
x = "Sector"
) +
ylim(0, 1) +
theme_minimal() +
theme(legend.position = "none")
Interpretación:
El 71.4% es el promedio de las probabilidades individuales de todas las empresas del sector “Comercio por mayor”.
El 43.7% es el promedio de las probabilidades individuales de todas las empresas del sector “Comercio por menor”.
# Modelo seleccionado
modelo_seleccionado <- glm(Estado_Liquidez_binaria ~ Sector,
data = Datos,
family = binomial(link = "logit"))
# Predecir probabilidades
Datos$Probabilidad <- predict(modelo_seleccionado, type = "response")
# Clasificar (umbral 0.5)
Datos$Prediccion <- ifelse(Datos$Probabilidad >= 0.5, 1, 0)
# Crear matriz de confusión
tabla_confusion <- table(Datos$Prediccion, Datos$Estado_Liquidez_binaria)
colnames(tabla_confusion) <- c("Real_Mala", "Real_Buena")
rownames(tabla_confusion) <- c("Pred_Mala", "Pred_Buena")
print(tabla_confusion)
##
## Real_Mala Real_Buena
## Pred_Mala 18 14
## Pred_Buena 8 20
# Calcular métricas
TP <- tabla_confusion["Pred_Buena", "Real_Buena"]
TN <- tabla_confusion["Pred_Mala", "Real_Mala"]
FP <- tabla_confusion["Pred_Buena", "Real_Mala"]
FN <- tabla_confusion["Pred_Mala", "Real_Buena"]
# Exactitud (Accuracy)
accuracy <- (TP + TN) / sum(tabla_confusion)
# Sensibilidad (Recall) - Tasa de verdaderos positivos
sensibilidad <- TP / (TP + FN)
# Especificidad - Tasa de verdaderos negativos
especificidad <- TN / (TN + FP)
# Precisión (Precision)
precision <- TP / (TP + FP)
# F1-Score
f1_score <- 2 * (precision * sensibilidad) / (precision + sensibilidad)
# Mostrar resultados
cat("=== MÉTRICAS DE DESEMPEÑO ===\n")
## === MÉTRICAS DE DESEMPEÑO ===
cat("Exactitud (Accuracy):", round(accuracy * 100, 2), "%\n")
## Exactitud (Accuracy): 63.33 %
cat("Sensibilidad (Recall):", round(sensibilidad * 100, 2), "%\n")
## Sensibilidad (Recall): 58.82 %
cat("Especificidad:", round(especificidad * 100, 2), "%\n")
## Especificidad: 69.23 %
cat("Precisión (Precision):", round(precision * 100, 2), "%\n")
## Precisión (Precision): 71.43 %
cat("F1-Score:", round(f1_score, 4), "\n")
## F1-Score: 0.6452
Relación entre variables: El análisis de la tabla de contingencia sugiere una posible asociación entre el sector y el estado de liquidez.
Modelo seleccionado: El modelo basado únicamente en el sector presenta un ajuste aceptable con un AIC de r round(AIC(modelo_x3), 2).
Probabilidades estimadas:
Comercio por mayor: r round(nuevos_datos$Probabilidad_Buena[1] * 100, 1)% de probabilidad de buena liquidez
Comercio por menor: r round(nuevos_datos$Probabilidad_Buena[2] * 100, 1)% de probabilidad de buena liquidez