AVISO IMPORTANTE!
Originalmente, mi variable de estudio seleccionada, el identificador del operador (Operator.ID), era de naturaleza cualitativa nominal, ya que únicamente consistía en un código numérico para etiquetar a la empresa responsable de la infraestructura, sin ningún orden inherente. Para enriquecer mi análisis de seguridad y poder establecer prioridades en la vigilancia regulatoria, decidí realizar una transformación profunda de los datos hacia una variable ordinal. Para ello, apliqué un criterio propio de “Nivel de Incidencia Operativa”, fundamentado en la frecuencia histórica acumulada de accidentes reportados por cada compañía. Se reclasificaron los operadores en tres niveles jerárquicos de impacto: Nivel 1: Operadores Menores Nivel 2: Actores Recurrentes Nivel 3: Gigantes Corporativo
La configuración inicial del entorno y la habilitación de paquetes
estadísticos son procedimientos básicos. dplyr permite una
estructuración eficiente de la matriz, mientras que knitr y
kableExtra son claves para maquetar los resultados con el
rigor formal que exige un documento académico.
library(knitr)
library(kableExtra)
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:kableExtra':
##
## group_rows
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
El análisis descriptivo comienza con la incorporación del archivo fuente (database-1.csv). Esta acción transfiere los registros al entorno de R, preservando la nomenclatura original de los parámetros operativos para garantizar un seguimiento fidedigno y sin alteraciones de los incidentes.
datos <- read.csv("database-_1_.csv", header = TRUE, sep = ",", dec = ".", check.names = FALSE)
names(datos)
## [1] "Report Number"
## [2] "Supplemental Number"
## [3] "Accident Year"
## [4] "Accident Date/Time"
## [5] "Operator ID"
## [6] "Operator Name"
## [7] "Pipeline/Facility Name"
## [8] "Pipeline Location"
## [9] "Pipeline Type"
## [10] "Liquid Type"
## [11] "Liquid Subtype"
## [12] "Liquid Name"
## [13] "Accident City"
## [14] "Accident County"
## [15] "Accident State"
## [16] "Accident Latitude"
## [17] "Accident Longitude"
## [18] "Cause Category"
## [19] "Cause Subcategory"
## [20] "Unintentional Release (Barrels)"
## [21] "Intentional Release (Barrels)"
## [22] "Liquid Recovery (Barrels)"
## [23] "Net Loss (Barrels)"
## [24] "Liquid Ignition"
## [25] "Liquid Explosion"
## [26] "Pipeline Shutdown"
## [27] "Shutdown Date/Time"
## [28] "Restart Date/Time"
## [29] "Public Evacuations"
## [30] "Property Damage Costs"
## [31] "Lost Commodity Costs"
## [32] "Public/Private Property Damage Costs"
## [33] "Emergency Response Costs"
## [34] "Environmental Remediation Costs"
## [35] "Other Costs"
## [36] "All Costs"
Para este apartado, la atención se centra en aislar el identificador de la compañía a cargo de la infraestructura (Operator.ID). Este procedimiento analítico es esencial para segmentar los fallos según la empresa operadora, lo que permite evaluar el historial de accidentalidad y la responsabilidad operativa de cada entidad.
zona <- datos$`Operator ID`
La determinación de las frecuencias absolutas convierte la data cruda en información gerencial. Al tabular el número de derrames por operador, se obtiene un indicador directo de qué compañías presentan mayor recurrencia de incidentes. Esta métrica es fundamental para enfocar auditorías técnicas o controles preventivos en las empresas con más reportes.
conteo_zona <- table(zona)
print(conteo_zona)
## zona
## 300 395 515 879 999 1248 1541 1845 1960 2162 2170 2190 2339
## 156 3 1 1 1 13 2 115 5 1 8 5 1
## 2371 2387 2552 2730 2731 3445 4430 4472 4805 4906 5081 5320 6911
## 2 4 136 2 34 9 4 2 42 47 7 1 1
## 7063 9175 10012 10250 11032 11169 11551 11733 12105 12470 12624 12628 12634
## 3 30 33 3 1 71 6 4 37 30 10 9 4
## 14145 14194 15156 15485 15674 15774 15786 15915 18092 18386 18667 18718 18779
## 1 4 8 2 32 18 1 1 20 1 1 180 2
## 19237 19410 20160 20202 22430 22442 22610 22830 22855 25146 26041 26045 26049
## 5 1 6 1 13 30 140 3 36 2 87 1 3
## 26061 26065 26085 26086 26094 26099 26112 26120 26125 26134 26136 26149 26303
## 2 5 19 4 7 1 1 1 6 10 1 8 2
## 30003 30005 30658 30755 30777 30781 30782 30825 30826 30829 30834 30909 30959
## 6 3 2 5 8 6 13 1 8 201 2 2 2
## 31013 31045 31056 31082 31130 31166 31174 31189 31270 31325 31336 31371 31423
## 2 7 1 1 14 4 60 10 1 4 2 5 2
## 31448 31454 31455 31476 31554 31555 31556 31563 31570 31574 31580 31583 31610
## 2 13 2 29 4 17 4 1 9 7 28 1 8
## 31613 31618 31627 31663 31666 31672 31684 31720 31723 31816 31822 31863 31871
## 1 155 2 2 4 12 114 8 1 2 2 1 2
## 31878 31888 31947 31948 31957 32009 32011 32035 32044 32051 32080 32099 32103
## 2 12 14 1 16 1 26 1 6 1 11 17 8
## 32109 32117 32147 32174 32223 32246 32258 32266 32283 32288 32296 32334 32346
## 75 1 87 2 2 1 9 1 3 5 2 13 1
## 32407 32410 32412 32450 32453 32481 32483 32502 32532 32537 32543 32545 32551
## 1 1 2 5 1 2 1 1 2 32 1 1 7
## 32602 32613 32619 32632 32658 32678 32683 32688 38894 38924 38933 39010 39012
## 2 1 2 3 2 6 3 3 2 1 2 1 2
## 39013 39023 39029 39043 39047 39055 39068 39080 39084 39085 39090 39098 39104
## 5 4 1 6 1 1 2 4 3 1 1 1 1
## 39105 39145 39149 39183 39191 39229 39264 39286 39302 39307 39349 39440 39467
## 2 9 8 4 1 1 1 1 1 5 1 3 1
## 39504 39509 39534 99031 99043
## 2 1 1 2 2
En este paso, extraje mi variable Operator.ID, calculé previamente el volumen total de incidentes de cada empresa en la base de datos para categorizarla, omití valores nulos y estructuré la tabla ordinal. Esto me permite revelar si la siniestralidad está dispersa de forma homogénea o si, por el contrario, se concentra en unos pocos actores de gran magnitud.
library(dplyr)
library(knitr)
library(kableExtra)
# 1. Preparar datos y agrupar
datos_operadores <- datos %>%
mutate(Operator.ID = `Operator ID`) %>%
group_by(Operator.ID) %>%
mutate(total_accidentes = n()) %>%
ungroup() %>%
mutate(Categoria_por_Operador = case_when(
is.na(Operator.ID) | Operator.ID == "" ~ "DESCONOCIDO",
total_accidentes > 100 ~ "GIGANTES CORPORATIVOS",
total_accidentes >= 20 & total_accidentes <= 100 ~ "ACTORES RECURRENTES",
TRUE ~ "OPERADORES MENORES"
)) %>%
filter(Categoria_por_Operador != "DESCONOCIDO") %>%
mutate(Nivel_Incidencia = case_when(
Categoria_por_Operador == "OPERADORES MENORES" ~ 1,
Categoria_por_Operador == "ACTORES RECURRENTES" ~ 2,
Categoria_por_Operador == "GIGANTES CORPORATIVOS" ~ 3,
TRUE ~ 0
))
TDF_agrupada <- datos_operadores %>%
count(Nivel_Incidencia, Categoria_por_Operador, name = "ni") %>%
arrange(desc(Nivel_Incidencia), desc(ni))
TDF_agrupada$hi_exacto <- (TDF_agrupada$ni / sum(TDF_agrupada$ni)) * 100
TDF_agrupada$decimal_exacto <- TDF_agrupada$hi_exacto / 100
TDF_agrupada$hi <- round(TDF_agrupada$hi_exacto, 2)
TDF_agrupada$decimal <- round(TDF_agrupada$decimal_exacto, 3)
dif_hi <- 100 - sum(TDF_agrupada$hi)
dif_dec <- 1 - sum(TDF_agrupada$decimal)
idx_max <- which.max(TDF_agrupada$ni)
TDF_agrupada$hi[idx_max] <- TDF_agrupada$hi[idx_max] + dif_hi
TDF_agrupada$decimal[idx_max] <- TDF_agrupada$decimal[idx_max] + dif_dec
TDF_agrupada$Nivel_Incidencia <- as.character(TDF_agrupada$Nivel_Incidencia)
Sumatoria <- data.frame(
Nivel_Incidencia = "",
Categoria_por_Operador = "Total",
ni = sum(TDF_agrupada$ni),
hi = sum(TDF_agrupada$hi),
decimal = sum(TDF_agrupada$decimal)
)
TDF_final <- rbind(TDF_agrupada[, c("Nivel_Incidencia", "Categoria_por_Operador", "ni", "hi", "decimal")], Sumatoria)
colnames(TDF_final) <- c("Nivel de incidencia", "Categoría por operador", "ni", "hi (%)", "fi")
titulo_formal <- "CUADRO N°1 <br/> Distribución de frecuencias de accidentes según el nivel de incidencia del operador de ductos en Estados Unidos, [2010-2016]"
kable(TDF_final, align = 'c',
digits = c(0, 0, 0, 2, 3)) %>%
kable_styling(full_width = FALSE, position = "center",
bootstrap_options = c("striped", "hover", "condensed", "bordered")) %>%
add_header_above(c(" " = 3, "Frecuencia relativa" = 2), bold = TRUE, background = "#D5D8DC") %>%
add_header_above(setNames(5, titulo_formal), align = "center", escape = FALSE, bold = FALSE, background = "white") %>%
row_spec(0, bold = TRUE) %>%
row_spec(nrow(TDF_final), bold = TRUE, background = "#f2f2f2") %>%
row_spec(which(TDF_final$`Nivel de incidencia` == "3"), bold = TRUE)
| Nivel de incidencia | Categoría por operador | ni | hi (%) | fi |
|---|---|---|---|---|
| 3 | GIGANTES CORPORATIVOS | 1197 | 42.83 | 0.428 |
| 2 | ACTORES RECURRENTES | 866 | 30.98 | 0.310 |
| 1 | OPERADORES MENORES | 732 | 26.19 | 0.262 |
| Total | 2795 | 100.00 | 1.000 |
En mi primera gráfica evidencio de manera absoluta la distribución de los siniestros. Resulta de sumo interés notar cómo el grupo selecto que denominé “Gigantes Corporativos” acumula una proporción enorme de los accidentes totales, confirmando mi hipótesis de que las megacorporaciones, por su gigantesco alcance en kilómetros de ductos, acaparan la mayor carga de reportes absolutos.
datos_grafico <- datos_operadores %>%
mutate(Nivel_Incidencia_Label = case_when(
Nivel_Incidencia == 1 ~ "1. Baja",
Nivel_Incidencia == 2 ~ "2. Media",
Nivel_Incidencia == 3 ~ "3. Alta"
)) %>%
count(Nivel_Incidencia_Label, Categoria_por_Operador, name = "ni")
ggplot(datos_grafico, aes(x = reorder(Categoria_por_Operador, -ni), y = ni, fill = Nivel_Incidencia_Label)) +
geom_bar(stat = "identity", width = 0.75, color = "black") +
scale_fill_manual(values = c(
"1. Baja" = "#AED6F1",
"2. Media" = "#3498DB",
"3. Alta" = "#154360"
)) +
labs(
title = "Gráfica N1: Distribución absoluta por Incidencia Operativa",
x = "Categoría por Operador",
y = "Cantidad de Accidentes",
fill = "Nivel de Incidencia"
) +
theme_light() +
theme(
axis.text.x = element_text(angle = 15, hjust = 1, color = "black"),
legend.position = "top"
)
Para obtener una perspectiva global más rigurosa, decidí escalar el eje Y de mi gráfica al tamaño total de mi muestra (2,795 incidentes). Esta visualización me permite dimensionar el peso real que tiene cada categoría frente a la totalidad de fallas históricas. Aquí se aprecia visualmente que ningún nivel por sí solo alcanza a cubrir el total del panorama, demostrando que la accidentabilidad es un fenómeno fragmentado entre los Gigantes Corporativos y la suma masiva de Operadores Menores.
datos_grafico_global <- datos_operadores %>%
mutate(Nivel_Incidencia_Label = case_when(
Nivel_Incidencia == 1 ~ "1. Baja",
Nivel_Incidencia == 2 ~ "2. Media",
Nivel_Incidencia == 3 ~ "3. Alta"
)) %>%
count(Nivel_Incidencia_Label, Categoria_por_Operador, name = "ni")
ggplot(datos_grafico_global, aes(x = reorder(Categoria_por_Operador, -ni), y = ni, fill = Nivel_Incidencia_Label)) +
geom_bar(stat = "identity", width = 0.7, color = "black") +
scale_fill_manual(values = c(
"1. Baja" = "#AED6F1",
"2. Media" = "#3498DB",
"3. Alta" = "#154360"
)) +
scale_y_continuous(
limits = c(0, 2795),
breaks = seq(0, 2795, 500)
) +
labs(
title = "Gráfica N2: Distribución global de Accidentes por Incidencia Operativa",
x = "Categoría por Operador",
y = "Cantidad",
fill = "Nivel de Incidencia"
) +
theme_light() +
theme(
axis.text.x = element_text(angle = 15, hjust = 1, color = "black"),
legend.position = "top"
)
Al convertir mis valores a proporciones porcentuales, el análisis relativo demuestra que los Gigantes Corporativos dominan más de un tercio del universo de fallos. No obstante, el hecho de que muchísimos Operadores Menores generen otra tajada tan significativa ilustra un hallazgo clave de mi estudio: la siniestralidad no depende únicamente de tener una red grande, sino que también delata las carencias estructurales o el mantenimiento insuficiente de las empresas más pequeñas.
datos_hi <- datos_operadores %>%
mutate(Nivel_Incidencia_Label = case_when(
Nivel_Incidencia == 1 ~ "1. Baja",
Nivel_Incidencia == 2 ~ "2. Media",
Nivel_Incidencia == 3 ~ "3. Alta"
)) %>%
count(Nivel_Incidencia_Label, Categoria_por_Operador, name = "ni") %>%
mutate(hi_pct = (ni / sum(ni)) * 100)
ggplot(datos_hi, aes(x = reorder(Categoria_por_Operador, -hi_pct), y = hi_pct, fill = Nivel_Incidencia_Label)) +
geom_bar(stat = "identity", width = 0.7, color = "black") +
scale_fill_manual(values = c(
"1. Baja" = "#AED6F1",
"2. Media" = "#3498DB",
"3. Alta" = "#154360"
)) +
scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, by = 20)) +
labs(
title = "Gráfica N3: Porcentaje de Accidentes por Incidencia Operativa",
x = "Categoría por Operador",
y = "Porcentaje (%)",
fill = "Nivel de Incidencia"
) +
theme_classic() +
theme(
axis.text.x = element_text(angle = 15, hjust = 1, color = "black"),
legend.position = "top"
)
Si aislo la escala para enfocarme estrictamente en la distribución local, puedo confirmar nuevamente que la brecha entre los Gigantes Corporativos y los Operadores Menores es mínima en términos de responsabilidad porcentual de accidentes. Ambas fuerzas moldean casi en simetría las estadísticas de riesgo operativo.
datos_hi_local <- datos_operadores %>%
mutate(Nivel_Incidencia_Label = case_when(
Nivel_Incidencia == 1 ~ "1. Baja",
Nivel_Incidencia == 2 ~ "2. Media",
Nivel_Incidencia == 3 ~ "3. Alta"
)) %>%
count(Nivel_Incidencia_Label, Categoria_por_Operador, name = "ni") %>%
mutate(hi_pct = (ni / sum(ni)) * 100)
ggplot(datos_hi_local, aes(x = reorder(Categoria_por_Operador, -hi_pct), y = hi_pct, fill = Nivel_Incidencia_Label)) +
geom_bar(stat = "identity", width = 0.7, color = "black") +
scale_fill_manual(values = c(
"1. Baja" = "#AED6F1",
"2. Media" = "#3498DB",
"3. Alta" = "#154360"
)) +
labs(
title = "Gráfica N4: Porcentaje local de Accidentes por Incidencia Operativa",
x = "Categoría por Operador",
y = "Porcentaje (%)",
fill = "Nivel de Incidencia"
) +
theme_classic() +
theme(
axis.text.x = element_text(angle = 15, hjust = 1, color = "black"),
legend.position = "top"
)
Mi diagrama circular provee una representación cristalina del Principio de Pareto encubierto en mi base de datos: un porcentaje minúsculo de corporaciones altamente activas ocupa una porción gigante del pastel de accidentes, compartiendo la otra gran mitad con la suma acumulada de cientos de Operadores Menores.
datos_pastel <- datos_operadores %>%
count(Categoria_por_Operador, name = "ni") %>%
mutate(hi_pct = round((ni / sum(ni)) * 100, 1)) %>%
mutate(
Categoria_por_Operador = factor(Categoria_por_Operador, levels = c(
"OPERADORES MENORES", "ACTORES RECURRENTES", "GIGANTES CORPORATIVOS"
)),
Nivel_Incidencia_Label = case_when(
Categoria_por_Operador == "GIGANTES CORPORATIVOS" ~ "Alta",
Categoria_por_Operador == "ACTORES RECURRENTES" ~ "Media",
TRUE ~ "Baja"
),
Nivel_Incidencia_Label = factor(Nivel_Incidencia_Label, levels = c("Alta", "Media", "Baja"))
)
colores_operador <- c(
"GIGANTES CORPORATIVOS" = "#154360",
"ACTORES RECURRENTES" = "#3498DB",
"OPERADORES MENORES" = "#AED6F1"
)
colores_niveles <- c(
"Alta" = "#154360",
"Media" = "#3498DB",
"Baja" = "#AED6F1"
)
ggplot(datos_pastel, aes(x = "", y = hi_pct, fill = Categoria_por_Operador)) +
geom_bar(stat = "identity", width = 1, color = "white", size = 1) +
geom_point(aes(color = Nivel_Incidencia_Label), alpha = 0, size = 0) +
coord_polar("y", start = 0) +
geom_text(aes(label = paste0(hi_pct, "%")),
position = position_stack(vjust = 0.5),
color = "white", size = 4, fontface = "bold") +
scale_fill_manual(values = colores_operador) +
scale_color_manual(values = colores_niveles) +
labs(
title = "Gráfica N5: Distribución Circular de Accidentes",
fill = "Categoría por Operador",
color = "Nivel de Incidencia"
) +
guides(
fill = guide_legend(order = 1),
color = guide_legend(order = 2, override.aes = list(alpha = 1, size = 5, shape = 15))
) +
theme_void() +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", margin = margin(b = 10)),
legend.position = "right",
legend.text = element_text(size = 9),
legend.title = element_text(face = "bold", size = 10)
)
## Warning in geom_bar(stat = "identity", width = 1, color = "white", size = 1):
## Ignoring unknown parameters: `size`
En esta sección, calculé mis indicadores estadísticos sobre el comportamiento ordinal para resumir numéricamente hacia dónde se inclina el peso de las compañías en el escenario total de siniestros.
library(knitr)
library(kableExtra)
# 1. Creamos el dataframe solo con la variable solicitada
tabla_categorias <- data.frame(
Variable = "Operadores de Ductos",
Media = "N/A",
Mediana = "N/A",
Moda = "GIGANTES CORPORATIVOS"
)
# 2. Renderizamos con el diseño clásico solicitado
kable(tabla_categorias,
align = c('l', 'c', 'c', 'c'),
caption = "Tabla 2: Categorías de Operadores según Indicadores Estadísticos") %>%
kable_classic(full_width = FALSE, html_font = "Georgia") %>%
row_spec(0, bold = TRUE, color = "#6c757d") %>%
column_spec(1, color = "#6c757d")
| Variable | Media | Mediana | Moda |
|---|---|---|---|
| Operadores de Ductos | N/A | N/A | GIGANTES CORPORATIVOS |
AVISO IMPORANTE!
Al categorizar los operadores en niveles de riesgo, nuestra variable resultante es cualitativa ordinal en formato de texto. Estadísticamente, no es posible calcular promedios ni medianas aritméticas sobre etiquetas categóricas, ya que carecen de distancias numéricas equivalentes. Por lo tanto, la única medida de tendencia central representativa y matemáticamente correcta para este análisis descriptivo es la Moda, indicando que el grupo con mayor frecuencia de accidentes recae en la categoría que vemos en la tabla (“GIGANTES CORPORATIVOS”).
La variable “Operador de ductos”, presenta como valor más frecuente “Gigantes corporativos”, con una participación destacada en la muestra. La variable “Operador de ductos” fluctúa entre 1197 y 732.