INTRODUCCION

Este estudio analiza el número de incidentes reportados en cada registro de operación de los oleoductos SOTE y OCP. Es un conteo que solo toma valores enteros (0, 1, 2), por lo que es una variable cuantitativa discreta. Los datos de la base son simulados con fines académicos.

1 Librerías

Carga de paquetes.

library(readxl)
library(dplyr)
library(gt)
library(e1071)

2 Carga datos

setwd() solo tiene efecto dentro del bloque en el que se ejecuta, por eso la lectura de la base está en el mismo bloque.

setwd("C:/Users/HP/Desktop/tabla_petro")

# Busca el archivo de Excel en la carpeta, aunque el nombre tenga un sufijo como "(1)"
archivo <- list.files(pattern = "^tabla_transporte_petroleo.*\\.xlsx$")[1]
if (is.na(archivo)) stop("No se encontró el archivo .xlsx dentro de la carpeta tabla_petro")
datos <- as.data.frame(read_excel(archivo, sheet = "Datos_10000"))
dim(datos)
## [1] 10000    20

3 Variables

Se extrae la columna Incidentes_reportados_simulados y se descartan los valores ausentes.

Datos <- datos %>%
  filter(!is.na(Incidentes_reportados_simulados))

Variable_Exacta <- Datos$Incidentes_reportados_simulados
if (length(Variable_Exacta) == 0) stop("ERROR: No hay datos válidos.")

4 Conteo

Frecuencias absolutas (ni), porcentuales (hi) y acumuladas ascendentes y descendentes (Ni, Hi).

TDF_Raw <- data.frame(Valor = Variable_Exacta) %>%
  group_by(Valor) %>%
  summarise(ni = n(), .groups = "drop") %>%
  arrange(Valor)

ni <- TDF_Raw$ni
N  <- sum(ni)
hi <- (ni / N) * 100

Ni_asc  <- cumsum(ni)
Ni_desc <- rev(cumsum(rev(ni)))
Hi_asc  <- cumsum(hi)
Hi_desc <- rev(cumsum(rev(hi)))

TDF <- data.frame(
  Valor   = TDF_Raw$Valor,
  ni      = ni,
  hi      = round(hi, 2),
  Ni_asc  = Ni_asc,
  Ni_desc = Ni_desc,
  Hi_asc  = round(Hi_asc, 2),
  Hi_desc = round(Hi_desc, 2)
)
colnames(TDF)[1] <- "Incidentes"
TDF
##   Incidentes   ni    hi Ni_asc Ni_desc Hi_asc Hi_desc
## 1          0 9410 94.10   9410   10000  94.10  100.00
## 2          1  543  5.43   9953     590  99.53    5.90
## 3          2   47  0.47  10000      47 100.00    0.47

5 Tabla

Tabla de distribución de frecuencias.

# Totales
totales <- c("TOTAL", sum(ni), round(sum(hi), 2), "-", "-", "-", "-")

TDF_Char <- TDF %>% mutate(across(everything(), as.character))
TDF_Show <- rbind(TDF_Char, totales)
colnames(TDF_Show) <- names(TDF)

TDF_Show %>%
  gt() %>%
  tab_header(
    title = md("**DISTRIBUCIÓN DE FRECUENCIAS DEL NÚMERO DE INCIDENTES**"),
    subtitle = md("Variable: **Número de incidentes por registro**")
  ) %>%
  tab_source_note(source_note = "Autor: *") %>%
  cols_label(
    Incidentes = "Incidentes (xi)",
    ni = "Cant. registros (ni)",
    hi = "Porcentaje (hi%)",
    Ni_asc = "Ni (Asc)", Ni_desc = "Ni (Desc)",
    Hi_asc = "Hi (Asc)", Hi_desc = "Hi (Desc)"
  ) %>%
  cols_align(align = "center", columns = everything()) %>%
  tab_style(
    style = list(cell_fill(color = "#235347"), cell_text(color = "white", weight = "bold")),
    locations = cells_title()
  ) %>%
  tab_style(
    style = list(cell_fill(color = "#DAF1DE"), cell_text(weight = "bold", color = "#163832")),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = list(cell_fill(color = "#8EB69B"), cell_text(weight = "bold", color = "#163832")),
    locations = cells_body(rows = nrow(TDF_Show))
  ) %>%
  tab_options(
    table.border.top.color = "#235347",
    table.border.bottom.color = "#235347",
    column_labels.border.bottom.color = "#235347",
    data_row.padding = px(6)
  )
DISTRIBUCIÓN DE FRECUENCIAS DEL NÚMERO DE INCIDENTES
Variable: Número de incidentes por registro
Incidentes (xi) Cant. registros (ni) Porcentaje (hi%) Ni (Asc) Ni (Desc) Hi (Asc) Hi (Desc)
0 9410 94.1 9410 10000 94.1 100
1 543 5.43 9953 590 99.53 5.9
2 47 0.47 10000 47 100 0.47
TOTAL 10000 100 - - - -
Autor: *

6 Gráficas

6.1 Gráficos de barras

col_barras <- "#235347"
col_ejes   <- "#163832"
col_claro  <- "#8EB69B"

titulo_g <- function(x) paste(strwrap(x, 80), collapse = "\n")

vals_x <- as.character(TDF$Incidentes)
vals_y <- TDF$ni
vals_y_pct <- TDF$hi
ylim_max <- max(vals_y) * 1.1

6.1.1 Gráfica 1: frecuencia absoluta (local)

par(mar = c(6, 5, 5, 2))
bp <- barplot(vals_y,
        main = titulo_g("Gráfica N°1: Distribución del número de incidentes por registro"),
        cex.main = 0.9,
        ylab = "Cantidad de registros",
        col = col_barras, border = "white", axes = FALSE,
        ylim = c(0, ylim_max),
        axisnames = FALSE)

axis(2, col = col_ejes, col.axis = col_ejes)
axis(1, at = bp, labels = vals_x, col = col_ejes, col.axis = col_ejes, las = 1, cex.axis = 0.9)
title(xlab = "Incidentes por registro", line = 3.5)
grid(nx = NA, ny = NULL, col = "#D7DBDD", lty = "dotted")
box(bty = "l", col = col_ejes)

6.1.2 Gráfica 2: frecuencia absoluta (global)

par(mar = c(6, 5, 5, 2))
bp2 <- barplot(vals_y,
        main = titulo_g("Gráfica N°2: Distribución del número de incidentes por registro"),
        cex.main = 0.9,
        ylab = "Cantidad de registros",
        col = col_barras, border = "white", axes = FALSE,
        ylim = c(0, N),
        axisnames = FALSE)

axis(2, col = col_ejes, col.axis = col_ejes)
axis(1, at = bp2, labels = vals_x, col = col_ejes, col.axis = col_ejes, las = 1, cex.axis = 0.9)
title(xlab = "Incidentes por registro", line = 3.5)
grid(nx = NA, ny = NULL, col = "#D7DBDD", lty = "dotted")
box(bty = "l", col = col_ejes)

6.1.3 Gráfica 3: porcentual (local)

par(mar = c(6, 5, 5, 2))
bp3 <- barplot(vals_y_pct,
        main = titulo_g("Gráfica N°3: Distribución porcentual del número de incidentes por registro"),
        cex.main = 0.9,
        ylab = "% del total",
        col = col_barras, border = "white", axes = FALSE,
        ylim = c(0, max(vals_y_pct) * 1.2),
        axisnames = FALSE)

axis(2, col = col_ejes, col.axis = col_ejes)
axis(1, at = bp3, labels = vals_x, col = col_ejes, col.axis = col_ejes, las = 1, cex.axis = 0.9)
text(x = bp3, y = vals_y_pct, label = paste0(round(vals_y_pct, 1), "%"), pos = 3, cex = 0.8, col = col_ejes)
title(xlab = "Incidentes por registro", line = 3.5)
grid(nx = NA, ny = NULL, col = "#D7DBDD", lty = "dotted")
box(bty = "l", col = col_ejes)

6.1.4 Gráfica 4: porcentual (global, escala de 0 a 100 %)

par(mar = c(6, 5, 5, 2))
bp4 <- barplot(vals_y_pct,
        main = titulo_g("Gráfica N°4: Distribución porcentual del número de incidentes por registro"),
        cex.main = 0.9,
        ylab = "% del total",
        col = col_barras, border = "white", axes = FALSE,
        ylim = c(0, 100),
        axisnames = FALSE)

axis(2, col = col_ejes, col.axis = col_ejes)
axis(1, at = bp4, labels = vals_x, col = col_ejes, col.axis = col_ejes, las = 1, cex.axis = 0.9)
text(x = bp4, y = vals_y_pct, label = paste0(round(vals_y_pct, 1), "%"), pos = 3, cex = 0.8, col = col_ejes)
title(xlab = "Incidentes por registro", line = 3.5)
abline(h = seq(0, 100, 20), col = "#D7DBDD", lty = "dotted")
box(bty = "l", col = col_ejes)

6.2 Gráfica 5: diagrama de caja

par(mar = c(6, 5, 5, 5), xpd = TRUE)
boxplot(Variable_Exacta, horizontal = TRUE, col = col_barras,
        main = titulo_g("Gráfica N°5: Diagrama de caja del número de incidentes por registro"),
        cex.main = 0.9,
        xlab = "", outline = TRUE, outpch = 19, outcol = col_claro,
        boxwex = 0.5, frame.plot = FALSE, xaxt = "n")

eje_x <- unique(round(pretty(Variable_Exacta, n = 8)))
axis(1, at = eje_x, labels = format(eje_x, scientific = FALSE), cex.axis = 0.9, col = col_ejes, col.axis = col_ejes)
title(xlab = "Incidentes por registro", line = 3.5)

# Con tantos ceros la caja se reduce a una línea; se anotan los cuartiles y el conteo de cada valor atípico
q <- quantile(Variable_Exacta, c(0.25, 0.5, 0.75))
if (q[3] - q[1] == 0) {
  text(x = q[2], y = 1.33, labels = paste0("Q1 = Me = Q3 = ", q[2]),
       cex = 0.85, col = col_ejes, adj = 0)
}
conteo_out <- table(boxplot.stats(Variable_Exacta)$out)
text(x = as.numeric(names(conteo_out)), y = 1.12,
     labels = paste0(conteo_out, " registros"), cex = 0.85, col = col_ejes)

par(xpd = FALSE)

Nota: como el 94.1 % de los registros no reportó incidentes, el primer cuartil, la mediana y el tercer cuartil valen 0 y la caja se reduce a una línea. Por eso todos los registros con 1 o 2 incidentes aparecen como valores atípicos. No es un error del gráfico: es una característica de la variable.

6.3 Gráfica 6: ojivas

Se usan puntos (y no líneas) porque la variable es discreta.

par(mar = c(6, 5, 5, 8), xpd = TRUE)

x_vals <- TDF$Incidentes
y_asc  <- TDF$Ni_asc
y_desc <- TDF$Ni_desc

plot(x_vals, y_asc, type = "p", col = col_barras, pch = 19, cex = 1.3,
     main = titulo_g("Gráfica N°6: Ojivas ascendente y descendente del número de incidentes por registro"),
     cex.main = 0.9,
     ylab = "Frecuencia acumulada", xlab = "",
     ylim = c(0, N), axes = FALSE, frame.plot = FALSE)

axis(1, at = x_vals, labels = x_vals, las = 1, cex.axis = 0.9, col = col_ejes, col.axis = col_ejes)
axis(2, col = col_ejes, col.axis = col_ejes)
title(xlab = "Incidentes por registro", line = 3.5)

points(x_vals, y_desc, col = col_claro, pch = 19, cex = 1.3)

legend("right", legend = c("Ascendente", "Descendente"),
       col = c(col_barras, col_claro), pch = 19, cex = 0.8,
       inset = c(-0.22, 0), bty = "n")

par(xpd = FALSE)

7 Indicadores Estadísticos

Cálculo de indicadores sobre los valores de la variable.

media_val   <- mean(Variable_Exacta)
mediana_val <- median(Variable_Exacta)

t_moda     <- table(Variable_Exacta)
freq_max   <- max(t_moda)
modas_calc <- as.numeric(names(t_moda)[t_moda == freq_max])
moda_txt   <- paste(modas_calc, collapse = ", ")

rango_txt    <- paste0("[", min(Variable_Exacta), "; ", max(Variable_Exacta), "]")
varianza_val <- var(Variable_Exacta)
sd_val       <- sd(Variable_Exacta)
cv_val       <- (sd_val / abs(media_val)) * 100

asimetria_val <- skewness(Variable_Exacta, type = 2)
curtosis_val  <- kurtosis(Variable_Exacta, type = 2)

vals_atipicos <- boxplot.stats(Variable_Exacta)$out
num_atipicos  <- length(vals_atipicos)
status_atipicos <- if (num_atipicos > 0) {
  paste0(num_atipicos, " outliers")
} else {
  "0 (Sin atípicos)"
}

df_resumen <- data.frame(
  "Variable"  = "Número de incidentes por registro",
  "Rango"     = rango_txt,
  "Media"     = media_val,
  "Mediana"   = mediana_val,
  "Moda"      = moda_txt,
  "Varianza"  = varianza_val,
  "Desv_Std"  = sd_val,
  "CV_Porc"   = cv_val,
  "Asimetria" = asimetria_val,
  "Curtosis"  = curtosis_val,
  "Atipicos"  = status_atipicos
)

df_resumen %>%
  gt() %>%
  tab_header(
    title = md("**ESTADÍSTICOS DESCRIPTIVOS**"),
    subtitle = "Indicadores calculados sobre los valores de la variable"
  ) %>%
  tab_source_note(source_note = "Autor: *") %>%
  fmt_number(columns = c(Media, Mediana, Varianza, Desv_Std), decimals = 4, use_seps = FALSE) %>%
  fmt_number(columns = c(CV_Porc, Curtosis), decimals = 2) %>%
  fmt_number(columns = c(Asimetria), decimals = 4) %>%
  cols_label(
    Variable = "Variable",
    Rango = "Rango [Min; Max]",
    Media = "Media (X̄)",
    Mediana = "Mediana (Me)",
    Moda = "Moda (Mo)",
    Varianza = "Varianza (S²)",
    Desv_Std = "Desv. Est. (S)",
    CV_Porc = "C.V. (%)",
    Asimetria = "Asimetría (As)",
    Curtosis = "Curtosis (K)",
    Atipicos = "Outliers"
  ) %>%
  cols_align(align = "center", columns = everything()) %>%
  tab_options(
    column_labels.background.color = "#235347",
    table.border.top.color = "#235347",
    table.border.bottom.color = "#235347",
    column_labels.border.bottom.color = "#235347",
    data_row.padding = px(8)
  ) %>%
  tab_style(
    style = list(cell_text(weight = "bold", color = "white")),
    locations = cells_column_labels()
  )
ESTADÍSTICOS DESCRIPTIVOS
Indicadores calculados sobre los valores de la variable
Variable Rango [Min; Max] Media (X̄) Mediana (Me) Moda (Mo) Varianza (S²) Desv. Est. (S) C.V. (%) Asimetría (As) Curtosis (K) Outliers
Número de incidentes por registro [0; 2] 0.0637 0.0000 0 0.0690 0.2628 412.52 4.3249 19.63 590 outliers
Autor: *
# Interpretación (se usa en la conclusión)
txt_cv   <- if (cv_val < 15) "homogéneo" else if (cv_val <= 30) "moderadamente heterogéneo" else "heterogéneo"
txt_asim <- if (abs(asimetria_val) < 0.5) "aproximadamente simétrica" else if (asimetria_val > 0) "asimétrica positiva (sesgo a la derecha)" else "asimétrica negativa (sesgo a la izquierda)"
txt_curt <- if (abs(curtosis_val) < 0.5) "mesocúrtica (similar a la normal)" else if (curtosis_val > 0) "leptocúrtica (más concentrada y con colas más pesadas)" else "platicúrtica (más aplanada)"
pct_moda <- round(max(TDF$hi), 2)

8 Conclusión

Los valores de la variable Número de incidentes por registro fluctúan entre 0 y 2 y giran en torno a la media de 0.0637 (mediana de 0), con una desviación estándar de 0.2628. El valor más frecuente es 0, que reúne el 94.1 % de los registros. Con un coeficiente de variación de 412.52 %, el conjunto de datos es heterogéneo. La distribución es asimétrica positiva (sesgo a la derecha) (As = 4.3249) y leptocúrtica (más concentrada y con colas más pesadas) (K = 19.63). Se identificaron 590 valores atípicos.