A analise será feita no datasete bfd_2019.

O objetivo é analisar e falar sobre o que tem na base assim como detalhes importantes e descobertas, assim gerando perguntas sobre o tema

A base consiste em 983396 contendo 48 colunas, a documentação sobre os atributos estão em documentação

carregando a basse e as lib que serão utilizadas

dados <- get(load(url("https://raw.githubusercontent.com/eogasawara/datamining/main/data-work/bfd_2019.rdata")))

obtendo algumas informações sobre a base

dim(dados)
## [1] 983396     48
str(dados)
## 'data.frame':    983396 obs. of  48 variables:
##  $ arrival                            : chr  "CYUL" "CYUL" "CYUL" "CYUL" ...
##  $ depart                             : chr  "SBGR" "SBGR" "SBGR" "SBGR" ...
##  $ route                              : chr  "SBGR-CYUL" "SBGR-CYUL" "SBGR-CYUL" "SBGR-CYUL" ...
##  $ company                            : chr  "ACA" "ACA" "ACA" "ACA" ...
##  $ flight                             : chr  "0097" "0097" "0097" "0097" ...
##  $ di                                 : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ type                               : chr  "I" "I" "I" "I" ...
##  $ depart_day_period                  : Ord.factor w/ 7 levels "Night"<"Early Morning"<..: 2 2 2 2 2 2 2 2 2 7 ...
##  $ arrival_day_period                 : Ord.factor w/ 7 levels "Night"<"Early Morning"<..: 6 6 6 6 6 6 6 6 6 2 ...
##  $ expected_depart                    : POSIXlt, format: "2019-12-12 08:40:00" "2019-12-14 08:40:00" ...
##  $ real_depart                        : POSIXlt, format: "2019-12-12 09:07:00" "2019-12-14 08:40:00" ...
##  $ expected_arrival                   : POSIXlt, format: "2019-12-12 18:45:00" "2019-12-14 18:45:00" ...
##  $ real_arrival                       : POSIXlt, format: "2019-12-12 19:34:00" "2019-12-14 18:45:00" ...
##  $ status_depart                      : chr  "Pontual" "Pontual" "Pontual" "Pontual" ...
##  $ status_arrival                     : chr  "Atraso 30-60" "Pontual" "Pontual" "Atraso 30-60" ...
##  $ observation                        : chr  "CONEXÃO DE AERONAVE" NA NA "CONEXÃO DE AERONAVE" ...
##  $ delay_depart                       : num  27 0 0 15 7 0 0 0 -1 0 ...
##  $ delay_arrival                      : num  49 0 0 38 34 0 0 0 2 0 ...
##  $ expected_flight_length             : num  605 605 605 605 605 605 605 605 605 615 ...
##  $ real_flight_length                 : num  627 605 605 628 632 605 605 605 608 615 ...
##  $ outlier_depart_delay               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ outlier_arrival_delay              : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ outlier_expected_flight_consistency: logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ outlier_real_flight_consistency    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ outlier_expected_flight_length     : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ outlier_real_flight_length         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ depart_air_temperature             : num  21 23 26 21 24 20 23 23 20 23 ...
##  $ depart_dew_point                   : num  19 19 18 20 19 20 20 20 19 23 ...
##  $ depart_relative_humidity           : num  88.3 78.2 61.4 94 73.6 ...
##  $ depart_wind_direction              : num  270 90 20 130 320 320 70 90 60 100 ...
##  $ depart_wind_speed                  : num  2 5 9 5 13 2 6 3 9 3 ...
##  $ depart_sky_coverage                : chr  NA "SCT" NA "OVC" ...
##  $ depart_pressure                    : num  29.9 30.2 30 30.1 30 ...
##  $ depart_visibility                  : num  6.21 6.21 6.21 6.21 6.21 4.97 6.21 6.21 5.59 6.21 ...
##  $ depart_apparent_temperature        : num  21 23 26 21 24 ...
##  $ depart_wind_speed_scale            : Ord.factor w/ 11 levels "Calm"<"Light Air"<..: 2 3 4 3 5 2 3 2 4 2 ...
##  $ depart_wind_direction_cat          : Ord.factor w/ 16 levels "N"<"NNE"<"NE"<..: 13 5 2 7 15 15 4 5 4 5 ...
##  $ arrival_air_temperature            : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_dew_point                  : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_relative_humidity          : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_wind_direction             : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_wind_speed                 : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_sky_coverage               : chr  NA NA NA NA ...
##  $ arrival_pressure                   : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_visibility                 : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_apparent_temperature       : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_wind_speed_scale           : Ord.factor w/ 11 levels "Calm"<"Light Air"<..: NA NA NA NA NA NA NA NA NA NA ...
##  $ arrival_wind_direction_cat         : Ord.factor w/ 16 levels "N"<"NNE"<"NE"<..: NA NA NA NA NA NA NA NA NA NA ...

À primeira vista, observamos que o conjunto de dados possui diversos pontos que podem ser explorados. Para seguir um caminho de análise, vamos examinar algumas colunas específicas e, a partir delas, destacar certos aspectos de forma mais detalhada.

Por exemplo, as colunas expected_depart, real_depart, expected_arrival e real_arrival são variáveis temporais. Elas indicam os horários programados (expected_) e os horários reais (real_) de partida e chegada dos voos.

As informações de atraso ou adiantamento estão refletidas nas colunas delay_depart e delay_arrival. A coluna delay_depart indica quanto tempo o voo partiu após ou antes do horário previsto. Quando o valor é negativo, como por exemplo -7, significa que o voo saiu 7 minutos adiantado. Já a coluna delay_arrival mostra o tempo de atraso ou adiantamento na chegada, com base na diferença entre expected_arrival e real_arrival.

A lógica é que, quanto maior o atraso na saída, maior a probabilidade de ele impactar também o horário de chegada ao destino.

A coluna observation trás informações sobre o voo o que nos esclarece muitas coisas, sobre o que está acontecendo com o voo.

library(dplyr)
## 
## Anexando pacote: 'dplyr'
## Os seguintes objetos são mascarados por 'package:stats':
## 
##     filter, lag
## Os seguintes objetos são mascarados por 'package:base':
## 
##     intersect, setdiff, setequal, union
obs_mais_comuns <-dados %>%
  filter(!is.na(observation)) %>%
  count(observation, sort = TRUE) 

head(obs_mais_comuns, 20)

Note que temos cerca de 45 tipos de observações diferentes, para reduzir a dimenção por problemas semelhantes vamos criar uma variavel com o proposito de agregar dados semelhantes

summary(dados$delay_depart)
##       Min.    1st Qu.     Median       Mean    3rd Qu.       Max.       NA's 
## -525615.00      -5.00       0.00      -3.54       4.00  132895.00      18135

como podemos observar temos valores outliers

summary(dados$delay_arrival)
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
## -525623.0     -10.0       0.0      -4.3       2.0  305314.0     18135

vamos ver a distribuição geral

dados %>%
  filter(delay_depart >= -500, delay_depart <= 500) %>%
  summarise(
    min = min(delay_depart, na.rm = TRUE),
    max = max(delay_depart, na.rm = TRUE),
    mean = mean(delay_depart, na.rm = TRUE),
    median = median(delay_depart, na.rm = TRUE)
  )

A mediana = 0 indica que mais da metade dos voos não tiveram atraso (ou partiram no horário). A média positiva (4.79 min) mostra que, no geral, os voos atrasam levemente. Os valores extremos originais (como 305314 ou -525623) provavelmente são erros ou registros inválidos.

library(ggplot2)
library(dplyr)
library(tidyr)

tempo <- 100

# Filtrar valores extremos para visualização
dados_filtrados <- dados %>%
  filter(
    delay_depart >= - tempo, delay_depart <= tempo,
    delay_arrival >= - tempo, delay_arrival <= tempo
  )

# Transformar para formato longo
dados_long <- dados_filtrados %>%
  pivot_longer(cols = c(delay_depart, delay_arrival),
               names_to = "tipo_delay",
               values_to = "delay")

# Plotar histograma com cores por tipo de atraso
ggplot(dados_long, aes(x = delay, fill = tipo_delay)) +
  geom_histogram(alpha = 0.5, position = "identity", binwidth = 10, color = "white") +
  scale_fill_manual(values = c("delay_depart" = "steelblue", "delay_arrival" = "darkred"),
                    labels = c("Partida", "Chegada")) +
  labs(
    title = "Distribuição dos Atrasos",
    x = "Atraso (minutos)",
    y = "Frequência",
    fill = "Tipo de Atraso"
  ) +
  theme_minimal()

Assim conseguimos agrupar a categorização

library(stringr)

dados <- dados %>%
  mutate(obs_categoria = case_when(
    str_detect(observation, regex("LIBERAÇÃO|PLANO DE VOO|ANTECIPAÇÃO|AUTORIZAD[AO]", ignore_case = TRUE)) ~ "Tráfego aéreo / Autorização",
    str_detect(observation, regex("DEFEITO|TROCA DE AERONAVE|AVARIA|PANE|DEGELO", ignore_case = TRUE)) ~ "Problema na aeronave",
    str_detect(observation, regex("CONEXÃO AERONAVE|VOO DE IDA", ignore_case = TRUE)) ~ "Conexão",
    str_detect(observation, regex("METEOROLÓGICAS|ABAIXO LIMITES|GELO|NEVE|LAMA", ignore_case = TRUE)) ~ "Clima",
    str_detect(observation, regex("AEROPORTO .*INTERDITADO|RESTRIÇÃO|FACILIDADES|DESTINO INTERDITADO|ALTERNATIVA", ignore_case = TRUE)) ~ "Infraestrutura Aeroportuária",
    str_detect(observation, regex("EQUIPO|ABASTECIMENTO|DESTANQUEIO|OPERAÇÕES EM SOLO", ignore_case = TRUE)) ~ "Falha de equipamentos/apoio",
    str_detect(observation, regex("SEGURANÇA|PAX|CARGA|ALFÂNDEGA|MIGRAÇÃO", ignore_case = TRUE)) ~ "Segurança / Passageiros / Alfândega",
    str_detect(observation, regex("^CANCELAMENTO", ignore_case = TRUE)) ~ "Cancelamento técnico ou climático",
    str_detect(observation, regex("FERIADO|VOO ESPECIAL|INCLUSÃO DE ETAPA", ignore_case = TRUE)) ~ "Outros específicos",
    str_detect(observation, regex("ATRASOS NÃO ESPECÍFICOS", ignore_case = TRUE)) ~ "Outros não específicos",
    TRUE ~ "Outros não específicos"
  ))

atrasos_por_categoria <- dados %>%
  group_by(obs_categoria) %>%
  summarise(
    media_atraso_depart = mean((delay_arrival - delay_depart), na.rm = TRUE),
    total_voos = n()
  ) %>%
  arrange(desc(media_atraso_depart))

atrasos_por_categoria

para enteder melhor o que está acontecendo com esses voo com o tempo de viagem tão longo, podemos observar seus atributos de tempo

library(dplyr)
library(ggplot2)

# 1. Filtrar voos com delay_depart ou delay_arrival > 500 minutos
voos_extremos <- dados %>%
  filter(delay_depart > 500 | delay_arrival > 500)

# 2. Ver as observações mais comuns
voos_extremos %>%
  count(obs_categoria, sort = TRUE)
voos_extremos %>%
  count(obs_categoria, sort = TRUE) %>%
  slice_head(n = 15) %>%
  ggplot(aes(x = reorder(obs_categoria, n), y = n)) +
  geom_col(fill = "#0072B2") +
  coord_flip() +
  labs(
    title = "Principais observações em voos com atraso > 500 min",
    x = "Observação",
    y = "Número de voos"
  ) +
  theme_minimal()

descrição de type: (N) Nacional, (I) Internacional, (R) Regional, (H) Sub-regional, (E) Especial, (c) frete/carga, (g) frete/carga internacional e (l) rede postal;

library(dplyr)

voos_atraso_chegada_grande <- dados %>%
  filter(
    delay_arrival > 60,
    !type %in% c("C", "G", "L"),
    abs(expected_flight_length - real_flight_length) > 60
  ) %>%
  select(route, type, expected_flight_length, real_flight_length, observation)

head(voos_atraso_chegada_grande, 10)

aqui podemos observar que mesmo com a demora para o voo sair e chegar a maior parte dos voos não tem tanta diferença de voo, porém se observamos os voos que tem uma janela de mais de 1 hora de expectativa de chegada temos o seguinte grafico

library(dplyr)
library(ggplot2)

obs_counts <- dados %>%
  filter(
    delay_arrival > 60,
    !type %in% c("C", "G", "L"),
    abs(expected_flight_length - real_flight_length) > 60
  ) %>%
  count(obs_categoria, sort = TRUE) %>%
  slice_head(n = 10)

ggplot(obs_counts, aes(x = reorder(obs_categoria, n), y = n)) +
  geom_col(fill = "steelblue") +
  geom_text(aes(label = n), hjust = -0.1, size = 3.5) +
  coord_flip() +
  labs(
    title = "Observações para voos com grande discrepância de tempo",
    x = "Observação",
    y = "Contagem"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold"),
    axis.text.y = element_text(size = 10),
    plot.margin = margin(t = 20, r = 20, b = 20, l = 20)  # aumenta as margens
  ) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.1)))

library(ggplot2)
library(dplyr)

dados_plot <- atrasos_por_categoria %>%
  filter(!is.na(media_atraso_depart), !is.nan(media_atraso_depart), is.finite(media_atraso_depart))

ggplot(dados_plot, aes(x = reorder(obs_categoria, media_atraso_depart), y = media_atraso_depart)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(x = "Categoria", y = "Média da Diferença de Atraso (chegada - partida)",
       title = "Atraso Médio por Categoria de Observação") +
  theme_minimal()

um detalhe muito interessante sobre as colunas status é que ela fala se o avião saiu no horário certo ou quando em qual ponto está o atraso do voo está em de 30 em 30 minutos

library(dplyr)

dados_contagem <- dados %>%
  count(status_depart, status_arrival)

dados_contagem

para ficar melhor de vizualizar

library(ggplot2)

niv <- c("Antecipado", "Pontual", "Atraso 30-60", "Atraso 60-120", "Atraso 120-240", "Atraso >240")

dados_contagem$status_depart <- factor(dados_contagem$status_depart, levels = niv)
dados_contagem$status_arrival <- factor(dados_contagem$status_arrival, levels = niv)

ggplot(dados_contagem, aes(x = status_depart, y = status_arrival)) +
  geom_point(aes(size = n, color = n), alpha = 0.7) +
  geom_text(aes(label = n), vjust = -0.8, size = 3.2) +
  scale_size_continuous(range = c(2, 15)) +
  scale_color_viridis_c() +
  theme_minimal() +
  labs(
    x = "Status na Partida",
    y = "Status na Chegada",
    title = "Relação entre Status de Partida e Chegada",
    size = "Contagem",
    color = "Contagem"
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

vamos ver no mapa

library(shiny)
library(leaflet)
library(geosphere)
## 
## Anexando pacote: 'geosphere'
## O seguinte objeto é mascarado por 'package:shiny':
## 
##     span
library(readr)
library(dplyr)

aeroportos <- read_csv("https://ourairports.com/data/airports.csv", show_col_types = FALSE)
aeroportos_icao <- aeroportos %>%
  select(ident, name, latitude_deg, longitude_deg)

get_cor_tipo <- function(tipo) {
  if (tipo == "I") return("red")
  if (tipo == "N") return("blue")
  return("gray")
}

nesse bloco conseguimos a latitude e longitude dos aeroportos, e vamos usar a escala de cor vermelhor para voos internacionais e N para voos nacionais.

library(leaflet)
library(dplyr)

rotas_df <- voos_atraso_chegada_grande %>%
      mutate(
        origem = sub("-.*", "", route),
        destino = sub(".*-", "", route)
      )

rotas_geo <- rotas_df %>%
  left_join(aeroportos_icao, by = c("origem" = "ident")) %>%
  rename(lat_origem = latitude_deg, lon_origem = longitude_deg, nome_origem = name) %>%
  left_join(aeroportos_icao, by = c("destino" = "ident")) %>%
  rename(lat_destino = latitude_deg, lon_destino = longitude_deg, nome_destino = name) %>%
  mutate(diff = as.numeric(real_flight_length) - as.numeric(expected_flight_length))

df_filtrado <- rotas_geo %>% filter(diff > 100) %>% filter(type=='I') # I ou N


mapa <- leaflet(df_filtrado) %>%
  addTiles()

for (i in 1:nrow(df_filtrado)) {
  origem <- c(df_filtrado$lon_origem[i], df_filtrado$lat_origem[i])
  destino <- c(df_filtrado$lon_destino[i], df_filtrado$lat_destino[i])
  
  if (any(is.na(origem)) || any(is.na(destino))) next
  
  cor_linha <- get_cor_tipo(df_filtrado$type[i])
  rota <- geosphere::gcIntermediate(origem, destino, n = 100, addStartEnd = TRUE, sp = TRUE)
  
  mapa <- mapa %>% addPolylines(data = rota, color = cor_linha, weight = 2)
}

mapa