Ajuda com função/purrr

Olá rzeires,

Atualmente estou trabalhando com dados de pixel NDVI. Como é um satélite MODIS e em alguns momentos as nuvens/chuva impedem a visualização do solo a série possui algumas observações em NA. A minha tratativa inicial dessa não observação é conseguir inserir no NA a média, mas teremos alguns cenários para essa média que são:

(1°) Se o NA é o caso da 1° linha do banco teste, pegue a média das duas observações anteriores;
(2°) Se o NA é o caso da 2° linha do banco teste, pegue a média das duas observações posteriores;
(3°) Se o NA é o caso da 4° linha do banco teste, pegue a média entre a observação anterior e posterior ao NA;
(4°) Se o NA é o caso da 7° linha do banco teste, pegue a média entre as duas observações anteriores (para o primeiro NA) e o segundo NA a tratativa é idêntica ao 1° caso ;
(5°) Se o NA é o caso da 8° linha do banco teste, para o primeiro NA a tratativa é idêntica ao caso 3° e o segundo NA deve pegar a média das duas observações anteriores;
(6°) Se o NA é o caso da 11° linha do banco teste, pegue a média entre a observação anterior e posterior aos NAs (para o primeiro NA) e o segundo NA a tratativa é idêntica ao 1° caso ;

A maioria das observações são encaixadas no caso 3°.

Eu não consegui pensar em uma solução com funções pq ainda estou engatinhando nesse quesito. Talvez um purrr ajude, mas também não consegui pensar em uma forma prática de desenvolvimento para o meu problema.

Agradeço desde já.

library(magrittr, include.only = "%>%")


banco_teste <- tibble::tribble(
  ~Id, ~pixel1,  ~pixel2, ~pixel3, ~pixel4,
   1L, 0.25, 0.78, 0.82, NA,
   2L, NA,   0.52, 0.43, 0.23,
   3L, 0.68, 0.58, 0.63, NA,
   4L, 0.68, 0.58, NA,   0.45,
   5L, 0.68, 0.58, 0.63, 0.45,
   6L, 0.68, NA,   0.63, 0.45,
   7L, 0.25, 0.78, NA,   NA,
   8L, 0.25, NA,   0.82, NA, 
   9L, 0.68, NA,   0.63, 0.45,
   10L,0.65, 0.53, NA,   0.23,
   11L,0.25, NA,   NA,   0.10)

Fernanda,

Seu problema não é nada simples de resolver! Na verdade, ele é tão capcioso que eu achei a solução com purrr muito mais difícil de implementar e explicar do que a versão sem purrr.

Para entender a minha resposta, vamos começar repensando a sua pergunta. Quando você dá todos esses exemplos, qual é a regra subjacente? Ou seja, eu quero saber qual é o algoritmo que te levou a criar esses 6 cenários. Exemplos são ótimos para seres humanos, mas infelizmente os computadores só trabalham com procedimentos bem definidos.

O conjunto de regras que eu abstraí dos seus cenários foi o seguinte:

  1. Encontramos o primeiro NA da linha e o substituímos pela média entre o primeiro valor válido à esquerda e o primeiro valor válido à direita.
  2. Se não houver nenhum valor válido de um dos dois lados (por exemplo, se o NA em questão for o primeiro ou o último valor da linha), pegamos dois valores válidos do mesmo lado.
  3. Resolvido o primeiro NA da linha, repetimos os passos 1. e 2. para qualquer outro NA subsequente.

Pode ser que eu tenha entendido errado, aí é só você me falar que eu tento reescrever o código. Mas, se for isso mesmo, podemos criar um código que nem dependa do número ou posição de NAs! Na verdade, até o número de colunas não importa muito.

Minha sugestão de resposta faz um loop nas linhas da base e aplica um algoritmo baseado nos pontos acima. Ele pega o primeiro NA da linha, encontra o primeiro não-NA à esquerda e o primeiro à direita; se não tiver nada válido à esquerda, ele pega um segundo não-NA à direita e vice-versa. Encontrados dois não-NAs, tiramos a média e fazemos a substituição desejada. O procedimento é repetido até que não haja mais nenhum NA na linha.

library(magrittr, include.only = "%>%")

banco_teste <- tibble::tribble(
  ~id, ~pixel1, ~pixel2, ~pixel3, ~pixel4,
  1L,  0.25,    0.78,    0.82,    NA,
  2L,  NA,      0.52,    0.43,    0.23,
  3L,  0.68,    0.58,    0.63,    NA,
  4L,  0.68,    0.58,    NA,      0.45,
  5L,  0.68,    0.58,    0.63,    0.45,
  6L,  0.68,    NA,      0.63,    0.45,
  7L,  0.25,    0.78,    NA,      NA,
  8L,  0.25,    NA,      0.82,    NA,
  9L,  0.68,    NA,      0.63,    0.45,
  10L, 0.65,    0.53,    NA,      0.23,
  11L, 0.25,    NA,      NA,      0.10
)

# Número de pixels na base
n_pix <- ncol(banco_teste) - 1

# Um loop nas linhas da base
for (id in seq_len(nrow(banco_teste))) {

  # A linha da vez (sem a coluna `id`)
  linha <- banco_teste[id, -1]

  # Posição do primeiro NA
  pos_na <- which(is.na(linha))[1]

  # Enquanto tiver pelo menos um NA na linha
  while (!is.na(pos_na)) {

    # Nossos buscadores da esquerda e direita
    i <- -1
    j <- +1

    # Diminuir i até que ele ache um não-NA (ou caia para fora da linha)
    while (pos_na + i > 0 && is.na(linha[pos_na + i])) {
      i <- i - 1
    }

    # Aumentar j até que ele ache um não-NA (ou caia para fora da linha)
    while (pos_na + j <= n_pix && is.na(linha[pos_na + j])) {
      j <- j + 1
    }

    # Se i tiver caido para fora do vetor, pegar um segundo valor à direita
    if (pos_na + i == 0) {
      i <- j + 1
    }

    # Se j tiver caido para fora do vetor, pegar um segundo valor à esquerda
    if (pos_na + j == n_pix + 1) {
      j <- i - 1
    }

    # Substituir o NA pela média dos dois valores encontrados
    linha[pos_na] <- mean(c(linha[[pos_na + i]], linha[[pos_na + j]]))

    # Se tiver mais um NA na linha, vamos passar para ele
    pos_na <- which(is.na(linha))[1]
  }

  # Colocar a linha de volta na base (sem os NAs)
  banco_teste[id, -1] <- linha
}

banco_teste
#> # A tibble: 11 × 5
#>       id pixel1 pixel2 pixel3 pixel4
#>    <int>  <dbl>  <dbl>  <dbl>  <dbl>
#>  1     1  0.25   0.78   0.82   0.8  
#>  2     2  0.475  0.52   0.43   0.23 
#>  3     3  0.68   0.58   0.63   0.605
#>  4     4  0.68   0.58   0.515  0.45 
#>  5     5  0.68   0.58   0.63   0.45 
#>  6     6  0.68   0.655  0.63   0.45 
#>  7     7  0.25   0.78   0.515  0.648
#>  8     8  0.25   0.535  0.82   0.678
#>  9     9  0.68   0.655  0.63   0.45 
#> 10    10  0.65   0.53   0.38   0.23 
#> 11    11  0.25   0.175  0.138  0.1

Created on 2022-05-11 by the reprex package (v2.0.1)

Espero que o código não tenha ficado muito complicado :sweat_smile: Foi a melhor forma que eu encontrei de resolver o problema. Se der algum problema com a base real, manda aqui para eu tentar ajustar.

3 curtidas

Oi Caio,

Que solução LINDA! Lhe agradeço desde já.

Suas pontuações são válidas em relação a qual algoritmo levei em conta para ‘criar’ essas regras e a minha resposta é: Algoritmo time de sensoriamente remoto (:sweat_smile:). A realidade do satélite MODIS são observações entre 16 e 16 dias para a mesma latitude/longitude observada e, como estou trabalhando com pastagens, faz sentido para nós que este valor ausente seja substituído pela média dos vizinhos. Lembrando que minha observação NÃO é o pixel em si, é o índice NDVI do pixel que tem como causa dos valores ausentes chuva ou nuvem.

O seu conjunto de regras ficou ótimo e é isso mesmo.

Caio, a minha base tem dimensão 1081x374 e obtive o seguinte erro com o seu código:

Error in .subset2(x, i, exact = exact) : subscript out of bounds

Achei estranho esse erro. No erro vi que este problema é devido a chamada de linhas/colunas não existentes, mas não futriquei no seu código.

O meu banco é idêntico ao exemplo:

dados

Fernanda,

O erro de “subscript out of bounds” ocorre quando usamos um índice inválido para acessar os elementos de um data frame.

Para fazer uma simulação mais realística, eu criei uma base aleatória com 1081 linhas e 374 colunas (1 ID e 373 NDVIs), e coloquei 100 NAs em cada coluna para garantir que o algoritmo funcionava em casos bastante variados. Infelizmente eu não obtive nenhum erro como o seu :frowning:

Sem mais informações sobre a base, fica difícil debugar o programa. Pensando aqui, eu imagino que esse erro possa estar vindo de uma linha composta apenas por NAs. Será que tem alguma linha assim na sua base? O comando janitor::remove_empty(tabela, "rows") pode ajudar a remover essas ocorrências.

Se não for isso, você pode tentar compartilhar a base ou simular o código linha a linha para ver exatamente quando e onde o erro acontece.

2 curtidas

Caio,

Me desculpa, foi vacilo meu. Joguei na minha base de teste que eu tinha replicado vários NA e uma linha foi totalmente NA.

Funcionou aqui LINDAMENTE.

Muito obrigada pela ajuda Caio, arrazouuuuuu :purple_heart:

2 curtidas