210 votes

Remplacement des NA par la dernière valeur non-NA

Dans un data.frame (ou data.table ), j'aimerais "remplir en avant" les NA avec la valeur non-NA précédente la plus proche. Un exemple simple, utilisant des vecteurs (au lieu de data.frame ) est le suivant :

> y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)

Je voudrais une fonction fill.NAs() qui me permet de construire yy de telle sorte que :

> yy
[1] NA NA NA  2  2  2  2  3  3  3  4  4

Je dois répéter cette opération pour un grand nombre de petits fichiers (~1 To au total). data.frame (~30-50 Mb), où une ligne est NA si toutes ses entrées le sont. Quelle est la bonne façon d'aborder le problème ?

La solution laide que j'ai élaborée utilise cette fonction :

last <- function (x){
    x[length(x)]
}    

fill.NAs <- function(isNA){
if (isNA[1] == 1) {
    isNA[1:max({which(isNA==0)[1]-1},1)] <- 0 # first is NAs 
                                              # can't be forward filled
}
isNA.neg <- isNA.pos <- isNA.diff <- diff(isNA)
isNA.pos[isNA.diff < 0] <- 0
isNA.neg[isNA.diff > 0] <- 0
which.isNA.neg <- which(as.logical(isNA.neg))
if (length(which.isNA.neg)==0) return(NULL) # generates warnings later, but works
which.isNA.pos <- which(as.logical(isNA.pos))
which.isNA <- which(as.logical(isNA))
if (length(which.isNA.neg)==length(which.isNA.pos)){
    replacement <- rep(which.isNA.pos[2:length(which.isNA.neg)], 
                                which.isNA.neg[2:max(length(which.isNA.neg)-1,2)] - 
                                which.isNA.pos[1:max(length(which.isNA.neg)-1,1)])      
    replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
} else {
    replacement <- rep(which.isNA.pos[1:length(which.isNA.neg)], which.isNA.neg - which.isNA.pos[1:length(which.isNA.neg)])     
    replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
}
replacement
}

La fonction fill.NAs est utilisé comme suit :

y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
isNA <- as.numeric(is.na(y))
replacement <- fill.NAs(isNA)
if (length(replacement)){
which.isNA <- which(as.logical(isNA))
to.replace <- which.isNA[which(isNA==0)[1]:length(which.isNA)]
y[to.replace] <- y[replacement]
} 

Sortie

> y
[1] NA  2  2  2  2  3  3  3  4  4  4

... ce qui semble fonctionner. Mais, bon sang, c'est moche ! Des suggestions ?

206voto

Dirk Eddelbuettel Points 134700

Vous voulez probablement utiliser le na.locf() de la fonction zoo pour reporter la dernière observation pour remplacer vos valeurs NA.

Voici le début de son exemple d'utilisation tiré de la page d'aide :

library(zoo)

az <- zoo(1:6)

bz <- zoo(c(2,NA,1,4,5,2))

na.locf(bz)
1 2 3 4 5 6 
2 2 1 4 5 2 

na.locf(bz, fromLast = TRUE)
1 2 3 4 5 6 
2 1 1 4 5 2 

cz <- zoo(c(NA,9,3,2,3,2))

na.locf(cz)
2 3 4 5 6 
9 3 2 3 2

76voto

Ruben Points 773

Désolé de déterrer une vieille question. Je n'ai pas pu trouver la fonction pour faire ce travail dans le train, alors j'en ai écrit une moi-même.

J'ai été fier de découvrir qu'il est un tout petit peu plus rapide.
Il est cependant moins flexible.

Mais il joue bien avec ave et c'est ce dont j'avais besoin.

repeat.before = function(x) {   # repeats the last non NA value. Keeps leading NA
    ind = which(!is.na(x))      # get positions of nonmissing values
    if(is.na(x[1]))             # if it begins with a missing, add the 
          ind = c(1,ind)        # first position to the indices
    rep(x[ind], times = diff(   # repeat the values at these indices
       c(ind, length(x) + 1) )) # diffing the indices + length yields how often 
}                               # they need to be repeated

x = c(NA,NA,'a',NA,NA,NA,NA,NA,NA,NA,NA,'b','c','d',NA,NA,NA,NA,NA,'e')  
xx = rep(x, 1000000)  
system.time({ yzoo = na.locf(xx,na.rm=F)})  
## user  system elapsed   
## 2.754   0.667   3.406   
system.time({ yrep = repeat.before(xx)})  
## user  system elapsed   
## 0.597   0.199   0.793   

Modifier

Comme cette réponse est devenue ma réponse la plus votée, on m'a souvent rappelé que je n'utilise pas ma propre fonction, car j'ai souvent besoin de celle de Zoo. maxgap argument. Parce que zoo a quelques problèmes bizarres dans les cas limites lorsque j'utilise dplyr + dates que je n'ai pas pu déboguer, je suis revenu sur ce sujet aujourd'hui pour améliorer mon ancienne fonction.

J'ai comparé ma fonction améliorée et toutes les autres entrées ici. Pour l'ensemble des fonctions de base, tidyr::fill est le plus rapide tout en ne ratant pas les cas limites. L'entrée Rcpp de @BrandonBertelsen est encore plus rapide, mais elle n'est pas flexible en ce qui concerne le type de l'entrée (il a testé les cas limites de manière incorrecte en raison d'une mauvaise compréhension de l'expression all.equal ).

Si vous avez besoin maxgap La fonction ci-dessous est plus rapide que zoo (et n'a pas les problèmes bizarres avec les dates).

J'ai mis en place le documentation de mes tests .

nouvelle fonction

repeat_last = function(x, forward = TRUE, maxgap = Inf, na.rm = FALSE) {
    if (!forward) x = rev(x)           # reverse x twice if carrying backward
    ind = which(!is.na(x))             # get positions of nonmissing values
    if (is.na(x[1]) && !na.rm)         # if it begins with NA
        ind = c(1,ind)                 # add first pos
    rep_times = diff(                  # diffing the indices + length yields how often
        c(ind, length(x) + 1) )          # they need to be repeated
    if (maxgap < Inf) {
        exceed = rep_times - 1 > maxgap  # exceeding maxgap
        if (any(exceed)) {               # any exceed?
            ind = sort(c(ind[exceed] + 1, ind))      # add NA in gaps
            rep_times = diff(c(ind, length(x) + 1) ) # diff again
        }
    }
    x = rep(x[ind], times = rep_times) # repeat the values at these indices
    if (!forward) x = rev(x)           # second reversion
    x
}

J'ai également placé la fonction dans mon paquet de formulaires (Github uniquement).

48voto

Tony DiFranco Points 193

A data.table solution :

dt <- data.table(y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))
dt[, y_forward_fill := y[1], .(cumsum(!is.na(y)))]
dt
     y y_forward_fill
 1: NA             NA
 2:  2              2
 3:  2              2
 4: NA              2
 5: NA              2
 6:  3              3
 7: NA              3
 8:  4              4
 9: NA              4
10: NA              4

cette approche pourrait également fonctionner avec des zéros de remplissage avant :

dt <- data.table(y = c(0, 2, -2, 0, 0, 3, 0, -4, 0, 0))
dt[, y_forward_fill := y[1], .(cumsum(y != 0))]
dt
     y y_forward_fill
 1:  0              0
 2:  2              2
 3: -2             -2
 4:  0             -2
 5:  0             -2
 6:  3              3
 7:  0              3
 8: -4             -4
 9:  0             -4
10:  0             -4

cette méthode devient très utile pour les données à l'échelle et lorsque vous souhaitez effectuer un remplissage en avant par groupe(s), ce qui est trivial avec data.table . il suffit d'ajouter le(s) groupe(s) à la liste des groupes d'utilisateurs. by avant la clause cumsum logique.

dt <- data.table(group = sample(c('a', 'b'), 20, replace = TRUE), y = sample(c(1:4, rep(NA, 4)), 20 , replace = TRUE))
dt <- dt[order(group)]
dt[, y_forward_fill := y[1], .(group, cumsum(!is.na(y)))]
dt
    group  y y_forward_fill
 1:     a NA             NA
 2:     a NA             NA
 3:     a NA             NA
 4:     a  2              2
 5:     a NA              2
 6:     a  1              1
 7:     a NA              1
 8:     a  3              3
 9:     a NA              3
10:     a NA              3
11:     a  4              4
12:     a NA              4
13:     a  1              1
14:     a  4              4
15:     a NA              4
16:     a  3              3
17:     b  4              4
18:     b NA              4
19:     b NA              4
20:     b  2              2

39voto

Rtist Points 391

Le site tidyr (qui fait partie du tidyverse ) a une façon simple de le faire :

y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)

# first, transform it into a data.frame

df = as.data.frame(y)
   y
1  NA
2   2
3   2
4  NA
5  NA
6   3
7  NA
8   4
9  NA
10 NA

library(tidyr)
fill(df, y, .direction = 'down')
    y
1  NA
2   2
3   2
4   2
5   2
6   3
7   3
8   4
9   4
10  4

37voto

Henrik Points 12148

Vous pouvez utiliser le data.table fonction nafill disponible auprès de data.table >= 1.12.3 .

library(data.table)
nafill(y, type = "locf")
# [1] NA  2  2  2  2  3  3  4  4  4

Si votre vecteur est une colonne dans un data.table vous pouvez également le mettre à jour par référence avec setnafill :

d <- data.table(x = 1:10, y)
setnafill(d, type = "locf", cols = "y")
d
#      x  y
#  1:  1 NA
#  2:  2  2
#  3:  3  2
#  4:  4  2
#  5:  5  2
#  6:  6  3
#  7:  7  3
#  8:  8  4
#  9:  9  4
# 10: 10  4

Si vous avez NA dans plusieurs colonnes...

d <- data.table(x = c(1, NA, 2), y = c(2, 3, NA), z = c(4, NA, 5))
#     x  y  z
# 1:  1  2  4
# 2: NA  3 NA
# 3:  2 NA  5

...vous pouvez les remplir par référence en une seule fois :

setnafill(d, type = "locf")
d
#    x y z
# 1: 1 2 4
# 2: 1 3 4
# 3: 2 3 5

Notez que :

Seulement double et entier Les types de données sont actuellement [ data.table 1.12.6 ] soutenu.

La fonctionnalité sera très probablement étendue prochainement ; voir la question ouverte nafill, setnafill pour le caractère, le facteur et autres types où vous trouverez également un solution de contournement temporaire .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X