3
votes

Remplir plusieurs valeurs NA en sandwich

J'ai un exemple de table avec certaines mais pas toutes les valeurs NA qui doivent être remplacées.

 dput(dat)
structure(list(id = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 
3, 3, 3, 3, 3, 3, 3), message = c(NA, "foo", "foo", NA, "foo", 
NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", 
NA, "bar", NA, "qux"), index = c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 
5, 6, 1, 2, 3, 4, 5, 6, 7, 8)), row.names = c(NA, -20L), class = "data.frame")

Mon objectif est de remplacer les valeurs NA qui sont entourées du même «message» en utilisant la première apparition du message (la plus petite valeur index ) et dernière apparition du message (en utilisant la valeur max index ) par id

Parfois, les séquences NA ne sont que de longueur 1, d'autres fois elles peuvent être très long. Quoi qu'il en soit, tous les NA qui sont "pris en sandwich" entre les messages doivent être remplis.

Le résultat du tableau incomplet ci-dessus serait:

#get distinct messages
messages = unique(dat$message)

#remove NA
messages = messages[!is.na(messages)]

#subset dat for each message
for (i in 1:length(messages)) {print(dat[dat$message == messages[i],]) }

Tout guidage utilisant data.table ou dplyr ici serait utile car je ne sais même pas par où commencer.

Autant que je pouvais obtenir, il y avait un sous-réglage par des messages uniques mais cette méthode ne prend pas en compte id :

 > output
   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

les données:

> dat
   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1    <NA>     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2    <NA>     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3    <NA>     2
15  3    <NA>     3
16  3     bar     4
17  3    <NA>     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8


2 commentaires

Pouvez-vous clarifier ce que vous entendez en le faisant par identifiant? c'est-à-dire que si dans ces données, la ligne 6 avait «toto» et la ligne 8, la ligne 7 ne serait néanmoins pas remplie mais laissée manquante? Je ne pense pas que cela soit actuellement illustré dans votre exemple de données


C'est exact, 7 serait laissé à désordre dans ce cas puisque le id de la ligne 6 est 1 et le id de la ligne 8 est 2. Et si la ligne 7 était "toto ", la ligne 6 resterait NA, encore une fois en raison d'ID différents


5 Réponses :


0
votes

Voici une approche sans regroupement pour remplir les valeurs, puis les remplacer par NA si elles ont été mal renseignées.

tidyr :: fill par défaut remplit les valeurs manquantes avec la valeur précédente, il remplira donc certaines valeurs. Malheureusement, il ne respecte pas le regroupement, nous devons donc utiliser une condition if_else pour corriger ses erreurs.

Premièrement, nous capturons les emplacements des valeurs manquantes d'origine et calculons les valeurs max et min index pour chaque id et message . Après le remplissage, nous nous joignons sur ces limites index . S'il n'y a pas de correspondance, alors l ' id a changé; s'il y a une correspondance, soit c'était un remplacement correct, soit l ' index est en dehors des limites. Nous vérifions donc les emplacements avec les valeurs manquantes d'origine pour ces conditions et les remplaçons par NA s'ils sont remplis.

EDIT: cela peut être cassé sur une autre entrée, en essayant de corriger

library(tidyverse)
dat <- structure(list(id = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3), message = c(NA, "foo", "foo", NA, "foo", NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", NA, "bar", NA, "qux"), index = c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8)), row.names = c(NA, -20L), class = "data.frame")

indices <- dat %>%
  group_by(id, message) %>%
  summarise(min = min(index), max = max(index)) %>%
  drop_na

dat %>%
  mutate(orig_na = is.na(message)) %>%
  fill(message) %>%
  left_join(indices, by = c("id", "message")) %>% 
  mutate(
    message = if_else(
      condition = orig_na &
        (index < min | index > max | is.na(min)),
      true = NA_character_,
      false = message
    )
  )
#>    id message index orig_na min max
#> 1   1    <NA>     1    TRUE  NA  NA
#> 2   1     foo     2   FALSE   2   5
#> 3   1     foo     3   FALSE   2   5
#> 4   1     foo     4    TRUE   2   5
#> 5   1     foo     5   FALSE   2   5
#> 6   1    <NA>     6    TRUE   2   5
#> 7   2    <NA>     1    TRUE  NA  NA
#> 8   2     baz     2   FALSE   2   6
#> 9   2     baz     3    TRUE   2   6
#> 10  2     baz     4   FALSE   2   6
#> 11  2     baz     5   FALSE   2   6
#> 12  2     baz     6   FALSE   2   6
#> 13  3     bar     1   FALSE   1   6
#> 14  3     bar     2    TRUE   1   6
#> 15  3     bar     3    TRUE   1   6
#> 16  3     bar     4   FALSE   1   6
#> 17  3     bar     5    TRUE   1   6
#> 18  3     bar     6   FALSE   1   6
#> 19  3    <NA>     7    TRUE   1   6
#> 20  3     qux     8   FALSE   8   8

Créé le 15/02/2019 par le reprex paquet (v0.2.1)


2 commentaires

sur quelle entrée peut-il être cassé?


Mon approche précédente, je pense, échouerait s'il y avait une valeur manquante dans la ligne 8, elle aurait remplacé la ligne 8 mais la ligne de gauche 7 comme foo



0
votes

Une autre solution tidyverse utilisant case_when. Modifié pour éviter de remplir après la fin de la série.

library(dplyr)

dfr <- data.frame(
  index =  c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8),
  message = c(NA, "foo", "foo", NA, "foo", NA, NA, "baz", NA, "baz", "baz", "baz", "bar", NA, NA, "bar", NA, "bar", NA, "qux"),
  id =  c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3)
)

dfrFilled <- dfr %>% 
  group_by(id) %>% 
  mutate(
    endSeries = max( # identify end of series
      index[message == na.omit(message)[1]],
      na.rm = T
      ),
    filledValues = case_when(
      min(index) == index ~ message,
      max(index) == index ~ message,
      index < endSeries ~ na.omit(message)[1], # fill if index is before end of series.
      TRUE ~ message
    )
  )


2 commentaires

oui output $ message [19] doit rester NA car la séquence de messages bar s'est terminée à output $ message [18]


Probablement trop tard, mais solution mise à jour fournie dans edit. Bonne chance! Beaucoup d'autres solutions intéressantes fournies ici.



0
votes

Si vous remplissez les deux méthodes et vérifiez que l'égalité devrait fonctionner, tant que vous tenez compte du regroupement et de l'index:

tidyverse:

library(data.table)
library(zoo)

setDT(dat)[order(index),
           message := ifelse(na.locf(message, na.rm = FALSE) == na.locf(message, na.rm = FALSE, fromLast = TRUE),
                             na.locf(message, na.rm = FALSE),
                             NA),
           by = "id"][]

    id message index
 1:  1    <NA>     1
 2:  1     foo     2
 3:  1     foo     3
 4:  1     foo     4
 5:  1     foo     5
 6:  1    <NA>     6
 7:  2    <NA>     1
 8:  2     baz     2
 9:  2     baz     3
10:  2     baz     4
11:  2     baz     5
12:  2     baz     6
13:  3     bar     1
14:  3     bar     2
15:  3     bar     3
16:  3     bar     4
17:  3     bar     5
18:  3     bar     6
19:  3    <NA>     7
20:  3     qux     8

data.table
library(tidyverse)

dat %>%
  arrange(id, index) %>%
  mutate(msg_down = fill(group_by(., id), message, .direction = 'down')$message,
         msg_up   = fill(group_by(., id), message, .direction = 'up')$message,
         message = case_when(!is.na(message) ~ message,
                             msg_down == msg_up ~ msg_down,
                             TRUE ~ NA_character_)) %>%
  select(-msg_down, -msg_up)

   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8


0 commentaires

1
votes

Une option qui utilise na.approx de zoo .

Premièrement, nous extrayons les éléments uniques de la colonne message qui ne le sont pas NA et trouvez-y des positions dans dat$message

x[out]
# [1] NA    "foo" "foo" "foo" "foo" NA    NA    "baz" "baz" "baz" "baz" "baz" "bar" "bar" "bar" "bar" "bar" "bar" NA    "qux"

tl; dr strong >

Lorsque nous appelons

out <- do.call(coalesce, 
               lapply(seq_along(x), function(i) as.integer(na.approx(match(y, i) * i, na.rm = FALSE))))
out
# [1] NA  1  1  1  1 NA NA  2  2  2  2  2  3  3  3  3  3  3 NA  4

nous obtenons les éléments uniquement là où il y a 1 s dans y code>. Par conséquent, lorsque nous faisons

lapply(seq_along(x), function(i) as.double(na.approx(match(y, i) * i, na.rm = FALSE)))
#[[1]]
# [1] NA  1  1  1  1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
#
#[[2]]
# [1] NA NA NA NA NA NA NA  2  2  2  2  2 NA NA NA NA NA NA NA NA
#
#[[3]]
# [1] NA NA NA NA NA NA NA NA NA NA NA NA  3  3  3  3  3  3 NA NA
#
#[[4]]
# [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA  4

le résultat est le même pour le 2s.

Pensez à 1 et 2 à partir des premier et deuxième éléments de

na.approx(match(y, 2) * 2, na.rm = FALSE)
# [1] NA NA NA NA NA NA NA  2  2  2  2  2 NA NA NA NA NA NA NA NA

soit "foo" et "baz" .

Maintenant, pour chaque match (y, i) * i , nous pouvons appeler na.approx depuis zoo pour remplir les NA s entre les deux ( i deviendra seq_along (x) plus tard).

x
# [1] "foo" "baz" "bar" "qux"

Nous faisons de même pour chaque élément de seq_along (x) , c'est-à-dire 1: 4 en utilisant lapply . Le résultat est une liste

match(y, 2) * 2
# [1] NA NA NA NA NA NA NA  2 NA  2  2  2 NA NA NA NA NA NA NA NA

( as.double était nécessaire ici car sinon coalesce se plaindrait que "L'argument 4 doit être de type double et non entier" )

Nous y sommes presque. Ce que nous devons faire ensuite est de trouver la première valeur non manquante à chaque position, c'est là que coalesce de dplyr entre en jeu et le résultat est

match(y, 1) * 1
# [1] NA  1  1 NA  1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA

Nous pouvons utiliser ce vecteur pour extraire les valeurs souhaitées de x comme

x <- unique(na.omit(dat$message))
(y <- match(dat$message, x))
# [1] NA  1  1 NA  1 NA NA  2 NA  2  2  2  3 NA NA  3 NA  3 NA  4

library(zoo)
library(dplyr)
out <- do.call(coalesce, 
               lapply(seq_along(x), function(i) as.double(na.approx(match(y, i) * i, na.rm = FALSE))))
dat$new <- x[out]
dat
#    id message index  new
#1   1    <NA>     1 <NA>
#2   1     foo     2  foo
#3   1     foo     3  foo
#4   1    <NA>     4  foo
#5   1     foo     5  foo
#6   1    <NA>     6 <NA>
#7   2    <NA>     1 <NA>
#8   2     baz     2  baz
#9   2    <NA>     3  baz
#10  2     baz     4  baz
#11  2     baz     5  baz
#12  2     baz     6  baz
#13  3     bar     1  bar
#14  3    <NA>     2  bar
#15  3    <NA>     3  bar
#16  3     bar     4  bar
#17  3    <NA>     5  bar
#18  3     bar     6  bar
#19  3    <NA>     7 <NA>
#20  3     qux     8  qux

J'espère que cela vous aidera.


0 commentaires

3
votes

Effectuez un na.locf0 à la fois vers l'avant et vers l'arrière et s'ils sont identiques, utilisez la valeur commune; sinon, utilisez NA. Le regroupement se fait avec ave .

   id message index
1   1    <NA>     1
2   1     foo     2
3   1     foo     3
4   1     foo     4
5   1     foo     5
6   1    <NA>     6
7   2    <NA>     1
8   2     baz     2
9   2     baz     3
10  2     baz     4
11  2     baz     5
12  2     baz     6
13  3     bar     1
14  3     bar     2
15  3     bar     3
16  3     bar     4
17  3     bar     5
18  3     bar     6
19  3    <NA>     7
20  3     qux     8

giving:

library(zoo)

filler <- function(x) {
  forward <- na.locf0(x)
  backward <- na.locf0(x, fromLast = TRUE)
  ifelse(forward == backward, forward, NA)
}
transform(dat, message = ave(message, id, FUN = filler))


0 commentaires