1 Introdução

Este relatório descreve, de forma objetiva e transparente, a metodologia adotada para o tratamento de valores ausentes (NAs) na base operacional utilizada no projeto de modelagem de bioincrustação em navios.

O foco é demonstrar que:

2 Descrição da base de dados

A base original foi fornecida no arquivo df.xlsx, com as seguintes características principais:

A primeira etapa consiste em carregar e padronizar a base:

df_bruto <- read_excel("data/df.xlsx") %>%
  clean_names() %>%          # padroniza nomes: decLatitude -> dec_latitude
  arrange(start_gmt_date)    # garante ordenação temporal

glimpse(df_bruto)
## Rows: 3,056
## Columns: 15
## $ session_id     <dbl> 3.98e+10, 3.98e+10, 3.98e+10, 3.98e+10, 3.98e+10, 3.98e…
## $ ship_name      <chr> "DANIEL PEREIRA", "DANIEL PEREIRA", "DANIEL PEREIRA", "…
## $ class          <chr> "Aframax", "Aframax", "Aframax", "Aframax", "Aframax", …
## $ event_name     <chr> "NAVEGACAO", "NAVEGACAO", "NAVEGACAO", "NAVEGACAO", "NA…
## $ start_gmt_date <dttm> 2021-05-25 11:00:00, 2021-05-26 11:00:00, 2021-05-27 1…
## $ trim           <dbl> 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, …
## $ displacement   <dbl> 128703.7, 128703.7, 128703.7, 128703.7, 128703.7, 12870…
## $ beaufort       <dbl> 3, 3, 3, 1, 1, 1, 3, 3, 2, 2, 3, 2, NA, NA, NA, NA, 3, …
## $ sea_condition  <dbl> 2, 3, 2, 2, 5, 3, 3, 2, 1, 2, 2, 2, NA, NA, NA, NA, 2, …
## $ speed          <dbl> 10.58, 11.80, 11.83, 11.72, 11.75, 11.84, 12.00, 11.63,…
## $ dec_latitude   <dbl> -30.3800, -28.5568, -27.1400, -25.2800, -23.4545, -22.0…
## $ dec_longitude  <dbl> 1.02500e+01, 5.20180e+00, 2.10000e-08, -4.42730e+00, -9…
## $ consumo        <dbl> 47, 48, 46, 47, 46, 48, 45, 41, 40, 40, 40, 20, 2, 0, 0…
## $ pontuacao      <dbl> 4.8, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ temperatura    <dbl> 298.9, 301.6, 307.3, 299.9, 301.6, 302.0, 300.3, 300.1,…

3 Diagnóstico inicial de valores ausentes (NAs)

Antes de qualquer tratamento, foi feito um diagnóstico da quantidade e do percentual de NAs em cada coluna.

na_info_inicial <- tibble(
  coluna = names(df_bruto),
  n_na   = sapply(df_bruto, function(x) sum(is.na(x))),
  pct_na = round(colMeans(is.na(df_bruto)) * 100, 2)
)

na_info_inicial %>%
  arrange(desc(pct_na))
## # A tibble: 15 × 3
##    coluna          n_na pct_na
##    <chr>          <int>  <dbl>
##  1 pontuacao       3053   99.9
##  2 temperatura     1396   45.7
##  3 dec_latitude     884   28.9
##  4 dec_longitude    884   28.9
##  5 displacement     854   28.0
##  6 beaufort         854   28.0
##  7 sea_condition    854   28.0
##  8 trim             853   27.9
##  9 speed            837   27.4
## 10 session_id         0    0  
## 11 ship_name          0    0  
## 12 class              0    0  
## 13 event_name         0    0  
## 14 start_gmt_date     0    0  
## 15 consumo            0    0

Os principais pontos observados foram:

Dado esse cenário, decidiu-se por um tratamento variável a variável, levando em conta a natureza física de cada campo.

4 Metodologia de tratamento de NAs

4.1 Visão geral da abordagem

A estratégia adotada combinou:

  • Interpolação temporal para séries contínuas (ex.: velocidade, posição, temperatura);
  • Interpolação espacial para latitude/longitude (trajetória do navio);
  • Medianas (globais ou por navio) para cobrir lacunas residuais;
  • Regressão linear simples para corrigir valores fisicamente impossíveis (ex.: consumo = 0 com navio em alta velocidade);
  • Preservação da coluna pontuacao sem imputação direta, devido à baixa cobertura e natureza subjetiva.

Além disso, foi construído um índice físico de fouling, utilizado como base para uma pontuacao_estimada.

4.2 Tratamento de latitude e longitude (dec_latitude, dec_longitude)

Justificativa: falhas de GPS são comuns, mas o movimento do navio é contínuo. É fisicamente coerente interpolar a posição entre dois pontos conhecidos.

Passos:

  1. Interpolação temporal linear (na.approx);
  2. Preenchimento das bordas com o último/primeiro valor válido (na.locf).
df <- df_bruto %>%
  mutate(
    dec_latitude  = as.numeric(dec_latitude),
    dec_longitude = as.numeric(dec_longitude)
  )

df$dec_latitude  <- na.approx(df$dec_latitude,  na.rm = FALSE)
df$dec_longitude <- na.approx(df$dec_longitude, na.rm = FALSE)

df$dec_latitude  <- na.locf(df$dec_latitude,  na.rm = FALSE)
df$dec_latitude  <- na.locf(df$dec_latitude,  fromLast = TRUE)
df$dec_longitude <- na.locf(df$dec_longitude, na.rm = FALSE)
df$dec_longitude <- na.locf(df$dec_longitude, fromLast = TRUE)

4.3 Tratamento de TRIM, displacement, Beaufort e sea_condition

Justificativa: essas variáveis apresentam variação relativamente suave:

  • trim e displacement mudam com o carregamento, mas não de forma caótica;
  • beaufort e sea_condition refletem condições de mar e vento, que evoluem gradualmente.

Abordagem:

  • Interpolação temporal linear;
  • Mediana (por navio, quando for o caso) para preencher lacunas residuais;
  • No caso de sea_condition, uso de beaufort como proxy quando disponível.
df <- df %>%
  mutate(
    trim         = as.numeric(trim),
    displacement = as.numeric(displacement),
    beaufort     = as.numeric(beaufort),
    sea_condition = as.numeric(sea_condition)
  )

# TRIM
df$trim <- na.approx(df$trim, na.rm = FALSE)
df$trim[is.na(df$trim)] <- median(df$trim, na.rm = TRUE)

# DISPLACEMENT por navio
df <- df %>%
  group_by(ship_name) %>%
  arrange(start_gmt_date, .by_group = TRUE) %>%
  mutate(displacement = na.approx(displacement, na.rm = FALSE)) %>%
  mutate(displacement = ifelse(is.na(displacement),
                               median(displacement, na.rm = TRUE),
                               displacement)) %>%
  ungroup()

# BEAUFORT
df$beaufort <- na.approx(df$beaufort, na.rm = FALSE)
df$beaufort[is.na(df$beaufort)] <- median(df$beaufort, na.rm = TRUE)

# SEA_CONDITION
df$sea_condition <- na.approx(df$sea_condition, na.rm = FALSE)
idx_sc_na <- is.na(df$sea_condition) & !is.na(df$beaufort)
df$sea_condition[idx_sc_na] <- df$beaufort[idx_sc_na]
df$sea_condition[is.na(df$sea_condition)] <- median(df$sea_condition, na.rm = TRUE)
df$sea_condition <- round(df$sea_condition)

4.4 Tratamento de velocidade (speed)

Justificativa operacional:

  • Em operação real, velocidade não deveria ser NA; ausência indica falha de medição ou lançamento;
  • Quando o navio está parado:
    • posição não muda;
    • consumo frequentemente é 0;

Regra aplicada:

  • Se a posição não mudou (lat/lon iguais ao registro anterior) → speed = 0;
  • Se consumo == 0speed = 0;
  • Nos demais casos → interpolação temporal.
df <- df %>%
  mutate(
    speed = as.numeric(speed),
    pos_igual = lag(dec_latitude) == dec_latitude &
                lag(dec_longitude) == dec_longitude
  ) %>%
  mutate(
    speed = case_when(
      !is.na(speed)         ~ speed,
      pos_igual == TRUE     ~ 0,
      consumo == 0          ~ 0,
      TRUE                  ~ NA_real_
    )
  )

df$speed <- na.approx(df$speed, na.rm = FALSE)
df$speed[is.na(df$speed)] <- median(df$speed, na.rm = TRUE)

4.5 Tratamento de temperatura (temperatura)

Justificativa:

  • Temperatura da água impacta fortemente o risco de bioincrustação;
  • Dados ambientais falham com frequência;
  • Há uma relação física clara entre latitude e temperatura (águas tropicais mais quentes).

Abordagem:

  1. Interpolação temporal;
  2. Regressão linear simples temperatura ~ dec_latitude;
  3. Mediana como fallback final.
df <- df %>%
  mutate(temperatura = as.numeric(temperatura))

df$temperatura <- na.approx(df$temperatura, na.rm = FALSE)

mod_temp <- lm(temperatura ~ dec_latitude, data = df)

df$temperatura[is.na(df$temperatura)] <-
  predict(mod_temp, df)[is.na(df$temperatura)]

df$temperatura[is.na(df$temperatura)] <- median(df$temperatura, na.rm = TRUE)

4.6 Tratamento de consumo (consumo)

Problema identificado:

  • Registros com consumo = 0 ao mesmo tempo em que speed > 3 nós
    → fisicamente impossível (erro de medição).

Solução:

  • Marcar esses cases como erro (consumo_erro);
  • Ajustar regressão consumo ~ speed + displacement + beaufort usando apenas consumos > 0;
  • Substituir os zeros inválidos pelos valores previstos pelo modelo.
df <- df %>%
  mutate(consumo = as.numeric(consumo)) %>%
  mutate(consumo_erro = ifelse(consumo == 0 & speed > 3, 1, 0))

table(df$consumo_erro)
## 
##    0    1 
## 1855 1201
mod_c <- lm(consumo ~ speed + displacement + beaufort,
            data = df %>% filter(consumo > 0))

df$consumo[df$consumo_erro == 1] <-
  predict(mod_c, df[df$consumo_erro == 1, ])

df$consumo[df$consumo < 0] <- 0

4.7 Tratamento da coluna PONTUACAO

A coluna pontuacao é um indicador de fouling baseado em inspeção, porém:

  • Tem quase 100% de valores ausentes;
  • Não é possível “reconstruir” o que o inspetor veria em cada data.

Por isso:

  • pontuacao não foi imputada;
  • Ela foi preservada como pontuacao_original, e a partir das variáveis físicas foi construída um índice de fouling e uma pontuacao_estimada.
df <- df %>%
  mutate(pontuacao_original = pontuacao)

5 Índice físico de fouling e pontuação estimada

5.1 Construção do fouling_index

Foi criado um índice físico (fouling_index) a partir de:

  • consumo relativo à velocidade (consumo/speed);
  • operação em baixa velocidade (flag speed < 5 nós);
  • temperatura da água (normalizada);
  • condição de mar (mar muito calmo favorece fouling).
df <- df %>%
  mutate(
    fuel_per_speed = consumo / pmax(speed, 1),
    low_speed_flag = ifelse(speed < 5, 1, 0),
    temp_norm      = rescale(temperatura, to = c(0, 1)),
    calm_sea_flag  = ifelse(beaufort <= 2, 1, 0),
    fouling_index_raw =
      fuel_per_speed +
      0.7 * low_speed_flag +
      0.5 * temp_norm +
      0.3 * calm_sea_flag,
    fouling_index = rescale(fouling_index_raw, to = c(0, 4))
  )

5.2 Calibração da pontuacao_estimada

Nos poucos pontos em que pontuacao_original existe:

  • É ajustada uma regressão linear simples
    pontuacao_original ~ fouling_index;
  • Essa relação é usada para estimar uma pontuacao_estimada em todos os registros;
  • Caso haja apenas 1 ponto de ancoragem, a média é alinhada;
  • Se não houver nenhum ponto, pontuacao_estimada = fouling_index.
dados_ancora <- df %>%
  filter(!is.na(pontuacao_original), !is.na(fouling_index))

if (nrow(dados_ancora) >= 2) {
  mod_p <- lm(pontuacao_original ~ fouling_index, data = dados_ancora)
  df$pontuacao_estimada <- as.numeric(predict(mod_p, newdata = df))
} else if (nrow(dados_ancora) == 1) {
  ref_p    <- dados_ancora$pontuacao_original[1]
  media_fi <- mean(df$fouling_index, na.rm = TRUE)
  df$pontuacao_estimada <- df$fouling_index - media_fi + ref_p
} else {
  df$pontuacao_estimada <- df$fouling_index
}

df$pontuacao_estimada <- pmax(pmin(df$pontuacao_estimada, 24), 0)

Essa abordagem permite:

  • preservar os poucos valores reais de pontuacao_original;
  • gerar um indicador contínuo utilizável em modelos e dashboards.

6 Auditoria final de NAs

Após todo o tratamento, foi feita uma nova verificação de NAs.

na_final <- tibble(
  coluna = names(df),
  n_na   = sapply(df, function(x) sum(is.na(x))),
  pct_na = round(colMeans(is.na(df)) * 100, 2)
)

na_final %>%
  arrange(desc(pct_na))
## # A tibble: 25 × 3
##    coluna              n_na pct_na
##    <chr>              <int>  <dbl>
##  1 pontuacao           3053  99.9 
##  2 pontuacao_original  3053  99.9 
##  3 pos_igual              1   0.03
##  4 session_id             0   0   
##  5 ship_name              0   0   
##  6 class                  0   0   
##  7 event_name             0   0   
##  8 start_gmt_date         0   0   
##  9 trim                   0   0   
## 10 displacement           0   0   
## # ℹ 15 more rows

O resultado esperado é:

7 Salvamento dos dados tratados

Por fim, a base tratada é salva tanto em RDS quanto em Excel para uso posterior (modelagem, dashboards, relatórios).

saveRDS(df, "data/df_tratado_fouling.rds")
writexl::write_xlsx(df, "data/df_tratado_fouling.xlsx")

8 Conclusão

O tratamento de NAs foi conduzido de forma:

Esta base tratada constitui o ponto de partida confiável para as etapas seguintes de:
modelagem preditiva de bioincrustação, análise de cenários de limpeza e avaliação do impacto em consumo e emissões.