Justificación de la variable

A diferencia de los meses, las horas del día representan una escala temporal de alta granularidad. La “Hora del accidente” se considera una variable cuantitativa continua (o discreta si tomamos la hora exacta como número entero). Sin embargo, al contar con 24 posibles resultados distintos, analizarla valor por valor difumina las tendencias estructurales. Por ello, se la somete a un proceso de agrupación por intervalos (clases). Al agrupar las 24 horas en bloques definidos (ej. bloques de 4 horas: [0-4), [4-8), etc.), podemos identificar fácilmente si las incidencias ocurren predominantemente en la madrugada, la mañana o la tarde, logrando un panorama probabilístico mucho más útil para la gestión operativa.

1 Carga de datos y Extracción de la Hora

Primero, leemos la base de datos y aislamos únicamente el componente “Hora” (0 a 24) de la fecha en la que ocurrió la contingencia.

datos <- read.csv("database-_1_.csv", stringsAsFactors = FALSE)

# Parseo del formato fecha y hora original
fechas <- as.POSIXct(datos$Accident.Date.Time, format="%m/%d/%Y %H:%M")
# Extracción de la hora en formato numérico 
horas_limpias <- as.numeric(format(fechas, "%H"))
horas_limpias <- as.numeric(format(fechas, "%H"))
horas_limpias <- horas_limpias[!is.na(horas_limpias)]

2 Tabla de Frecuencia por Intervalos

A continuación, creamos intervalos de 4 horas de amplitud. Esto dividirá el día en 6 franjas clave. También añadimos la “Marca de Clase” (punto medio del intervalo) necesaria para cálculos matemáticos.

amplitud <- 4
limites <- seq(0, 24, by = amplitud)

n <- length(limites)
etiquetas <- paste0("[", limites[-n], "-", limites[-1], ")")
# Forzamos a que el último intervalo tenga el corchete cerrado "]" al final
etiquetas[length(etiquetas)] <- paste0("[", limites[n-1], "-", limites[n], "]")

# 2. Agrupar la variable en intervalos usando las etiquetas automáticas
horas_agrupadas <- cut(horas_limpias, breaks = limites, labels = etiquetas, right = FALSE, include.lowest = TRUE)

# 3. Creación de tabla base
tabla_base <- table(horas_agrupadas)
Intervalo <- names(tabla_base)
ni <- as.vector(tabla_base)
hi <- round(ni / sum(ni) * 100, 2)

# Ajuste por pérdida de decimales 
diferencia <- 100 - sum(hi)
if(diferencia != 0){
  indice_max <- which.max(ni)
  hi[indice_max] <- hi[indice_max] + diferencia
}

# 4. Frecuencias Acumuladas
Ni_asc <- cumsum(ni)
Ni_dsc <- rev(cumsum(rev(ni)))
Hi_asc <- cumsum(hi)            
Hi_dsc <- rev(cumsum(rev(hi)))

# 5. Marca de clase 
xi <- limites[-n] + (amplitud / 2) 

# 6. Construcción del Dataframe
TDFHoras <- data.frame(
  "Intervalos" = Intervalo,
  "MC" = xi,
  "ni" = ni, 
  "hi (%)" = hi, 
  "Ni Asc" = Ni_asc, 
  "Ni Dsc" = Ni_dsc, 
  "Hi Asc" = Hi_asc, 
  "Hi Dsc" = Hi_dsc,
  check.names = FALSE
)

paged_table(TDFHoras, options = list(rows.print = 6))

3 Distribución de apagados según la hora del día

La visualización a través del histograma nos permite ver claramente en qué bloques horarios colapsan los sistemas. Al estar agrupado, las barras representan intervalos completos de tiempo.

library(ggplot2)
TDFHoras$Intervalos <- reorder(TDFHoras$Intervalos, TDFHoras$`MC`)

p_ni <- ggplot(TDFHoras, aes(x = Intervalos, y = ni)) +
  geom_col(width = 0.9, fill = "steelblue", color = "black", alpha = 0.8, linewidth = 0.5) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
  labs(
    title = "Gráfica No 1: Distribución local de apagados por Intervalos de Hora",
    x = "Franja Horaria del Día",
    y = "Cantidad de Incidentes"
  ) +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
    axis.text.x = element_text(angle = 0, hjust = 0.5, color = "black"),
    axis.text.y = element_text(color = "black"),
    axis.line = element_line(linewidth = 0.5, color = "black")
  )

print(p_ni)

4 Visión completa de casos de apagados por horario

Para comprender la verdadera magnitud de los incidentes en cada franja horaria respecto al panorama completo, es estrictamente necesario establecer el eje vertical (Y) con el límite máximo de la población total de estudio (2795 apagados). Si permitiéramos que la gráfica se ajustara automáticamente al intervalo con más accidentes, visualmente parecería que ese bloque acapara la totalidad de los problemas, generando un sesgo de percepción. Al fijar esta escala global, la gráfica actúa como un termómetro absoluto: evita distorsiones y permite dimensionar de forma objetiva qué porción del universo total de fallos representa cada bloque temporal.

TDFHoras$Intervalos <- reorder(TDFHoras$Intervalos, TDFHoras$`MC`)

p_ni_global <- ggplot(TDFHoras, aes(x = Intervalos, y = ni)) +
  geom_col(width = 0.9, fill = "steelblue", color = "black", alpha = 0.8, linewidth = 0.5) +
  scale_y_continuous(limits = c(0, 2795), expand = expansion(mult = c(0, 0.05))) +
  labs(
    title = "Gráfica No 2: Distribución global de apagados por Intervalos de Hora",
    x = "Franja Horaria del Día",
    y = "Cantidad de Incidentes"
  ) +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
    axis.text.x = element_text(angle = 0, hjust = 0.5, color = "black"),
    axis.text.y = element_text(color = "black"),
    axis.line = element_line(linewidth = 0.5, color = "black")
  )

print(p_ni_global)

5 Distribución de interrupciones por cada periodo de tiempo

Con este gráfico normalizamos los datos para evaluar la probabilidad real de que un apagado ocurra dentro de un bloque horario determinado a lo largo del día.

TDFHoras$Intervalos <- reorder(TDFHoras$Intervalos, TDFHoras$`MC`)

p_hi <- ggplot(TDFHoras, aes(x = Intervalos, y = `hi (%)`)) +
  geom_col(width = 0.9, fill = "steelblue", color = "black", alpha = 0.8, linewidth = 0.5) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05)),
                     labels = function(x) paste0(x, "%")) + 
  labs(title = "Gráfica No 3: Probabilidad local (%) de apagados por Intervalo",
       x = "Franja Horaria",
       y = "Porcentaje") +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
    axis.text.x = element_text(angle = 0, hjust = 0.5, color = "black"),
    axis.text.y = element_text(color = "black"),
    axis.line = element_line(linewidth = 0.5, color = "black")
  )

print(p_hi)

6 Visión completa del riesgo de apagados por horario

Para evaluar correctamente la probabilidad en términos absolutos, es crucial establecer la escala del eje vertical de 0% a 100%. Si el gráfico se ajustara automáticamente al porcentaje máximo obtenido (por ejemplo, si el intervalo pico solo tiene el 30% o 40%), la barra llenaría casi toda la gráfica, dando la falsa impresión visual de que ese bloque abarca casi la totalidad de los eventos. Al fijar el tope en 100%, logramos un “espectro de probabilidad completo”, permitiendo dimensionar visualmente la verdadera fracción de riesgo que representa cada franja horaria respecto al día entero.

library(ggplot2)

TDFHoras$Intervalos <- reorder(TDFHoras$Intervalos, TDFHoras$`MC`)

p_hi_global <- ggplot(TDFHoras, aes(x = Intervalos, y = `hi (%)`)) +
  geom_col(width = 0.9, fill = "steelblue", color = "black", alpha = 0.8, linewidth = 0.5) +
  scale_y_continuous(limits = c(0, 100),
                     expand = expansion(mult = c(0, 0.05)),
                     labels = function(x) paste0(x, "%")) + 
  labs(title = "Gráfica No 3.1: Probabilidad global (%) de apagados por Intervalo",
       x = "Franja Horaria",
       y = "Porcentaje") +
  theme_classic() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
    axis.text.x = element_text(angle = 0, hjust = 0.5, color = "black"),
    axis.text.y = element_text(color = "black"),
    axis.line = element_line(linewidth = 0.5, color = "black")
  )

print(p_hi_global)

7 Diagrama de Caja

Evaluamos la dispersión horaria cruda. A diferencia de los intervalos, la caja expone dónde se comprime el 50% central de los sucesos a lo largo de las 24 horas del reloj.

boxplot(horas_limpias, 
        horizontal = TRUE, 
        col = "steelblue", 
        ylim = c(0, 24), 
        main = "Gráfica 4: Rango Intercuartílico de la Hora de Apagado",
        xaxt = "n") 
axis(1, at = seq(0, 24, by = 2), labels = seq(0, 24, by = 2), las = 1)
grid(nx = NULL, ny = NA, col = "lightgray", lty = "dotted")

8 Ojivas Acumulativas

Las ojivas ilustran la suma acumulativa a medida que transcurren las horas del día, revelando la aceleración de los accidentes en horarios laborales picos frente a horas nocturnas.

# Agregamos un índice para mapear el eje X con los intervalos ordenados
TDFHoras$Index <- 1:nrow(TDFHoras)

p_ojiva <- ggplot(TDFHoras) +
  geom_line(aes(x = Index, y = `Ni Asc`, color = "Ascendente", linetype = "Ascendente"), linewidth = 0.8) +
  geom_point(aes(x = Index, y = `Ni Asc`, color = "Ascendente"), size = 2) +
  
  geom_line(aes(x = Index, y = `Ni Dsc`, color = "Descendente", linetype = "Descendente"), linewidth = 0.8) +
  geom_point(aes(x = Index, y = `Ni Dsc`, color = "Descendente"), size = 2) +
  
  scale_x_continuous(breaks = TDFHoras$Index, labels = TDFHoras$Intervalos) +
  scale_color_manual(name = NULL, 
                     values = c("Ascendente" = "black", "Descendente" = "steelblue")) +
  scale_linetype_manual(name = NULL, 
                        values = c("Ascendente" = "longdash", "Descendente" = "solid")) +
  labs(title = "Gráfica 3: Distribución acumulada de los apagados",
       x = "Franja Horaria",
       y = "Cantidad Acumulada") +
  theme_bw() + 
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
    legend.position = c(0.85, 0.85),  
    legend.background = element_rect(color = "black", fill = "white", linewidth = 0.5), 
    legend.key = element_blank(),
    panel.grid.minor = element_blank(),
    axis.text = element_text(color = "black")
  )

print(p_ojiva)

9 Tabla Estadístico

Determinamos el marco matemático exacto en base a la data cruda, analizando hacia qué hora apunta el “centro de gravedad” de todos los cortes operativos.

library(e1071) 
library(knitr) 

# 1. Rango 
ri <- min(horas_limpias)
rs <- 24

# 2. Mediana
mediana <- median(horas_limpias)

# 3. Media Aritmética
media_aritmetica <- mean(horas_limpias)

# 4. Moda 
t <- table(horas_limpias)
Mo <- as.numeric(names(t)[which.max(t)]) 

# 5. Desviación Estándar
desviacion_estandar <- sd(horas_limpias)

# 6. Coeficiente de Variabilidad
coeficiente_variabilidad <- (desviacion_estandar / media_aritmetica) * 100

# 7. Asimetría (Skewness)
As <- skewness(horas_limpias)

# 8. Curtosis
curtosis_val <- kurtosis(horas_limpias)

# --- PREPARACIÓN DE LA TABLA ---
Variable <- "Hora de Apagado"
S_texto <- paste(round(desviacion_estandar, 2)) 

Tabla_indicadores <- data.frame(
  Variable,
  ri,                             # Mínimo automático (0)
  rs,                             # Máximo FORZADO a 24
  round(media_aritmetica, 2),     # Media 
  mediana,                        # Mediana
  Mo,                             # Moda
  S_texto,                        # Desviación
  round(coeficiente_variabilidad, 2), 
  round(As, 2), 
  round(curtosis_val, 2)
)

colnames(Tabla_indicadores) <- c("Variable","Mínimo","Máximo","Media","Me","Mo","S","Cv (%)","As","K")

# Imprimir Tabla 
kable(Tabla_indicadores, format = "markdown", caption = "Tabla No. 1: Indicadores estadísticos de las horas de fallos (Límite superior 24h).")
Tabla No. 1: Indicadores estadísticos de las horas de fallos (Límite superior 24h).
Variable Mínimo Máximo Media Me Mo S Cv (%) As K
Hora de Apagado 0 24 11.63 11 9 4.88 41.99 0.08 -0.27

10 Valores Atípicos

Buscamos comportamientos “no estándares” o atípicos con respecto a las horas habituales. Al ser el horario un factor repetitivo (0-23) es posible que no se detecten irregularidades muy alejadas estadísticamente de las colas.

stats_outliers <- boxplot.stats(horas_limpias)$out
num_outliers <- length(stats_outliers)
print(paste("Cantidad de horas atípicas:", num_outliers))
## [1] "Cantidad de horas atípicas: 0"
minimooutliers <- if(num_outliers > 0) min(stats_outliers) else "Ninguno"
maximooutliers <- if(num_outliers > 0) max(stats_outliers) else "Ninguno"

print(paste("Hora atípica mínima:", minimooutliers))
## [1] "Hora atípica mínima: Ninguno"
print(paste("Hora atípica máxima:", maximooutliers))
## [1] "Hora atípica máxima: Ninguno"

11 Conclusión

La variable “Hora de apagado” fluctúa entre 0 y 24 y sus valores se encuentran alrededor de 11.63, con una desviación estándar de 4.88, siendo una variable heterogénea, cuyos valores se concentran en la parte media de la distribución con la agregación de valores atípicos de 0 outliers; por todo lo anterior, el comportamiento de la variable es regular.