1. Carga y Limpieza de Datos
# 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]
  1. Tabla de Distribución de Frecuencias
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")
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
  1. Selección del Segmento (Zoom 90%)
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%)")
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
  1. Visualización de Histogramas
#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)

  1. Análisis de Ojivas descententes y ascendentes
# 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()

  1. Diagrama de Caja (Boxplot)
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 = ","))

  1. Indicadores Estadísticos
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.")
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.