CPAD Projeto Aeronáuticas da Aviação Civil Brasileira

1. Introdução

1.1 Declaração do Problema

A aviação é, estatisticamente, um dos meios de transporte mais seguros que existem, mas essa segurança não acontece por acaso. Por trás dela há um trabalho continuo e meticuloso de investigação de ocorrências aeronáuticas. No Brasil, esse trabalho é coordenado pelo CENIPA (Centro de Investigação e Prevenção de Acidentes Aeronáuticos), responsável por registrar e investigar cada acidente, incidente grave e incidente da aviação civil.

A pergunta que este projeto tenta responder é: onde, quando e por que essas ocorrências acontecem? Quais fases do voo concentram mais acidentes? Quais estados têm maior incidência? Que fatores humanos e operacionais aparecem com mais frequencia? E, principalmente, qual o grau de letalidade de cada tipo de ocorrencia? Responder a essas questões interessa tanto a mim, como estudante querendo praticar ciência de dados em um conjunto real e “sujo”, quanto a qualquer pessoa preocupada com segurança de voo, pois entender padrões de acidentes é o primeiro passo para preveni-los.

1.2 Dados e Metodologia

A análise se baseia nos dados públicos de Ocorrências Aeronáuticas da Aviação Civil Brasileira, disponibilizados pelo CENIPA no portal dados.gov.br. O conjunto é distribuído em quatro arquivos CSV que se relacionam por códigos de ocorrência:

  • ocorrencia.csv — a tabela central, com uma linha por ocorrência;
  • aeronave.csv — dados das aeronaves envolvidas;
  • ocorrencia_tipo.csv — o(s) tipo(s) de cada ocorrência;
  • fator_contribuinte.csv — os fatores que contribuíram para o evento, quando investigados.

A metodologia percorre as seguintes etapas:

  1. Importação dos quatro CSVs, que vêm em codificação Latin-1 e separados por ;;
  2. Junção (join) das quatro tabelas em um único data frame analítico;
  3. Limpeza e padronização, incluindo tratamento de valores ausentes mascarados, datas, tipos numéricos e capitalização de texto;
  4. Criação de variáveis derivadas como ano, faixa de horário, indicador de fatalidade e nível de dano como fator ordenado;
  5. Análise exploratória com tabelas e gráficos, incluindo recursos interativos.

1.3 Abordagem Técnica

O trabalho foi desenvolvido inteiramente em R, com o ecossistema tidyverse (dplyr, tidyr, stringr, lubridate) para manipulação e limpeza. A grande peculiaridade aqui é que os dados não chegam prontos: são quatro tabelas que precisam ser mescladas, têm valores ausentes registrados de formas incomuns (***, NULL, texto vazio) e algumas delas têm relação um-para-muitos, ou seja, uma ocorrência pode ter vários tipos e vários fatores contribuintes, o que exige agregação prévia à junção para evitar duplicação indevida de linhas.

Para a visualização, combinei gráficos estáticos (ggplot2) com gráficos interativos (plotly) e tabelas navegáveis (DT), permitindo que o leitor explore os dados por conta própria.

1.4 Valor para os Clientes da Análise

Os resultados desta analise tem relevância para diferentes públicos. Órgãos reguladores como CENIPA e ANAC podem usar os padrões identificados para priorizar campanhas de segurança nas fases de voo e nos fatores mais críticos. Empresas aéreas e escolas de aviação podem direcionar treinamentos para os tipos de ocorrência mais comuns em suas operações. E pesquisadores e jornalistas ganham uma base organizada para investigar a evolução da segurança de voo no Brasil ao longo do tempo. Em resumo, a análise transforma quatro arquivos brutos do governo em informação acionável sobre segurança aérea.

2. Pacotes Requeridos

Os pacotes abaixo são necessários para replicar a analise. Tem que instalar eles antes de executar com install.packages("nome_do_pacote").

library(dplyr)       # Manipulação e transformação dos dados (filter, mutate, join...)
library(tidyr)       # Reformulação de dados (pivot, separate, etc.)
library(stringr)     # Limpeza e padronização de texto (maiúsculas, espaços)
library(lubridate)   # Conversão e manipulação de datas
library(ggplot2)     # Gráficos estáticos baseados na gramática dos gráficos
library(plotly)      # Versão interativa dos gráficos (hover, zoom)
library(DT)          # Tabelas interativas com busca, ordenação e paginação
library(scales)      # Formatação de eixos (milhares, porcentagem)
library(knitr)       # Geração de tabelas e do relatório
library(kableExtra)  # Formatação avançada de tabelas estáticas

3. Preparação dos Dados

3.1 Fonte dos Dados

Os dados foram obtidos no Portal Brasileiro de Dados Abertos, no conjunto Ocorrências Aeronáuticas da Aviação Civil Brasileira, mantido pelo CENIPA:

🔗 https://dados.gov.br/dados/conjuntos-dados/ocorrencias-aeronauticas-da-aviacao-civil-brasileira

Os quatro arquivos CSV utilizados (ocorrencia.csv, aeronave.csv, ocorrencia_tipo.csv e fator_contribuinte.csv) devem estar na mesma pasta deste arquivo .Rmd.

# Os arquivos vêm separados por ";" e em codificação Latin-1 (ISO-8859-1),
# por isso precisamos informar 'sep' e 'fileEncoding' na leitura.
ler_cenipa <- function(arquivo) {
  read.csv(arquivo,
           sep = ";",
           fileEncoding = "ISO-8859-1",
           stringsAsFactors = FALSE,
           na.strings = c("", "NULL", "***"))
}

ocorrencia  <- ler_cenipa("ocorrencia.csv")
aeronave    <- ler_cenipa("aeronave.csv")
oc_tipo     <- ler_cenipa("ocorrencia_tipo.csv")
fator       <- ler_cenipa("fator_contribuinte.csv")

# Dimensões de cada tabela original
dim_orig <- tibble::tibble(
  Tabela   = c("ocorrencia", "aeronave", "ocorrencia_tipo", "fator_contribuinte"),
  Linhas   = c(nrow(ocorrencia), nrow(aeronave), nrow(oc_tipo), nrow(fator)),
  Colunas  = c(ncol(ocorrencia), ncol(aeronave), ncol(oc_tipo), ncol(fator))
)
dim_orig

3.2 Descrição da Fonte Original

A base é mantida pelo CENIPA e reúne as ocorrências da aviação civil brasileira investigadas pelo órgão, com registros que vão de 2007 até 2026 (data da coleta). O propósito original dos dados é dar transparência e apoio à prevenção de acidentes: cada ocorrência registrada alimenta as estatísticas oficiais de segurança de voo.

Algumas peculiaridades importantes da fonte merecem atenção antes de qualquer análise. Em primeiro lugar, trata-se de quatro tabelas relacionais, não de um único arquivo. A tabela ocorrencia repete o mesmo código em cinco colunas (codigo_ocorrencia, codigo_ocorrencia1 a codigo_ocorrencia4), e cada coluna serve de chave para uma das outras tabelas. Além disso, os valores ausentes vêm mascarados como ***, NULL ou texto vazio, por isso já os convertemos em NA durante a leitura. Há também relações um-para-muitos a considerar: uma mesma ocorrência pode ter vários tipos e vários fatores contribuintes, sendo que somente uma parte das ocorrências, tipicamente os acidentes, tem fatores investigados. Por fim, o texto vem em caixa alta e há mistura de tipos, com datas no formato dd/mm/aaaa, números, texto e coordenadas geográficas com ponto decimal.

3.3 Limpeza e Preparação dos Dados

A preparação ocorre em duas grandes etapas: juntar as quatro tabelas e, em seguida, limpar e enriquecer o resultado.

Etapa de junção. A tabela ocorrencia é a espinha dorsal do conjunto. A aeronave se junta a ela quase um-para-um, e a granularidade do conjunto final será de uma linha por aeronave envolvida. Já ocorrencia_tipo e fator_contribuinte têm relação um-para-muitos; se as juntássemos diretamente, as linhas se multiplicariam. Por isso, agregamos essas duas tabelas para uma linha por ocorrência antes de realizar o join, guardando o tipo e o fator principal e criando contadores (qtd_tipos, qtd_fatores).

# Removemos as colunas de código redundantes, mantendo só 'codigo_ocorrencia'
ocorrencia <- ocorrencia %>%
  select(-codigo_ocorrencia1, -codigo_ocorrencia2,
         -codigo_ocorrencia3, -codigo_ocorrencia4)

# Agrega tipos: tipo principal + total de tipos por ocorrência
tipo_agg <- oc_tipo %>%
  group_by(codigo_ocorrencia1) %>%
  summarise(
    ocorrencia_tipo     = first(ocorrencia_tipo),
    taxonomia_tipo_icao = first(taxonomia_tipo_icao),
    qtd_tipos           = n(),
    .groups = "drop"
  )

# Agrega fatores: fator principal (nome/aspecto/área) + total de fatores
fator_agg <- fator %>%
  group_by(codigo_ocorrencia3) %>%
  summarise(
    fator_nome    = first(fator_nome),
    fator_area    = first(fator_area),
    fator_aspecto = first(fator_aspecto),
    qtd_fatores   = n(),
    .groups = "drop"
  )

# Junção encadeada (left joins a partir da ocorrência)
dados <- ocorrencia %>%
  left_join(aeronave, by = c("codigo_ocorrencia" = "codigo_ocorrencia2")) %>%
  left_join(tipo_agg,  by = c("codigo_ocorrencia" = "codigo_ocorrencia1")) %>%
  left_join(fator_agg, by = c("codigo_ocorrencia" = "codigo_ocorrencia3"))

dim(dados)  # linhas x colunas do conjunto unido
## [1] 14822    44

Etapa de limpeza e enriquecimento. Com as tabelas unidas, padronizamos os tipos e criamos as variáveis derivadas que serão úteis na análise. Cada passo do código está comentado explicando o motivo da transformação.

dados_limpos <- dados %>%
  mutate(
    # Datas: o original vem como texto "dd/mm/aaaa" -> converter para Date real
    ocorrencia_dia = dmy(ocorrencia_dia),
    # Variável derivada: ano (útil para análise temporal)
    ano = year(ocorrencia_dia),

    # Converter campos numéricos que vieram como texto
    fatalidades    = as.integer(aeronave_fatalidades_total),
    assentos       = as.integer(aeronave_assentos),
    ano_fabricacao = as.integer(aeronave_ano_fabricacao),
    latitude       = as.numeric(ocorrencia_latitude),
    longitude      = as.numeric(ocorrencia_longitude),

    # Padronizar texto em Caixa de Título (evita "EMBRAER" vs "Embraer")
    fabricante = str_to_title(str_squish(aeronave_fabricante)),
    cidade     = str_to_title(str_squish(ocorrencia_cidade)),

    # Classificação como fator ordenado por gravidade
    classificacao = factor(
      ocorrencia_classificacao,
      levels = c("INCIDENTE", "INCIDENTE GRAVE", "ACIDENTE")
    ),

    # Nível de dano como fator ordenado
    nivel_dano = factor(
      aeronave_nivel_dano,
      levels = c("NENHUM", "LEVE", "SUBSTANCIAL", "DESTRUÍDA")
    ),

    # Variável derivada: a ocorrência teve ao menos uma fatalidade?
    teve_fatalidade = ifelse(!is.na(fatalidades) & fatalidades > 0,
                             "Com fatalidade", "Sem fatalidade"),

    # Faixa de horário (a partir da hora "hh:mm:ss")
    hora_num = as.integer(substr(ocorrencia_hora, 1, 2)),
    periodo  = case_when(
      hora_num >= 5  & hora_num < 12 ~ "Manhã",
      hora_num >= 12 & hora_num < 18 ~ "Tarde",
      hora_num >= 18 & hora_num < 24 ~ "Noite",
      TRUE                           ~ "Madrugada"
    )
  ) %>%
  # Mantém apenas registros com data válida e ano dentro do esperado
  filter(!is.na(ano), ano >= 2007, ano <= 2026)

dim(dados_limpos)
## [1] 14822    57

3.4 Dataset Final

A tabela interativa abaixo traz uma amostra condensada do conjunto final já limpo, exibindo apenas algumas colunas de interesse e as primeiras cinquenta linhas. A opção por uma tabela navegável evita imprimir um data frame gigante no relatório.

dados_limpos %>%
  select(ano, classificacao, ocorrencia_uf, cidade, fabricante,
         aeronave_fase_operacao, ocorrencia_tipo, nivel_dano, fatalidades) %>%
  head(50) %>%
  DT::datatable(
    rownames = FALSE,
    options = list(pageLength = 10, scrollX = TRUE),
    colnames = c("Ano", "Classificação", "UF", "Cidade", "Fabricante",
                 "Fase do Voo", "Tipo de Ocorrência", "Nível de Dano", "Fatalidades"),
    caption = "Amostra (50 primeiras linhas) do conjunto final limpo"
  )

3.5 Resumo das Variáveis de Interesse

A tabela a seguir resume, em linguagem direta, as principais variáveis que serão usadas na análise exploratória.

Tabela 1: Variáveis de interesse do conjunto final
Variável Tipo Descrição
ano Numérica Ano da ocorrência (2007–2026), derivado da data.
classificacao Fator Incidente, Incidente Grave ou Acidente (ordem de gravidade).
ocorrencia_uf Texto Unidade da Federação onde ocorreu.
fabricante Texto Fabricante da aeronave, padronizado.
aeronave_fase_operacao Texto Fase do voo (decolagem, cruzeiro, pouso, etc.).
ocorrencia_tipo Texto Tipo principal da ocorrência (ex.: colisão com ave).
fator_nome Texto Fator contribuinte principal (quando investigado).
fator_area Fator Área do fator (humano, operacional, material).
nivel_dano Fator Dano à aeronave (nenhum a destruída).
fatalidades Numérica Total de fatalidades na aeronave.
teve_fatalidade Fator Indicador derivado: houve ao menos uma morte?
periodo Fator Faixa do dia (manhã/tarde/noite/madrugada).

4. Análise Exploratória dos Dados

Esta seção está parcialmente desenvolvida nesta entrega. Abaixo estão as primeiras análises, não vou conseguir entregar tudo de uma vez

4.1 Panorama Geral: Classificação e Letalidade

A primeira pergunta natural é: das três classificações de ocorrência, qual é realmente perigosa? A tabela abaixo cruzza a quantidade de registros com o número de mortes e a taxa de fatalidade, entendida como a proporção de registros com ao menos uma morte.

tab_class <- dados_limpos %>%
  group_by(classificacao) %>%
  summarise(
    registros = n(),
    com_morte = sum(teve_fatalidade == "Com fatalidade", na.rm = TRUE),
    mortes    = sum(fatalidades, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(taxa_fatalidade = round(com_morte / registros * 100, 1)) %>%
  arrange(classificacao)

tab_class %>%
  kable(
    caption = "Tabela 2: Registros, mortes e taxa de fatalidade por classificação",
    col.names = c("Classificação", "Registros", "Com fatalidade",
                  "Total de mortes", "Taxa de fatalidade (%)"),
    format.args = list(big.mark = ".")
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
  row_spec(which(tab_class$classificacao == "ACIDENTE"),
           bold = TRUE, color = "white", background = "#922922")
Tabela 2: Registros, mortes e taxa de fatalidade por classificação
Classificação Registros Com fatalidade Total de mortes Taxa de fatalidade (%)
INCIDENTE 10.500 0 0 0.0
INCIDENTE GRAVE 1.304 0 0 0.0
ACIDENTE 3.018 707 1.704 23.4

Insight: os incidentes são a maioria absoluta dos registros, mas tem umataxa de fatalidade praticamente nula, sendo eventos sem dano grave. Toda a mortalidade se concentra na categoria acidente, onde cerca de 23% dos registros envolvem ao menos uma fatalidade. Em outras palavras, a gravidade não está no volume de ocorrências, mas sim na classificação: um número relativamente pequeno de acidentes responde por todas as mortes presentes na base.