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
5 Réponses :
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)
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
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 ) )
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.
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:
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
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
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 2
s.
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.
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))
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 leid
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