13/12/2025# Instalación de librerías
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("scales")) install.packages("scales")
if (!require("e1071")) install.packages("e1071")
if (!require("knitr")) install.packages("knitr")
library(ggplot2)
library(scales)
library(e1071)
library(knitr)
# setwd("C:/Users/ronal/OneDrive/Desktop") # Ajusta según tu necesidad
# Carga de datos
datos <- read.csv("database (1).csv", header = TRUE, sep = ",", dec = ".")
# Extracción y limpieza
All.Costs <- na.omit(datos$All.Costs)
All.Costs <- All.Costs[All.Costs > 0]
xmin <- min(All.Costs)
xmax <- max(All.Costs)
R <- xmax - xmin
K <- floor(1 + 3.3 * log10(length(All.Costs)))
A <- R / K
# Límites y Marcas de Clase
Li <- round(seq(from = xmin, to = xmax - A, by = A), 2)
Ls <- round(seq(from = xmin + A, to = xmax, by = A), 2)
MC <- round((Li + Ls) / 2)
# Frecuencias
ni <- numeric(K)
for (i in 1:(K-1)) {
ni[i] <- sum(All.Costs >= Li[i] & All.Costs < Ls[i])
}
ni[K] <- sum(All.Costs >= Li[K] & All.Costs <= xmax)
hi <- ni / sum(ni) * 100
Ni_asc <- cumsum(ni)
Ni_desc <- rev(cumsum(rev(ni)))
Hi_asc <- cumsum(hi)
Hi_desc <- rev(cumsum(rev(hi)))
TDF <- data.frame(Li, Ls, MC, ni, hi_porc = round(hi, 2), Ni_asc, Ni_desc,
Hi_asc_porc = round(Hi_asc, 2), Hi_desc_porc = round(Hi_desc, 2))
knitr::kable(TDF, caption = "Tabla de Distribución de Frecuencias de All.Costs")
| Li | Ls | MC | ni | hi_porc | Ni_asc | Ni_desc | Hi_asc_porc | Hi_desc_porc |
|---|---|---|---|---|---|---|---|---|
| 1 | 70043844 | 35021923 | 2757 | 99.86 | 2757 | 2761 | 99.86 | 100.00 |
| 70043844 | 140087687 | 105065766 | 2 | 0.07 | 2759 | 4 | 99.93 | 0.14 |
| 140087687 | 210131530 | 175109609 | 1 | 0.04 | 2760 | 2 | 99.96 | 0.07 |
| 210131530 | 280175373 | 245153452 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 280175373 | 350219216 | 315197295 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 350219216 | 420263060 | 385241138 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 420263060 | 490306903 | 455284981 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 490306903 | 560350746 | 525328824 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 560350746 | 630394589 | 595372667 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 630394589 | 700438432 | 665416510 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 700438432 | 770482275 | 735460353 | 0 | 0.00 | 2760 | 1 | 99.96 | 0.04 |
| 770482275 | 840526118 | 805504196 | 1 | 0.04 | 2761 | 1 | 100.00 | 0.04 |
umbral_90 <- quantile(All.Costs, 0.90)
datos_zoom <- All.Costs[All.Costs <= umbral_90]
n_z <- length(datos_zoom)
xmin_z <- min(datos_zoom)
xmax_z <- max(datos_zoom)
K_z <- floor(1 + 3.322 * log10(n_z))
A_z <- (xmax_z - xmin_z) / K_z
cortes_z <- seq(xmin_z, xmin_z + (K_z * A_z), by = A_z)
Li_z <- cortes_z[1:K_z]
Ls_z <- cortes_z[2:(K_z + 1)]
MC_z <- (Li_z + Ls_z) / 2
ni_z <- numeric(K_z)
for (i in 1:(K_z - 1)) {
ni_z[i] <- sum(datos_zoom >= Li_z[i] & datos_zoom < Ls_z[i])
}
ni_z[K_z] <- sum(datos_zoom >= Li_z[K_z] & datos_zoom <= xmax_z)
hi_z <- (ni_z / n_z) * 100
Ni_asc_z <- cumsum(ni_z)
Ni_desc_z <- rev(cumsum(rev(ni_z)))
Hi_asc_z <- cumsum(hi_z)
Hi_desc_z <- rev(cumsum(rev(hi_z)))
TDF_final_zoom <- data.frame(
Li = round(Li_z, 2), Ls = round(Ls_z, 2), MC = round(MC_z, 2),
ni = ni_z, hi_porc = round(hi_z, 2), Ni_asc = Ni_asc_z,
Ni_desc = Ni_desc_z, Hi_asc_porc = round(Hi_asc_z, 2), Hi_desc_porc = round(Hi_desc_z, 2)
)
kable(TDF_final_zoom, caption = "Tabla de Distribución de Frecuencias (90%)")
| Li | Ls | MC | ni | hi_porc | Ni_asc | Ni_desc | Hi_asc_porc | Hi_desc_porc |
|---|---|---|---|---|---|---|---|---|
| 1.00 | 43809.25 | 21905.12 | 1695 | 68.21 | 1695 | 2485 | 68.21 | 100.00 |
| 43809.25 | 87617.50 | 65713.38 | 286 | 11.51 | 1981 | 790 | 79.72 | 31.79 |
| 87617.50 | 131425.75 | 109521.62 | 120 | 4.83 | 2101 | 504 | 84.55 | 20.28 |
| 131425.75 | 175234.00 | 153329.88 | 103 | 4.14 | 2204 | 384 | 88.69 | 15.45 |
| 175234.00 | 219042.25 | 197138.12 | 68 | 2.74 | 2272 | 281 | 91.43 | 11.31 |
| 219042.25 | 262850.50 | 240946.38 | 50 | 2.01 | 2322 | 213 | 93.44 | 8.57 |
| 262850.50 | 306658.75 | 284754.62 | 44 | 1.77 | 2366 | 163 | 95.21 | 6.56 |
| 306658.75 | 350467.00 | 328562.88 | 23 | 0.93 | 2389 | 119 | 96.14 | 4.79 |
| 350467.00 | 394275.25 | 372371.12 | 31 | 1.25 | 2420 | 96 | 97.38 | 3.86 |
| 394275.25 | 438083.50 | 416179.38 | 24 | 0.97 | 2444 | 65 | 98.35 | 2.62 |
| 438083.50 | 481891.75 | 459987.62 | 15 | 0.60 | 2459 | 41 | 98.95 | 1.65 |
| 481891.75 | 525700.00 | 503795.88 | 26 | 1.05 | 2485 | 26 | 100.00 | 1.05 |
#Gráfica No. 1: Histograma de Frecuencia Absoluta
options(scipen = 999) # Desactiva notación científica
hist(datos_zoom,
main = "Gráfica No. 1: Frecuencia de Costos Totales",
ylab = "Cantidad",
xlab = "Costos Totales",
col = "grey",
breaks = K_z,
ylim = c(0, n_z),
las = 2)
#Gráfica No. 2: Histograma Local de Costos Totales
options(scipen = 999)
hist(datos_zoom,
main = "Gráfica No. 2:Costos Totales",
xlab = "Costos Totales",
ylab = "Cantidad",
col = "grey",
breaks = K_z,
las = 2)
# Gráfica Global. Porcentaje de Costos Totales
p_hi_global <- ggplot(TDF, aes(x = MC, y = hi_porc)) +
geom_col(fill = "gray70", color = "black", alpha = 0.7, width = A) +
scale_x_continuous(labels = comma) +
scale_y_continuous(labels = function(x) paste0(x, "%"), limits = c(0, 100)) +
labs(title = "Gráfica N. 3 : Porcentaje de Costos Totales", x = "Costos Totales", y = "Frecuencia Relativa (%)") +
theme_minimal() +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
axis.line = element_line(color = "black"))
print(p_hi_global)
# Gráfica Local. Distribucion de Costos Totales
p_hi_local <- ggplot(TDF_final_zoom, aes(x = MC, y = hi_porc)) +
geom_col(fill = "gray", color = "black", alpha = 0.8, width = A_z) +
scale_x_continuous(breaks = TDF_final_zoom$MC, labels = comma) +
scale_y_continuous(labels = function(x) paste0(x, "%"), expand = expansion(mult = c(0, 0.05))) +
labs(title = "Gráfica N. 4: Distribución de los Costos Totales", x = "Costos Totales", y = "Frecuencia Relativa (%)") +
theme_classic() +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.text.x = element_text(angle = 45, hjust = 1))
print(p_hi_local)
# Ojivas Ni
eje_x <- c(TDF_final_zoom$Li[1], TDF_final_zoom$Ls)
ni_asc_alineado <- c(0, TDF_final_zoom$Ni_asc)
ni_desc_ajustado <- c(TDF_final_zoom$Ni_asc[nrow(TDF_final_zoom)], TDF_final_zoom$Ni_desc)
df_ni_final <- data.frame(x = eje_x, y_asc = ni_asc_alineado, y_desc = ni_desc_ajustado)
ggplot(df_ni_final) +
geom_line(aes(x = x, y = y_asc, color = "Ascendente"), linewidth = 1) +
geom_point(aes(x = x, y = y_asc, color = "Ascendente"), size = 2) +
geom_line(aes(x = x, y = y_desc, color = "Descendente"), linewidth = 1, linetype = "dashed") +
geom_point(aes(x = x, y = y_desc, color = "Descendente"), size = 2) +
scale_x_continuous(labels = comma) + scale_y_continuous(labels = comma) +
scale_color_manual(values = c("Ascendente" = "black", "Descendente" = "grey")) +
labs(title = "Gráfica 5: Ojivas de Frecuencia Absoluta", x = "Costos Totales", y = "Cantidad (Ni)") +
theme_bw()
# Ojivas Hi
hi_asc_alineado <- c(0, TDF_final_zoom$Hi_asc_porc)
hi_desc_alineado <- c(100, TDF_final_zoom$Hi_desc_porc)
df_hi_final <- data.frame(x = eje_x, y_asc = hi_asc_alineado, y_desc = hi_desc_alineado)
ggplot(df_hi_final) +
geom_line(aes(x = x, y = y_asc, color = "Ascendente"), linewidth = 1.2) +
geom_point(aes(x = x, y = y_asc, color = "Ascendente"), size = 2) +
geom_line(aes(x = x, y = y_desc, color = "Descendente"), linewidth = 1.2, linetype = "dashed") +
geom_point(aes(x = x, y = y_desc, color = "Descendente"), size = 2) +
scale_x_continuous(labels = comma) +
scale_y_continuous(labels = function(x) paste0(x, "%"), limits = c(0, 100)) +
scale_color_manual(values = c("Ascendente" = "black", "Descendente" = "grey")) +
labs(title = "Gráfica 6: Ojivas de Frecuencia Relativa", x = "Costos Totales", y = "Porcentaje (%)") +
theme_minimal()
boxplot(datos_zoom, horizontal = TRUE, col = "gray",
main = "Gráfica No. 7: Distribución de Costos Totales",
xlab = "Costo Total", las = 1, pch = 19, outcol = "black")
axis(1, at = pretty(range(datos_zoom)), labels = format(pretty(range(datos_zoom)), big.mark = ","))
mediana <- median(datos_zoom)
media_aritmetica <- mean(datos_zoom)
t <- table(datos_zoom)
Mo <- as.numeric(names(t)[which.max(t)])
S <- sd(datos_zoom)
coeficiente_variabilidad <- (S / media_aritmetica) * 100
As <- skewness(datos_zoom)
curtosis_val <- kurtosis(datos_zoom)
Tabla_indicadores <- data.frame(
Variable = "Costos Totales (90%)",
Mínimo = round(min(datos_zoom), 2), Máximo = round(max(datos_zoom), 2),
Media = round(media_aritmetica, 2), Mediana = round(mediana, 2),
Moda = round(Mo, 2), SD = round(S, 2), CV = round(coeficiente_variabilidad, 2),
As = round(As, 2), K = round(curtosis_val, 2)
)
kable(Tabla_indicadores, format = "markdown", caption = "Tabla No. 1: Indicadores estadísticos.")
| Variable | Mínimo | Máximo | Media | Mediana | Moda | SD | CV | As | K |
|---|---|---|---|---|---|---|---|---|---|
| Costos Totales (90%) | 1 | 525700 | 62015.52 | 18757 | 5000 | 102072.4 | 164.59 | 2.46 | 5.89 |
# Análisis de Atípicos
stats_outliers <- boxplot.stats(datos_zoom)$out
num_outliers <- length(stats_outliers)
cat("Cantidad de valores atípicos detectados:", num_outliers, "\n")
## Cantidad de valores atípicos detectados: 354
CONCLUSIONES
Los costos totales analizados fluctúan en un rango global de $0.0 hasta $840,526,118.0, lo que evidencia una amplitud extrema en la base de datos original. Al centrar el estudio en el segmento representativo del 90%, los costos se sitúan entre un mínimo de $1,525.0 y un máximo de $700,620.0, con un costo promedio de $62,015.52 y una mediana de $18,757.0. La marcada diferencia entre la media y la mediana confirma un fuerte sesgo positivo, donde el promedio se ve “inflado” por los valores más altos del segmento.
La variable presenta una desviación estándar (SD) de $102,072.4, lo que genera un Coeficiente de Variabilidad (CV) de 164.59%. Este valor indica una dispersión extremadamente alta, señalando que los costos no son uniformes y presentan una gran heterogeneidad entre los registros.
El coeficiente de asimetría de 2.46 refleja un sesgo a la derecha muy pronunciado; esto significa que la gran mayoría de los costos son relativamente bajos (cercanos al mínimo), mientras que una minoría de proyectos con costos elevados desplaza la cola de la distribución hacia la derecha. Por su parte, la curtosis de 5.89 clasifica la distribución como Leptocúrtica, lo que indica una alta concentración de datos en los niveles de costo iniciales, con una caída muy abrupta hacia los valores superiores. Estos indicadores justifican plenamente el análisis mediante el “Zoom” del 90%, ya que la naturaleza de la variable está dominada por valores pequeños con presencia de valores atípicos significativos que distorsionan las medidas de tendencia central tradicionales.