NA'ların NA olmayan en son değerlerle değiştirilmesi


141

Bir data.frame (veya data.table), NA en yakın önceki NA olmayan değeri ile "ileri doldurmak" istiyorum. Vektörleri (a yerine data.frame) kullanmak için basit bir örnek şudur:

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

Bu fill.NAs()şekilde inşa etmemi sağlayan bir işlev istiyorum yy:

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

Ben data.framebir satır NA tüm girişleri olduğu birçok (toplam ~ 1 Tb) küçük boyutlu s (~ 30-50 Mb), bu işlemi tekrarlamak gerekiyor . Soruna yaklaşmanın iyi bir yolu nedir?

Pişirdiğim çirkin çözüm bu işlevi kullanıyor:

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
}

İşlev fill.NAsaşağıdaki gibi kullanılır:

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]
} 

Çıktı

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

... işe yarıyor gibi görünüyor. Ama adamım, bu çirkin mi? Baska öneri?


1
Bu bir yana diğer sorular bakınca sizin şimdi buldum roll=TRUEiçinde data.table.
Matt Dowle

3
Yeni bir yöntem olarak tanıtılıyor filliçindeR
Saksham

14
Ayrıca, içine bak tidyr::fill().
zx8754

Yanıtlar:


160

Büyük olasılıkla , NA değerlerinizi değiştirmek için son gözlemi ileri taşımak için hayvanat bahçesi paketindeki na.locf()işlevi kullanmak istersiniz .

Yardım sayfasından kullanım örneğinin başlangıcı:

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 

2
Ayrıca na.locfhayvanat bahçesinde sıradan vektörler ve hayvanat bahçesi nesneleri ile çalıştığını unutmayın . Onun na.rmargümanı bazı uygulamalarda faydalı olabilir.
G. Grothendieck

5
İlerlemek na.locf(cz, na.rm=FALSE)için kullanın NA.
BallpointBen

@BallpointBen'ın yorumu önemlidir ve cevaba dahil edilmelidir. Teşekkürler!
Ben

62

Eski bir soruyu açtığım için üzgünüm. Bu işi trende yapma işlevine bakamadım, bu yüzden kendim yazdım.

Bunun biraz daha hızlı olduğunu öğrenmekle gurur duydum.
Yine de daha az esnektir.

Ama çok hoş oynuyor ave, buna ihtiyacım vardı.

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   

Düzenle

Bu benim en çok beğenilen cevabım haline geldiğinde, sık sık kendi işlevimi kullanmadığımı hatırlattı, çünkü sıklıkla hayvanat bahçesine ihtiyacım var maxgap argümanına . Hata ayıklayamadığım dplyr + tarihlerini kullandığımda hayvanat bahçesinin bazı garip sorunları olduğu için, bugün eski işlevimi geliştirmek için buna geri döndüm.

Geliştirilmiş işlevimi ve diğer tüm girdileri burada karşılaştırdım. Temel özellikler için, tidyr::fillkenar kasaları başarısız olmasa da en hızlısıdır. @BrandonBertelsen'in Rcpp girişi hala daha hızlı, ancak girişin türüne göre esnek değil (yanlış anlaşılmasından dolayı uç vakaları yanlış test etti all.equal).

İhtiyacınız olursa maxgap, aşağıdaki işlevim hayvanat bahçesinden daha hızlıdır (ve tarihlerle ilgili garip problemler yoktur).

Testlerimin belgelerini koydum .

yeni fonksiyon

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
}

Ayrıca işlevi benim formr paketine koydum (sadece Github).


2
+1, ancak bunu dfbirden çok sütuna sahip bir uygulamaya uygulamak istiyorsanız, bu sütun başına döngü olması gerektiğini tahmin ediyorum ?
Zhubarb

3
@Ruben Raporunuz için tekrar teşekkürler. Şimdiye kadar hata R-Forge'da düzeltildi. Ayrıca na.locf0, şimdi kapsam ve performans olarak repeat_lastişlevinize benzer olan iş atı işlevini değiştirdim ve ihraç ettim . İpucu, kullanmak diffyerine kullanmaktan cumsumkaçınmaktı ifelse. Ana na.locf.defaultişlev hala biraz daha yavaştır, çünkü daha fazla kontrol yapar ve birden fazla sütun vb.
İşler

23

bir data.tableçözüm:

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

bu yaklaşım, ileri doldurma sıfırlarıyla da çalışabilir:

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

bu yöntem, ölçekli verilerde ve önemsiz olan gruplara göre ileri dolum yapmak istediğinizde çok yararlı olur data.table. grup (lar) ı mantıktan byönceki maddeye ekleyin cumsum.

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

1
Bunu gruplara göre yapma yeteneği harika!
JCWong

22

Büyük bir veri hacmi ile uğraşmak, daha verimli olabilmek için data.table paketini kullanabiliriz.

require(data.table)
replaceNaWithLatest <- function(
  dfIn,
  nameColNa = names(dfIn)[1]
){
  dtTest <- data.table(dfIn)
  setnames(dtTest, nameColNa, "colNa")
  dtTest[, segment := cumsum(!is.na(colNa))]
  dtTest[, colNa := colNa[1], by = "segment"]
  dtTest[, segment := NULL]
  setnames(dtTest, "colNa", nameColNa)
  return(dtTest)
}

2
Bir lapply doğrudan birden fazla NA sütununa uygulayabilmesi için eklenebilir:replaceNaWithLatest <- function( dfIn, nameColsNa = names(dfIn)[1] ){ dtTest <- data.table(dfIn) invisible(lapply(nameColsNa, function(nameColNa){ setnames(dtTest, nameColNa, "colNa") dtTest[, segment := cumsum(!is.na(colNa))] dtTest[, colNa := colNa[1], by = "segment"] dtTest[, segment := NULL] setnames(dtTest, "colNa", nameColNa) })) return(dtTest) }
xclotet

İlk başta bu çözümden heyecanlandım, ama aslında aynı şeyi yapmıyor. Soru, 1 veri kümesini diğeriyle doldurmakla ilgilidir. Bu cevap sadece ithamdır.
Hack-R

19

Şapkamı şuraya atmak:

library(Rcpp)
cppFunction('IntegerVector na_locf(IntegerVector x) {
  int n = x.size();

  for(int i = 0; i<n; i++) {
    if((i > 0) && (x[i] == NA_INTEGER) & (x[i-1] != NA_INTEGER)) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

Temel bir örnek ve bir karşılaştırma ölçütü oluşturun:

x <- sample(c(1,2,3,4,NA))

bench_em <- function(x,count = 10) {
  x <- sample(x,count,replace = TRUE)
  print(microbenchmark(
    na_locf(x),
    replace_na_with_last(x),
    na.lomf(x),
    na.locf(x),
    repeat.before(x)
  ), order = "mean", digits = 1)
}

Ve bazı ölçütler çalıştırın:

bench_em(x,1e6)

Unit: microseconds
                    expr   min    lq  mean median    uq   max neval
              na_locf(x)   697   798   821    814   821 1e+03   100
              na.lomf(x)  3511  4137  5002   4214  4330 1e+04   100
 replace_na_with_last(x)  4482  5224  6473   5342  5801 2e+04   100
        repeat.before(x)  4793  5044  6622   5097  5520 1e+04   100
              na.locf(x) 12017 12658 17076  13545 19193 2e+05   100

Her ihtimale karşı:

all.equal(
     na_locf(x),
     replace_na_with_last(x),
     na.lomf(x),
     na.locf(x),
     repeat.before(x)
)
[1] TRUE

Güncelleme

Sayısal bir vektör için işlev biraz farklıdır:

NumericVector na_locf_numeric(NumericVector x) {
  int n = x.size();
  LogicalVector ina = is_na(x);

  for(int i = 1; i<n; i++) {
    if((ina[i] == TRUE) & (ina[i-1] != TRUE)) {
      x[i] = x[i-1];
    }
  }
  return x;
}

15

Bu benim için çalıştı:

  replace_na_with_last<-function(x,a=!is.na(x)){
     x[which(a)[c(1,1:sum(a))][cumsum(a)+1]]
  }


> replace_na_with_last(c(1,NA,NA,NA,3,4,5,NA,5,5,5,NA,NA,NA))

[1] 1 1 1 1 3 4 5 5 5 5 5 5 5 5

> replace_na_with_last(c(NA,"aa",NA,"ccc",NA))

[1] "aa"  "aa"  "aa"  "ccc" "ccc"

hız da makul:

> system.time(replace_na_with_last(sample(c(1,2,3,NA),1e6,replace=TRUE)))


 user  system elapsed 

 0.072   0.000   0.071 

2
Bu işlev, önde gelen NA'lar olduğunda beklediğiniz şeyi yapmaz. replace_na_with_last(c(NA,1:4,NA))(yani aşağıdaki değerle doldurulur). Bu aynı zamanda varsayılan davranışıdır imputeTS::na.locf(x, na.remaining = "rev").
Ruben

Bu dava için bir varsayılan eklemek daha iyi, biraz farklı bir yaklaşım: replace_na_with_last<-function(x,p=is.na,d=0)c(d,x)[cummax(seq_along(x)*(!p(x)))+1]
Nick Nassuphis

@NickNassuphis'in cevabı kısa, tatlı, pakete bağımlı değil ve dplyr borularla iyi çalışıyor!
Kim

14

Bu işlevi deneyin. ZOO paketi gerektirmez:

# last observation moved forward
# replaces all NA values with last non-NA values
na.lomf <- function(x) {

    na.lomf.0 <- function(x) {
        non.na.idx <- which(!is.na(x))
        if (is.na(x[1L])) {
            non.na.idx <- c(1L, non.na.idx)
        }
        rep.int(x[non.na.idx], diff(c(non.na.idx, length(x) + 1L)))
    }

    dim.len <- length(dim(x))

    if (dim.len == 0L) {
        na.lomf.0(x)
    } else {
        apply(x, dim.len, na.lomf.0)
    }
}

Misal:

> # vector
> na.lomf(c(1, NA,2, NA, NA))
[1] 1 1 2 2 2
> 
> # matrix
> na.lomf(matrix(c(1, NA, NA, 2, NA, NA), ncol = 2))
     [,1] [,2]
[1,]    1    2
[2,]    1    2
[3,]    1    2

Onu geliştirmek için, bu ekleyebilirsiniz: if (!anyNA(x)) return(x).
Artem Klevtsov

13

Bir lider olmak NAbiraz kırışıklıktır, ancak lider terim olmadığında LOCF yapmanın çok okunabilir (ve vectorized) bir yolunu buluyorum eksik :

na.omit(y)[cumsum(!is.na(y))]

Genel olarak biraz daha az okunabilir bir modifikasyon çalışır:

c(NA, na.omit(y))[cumsum(!is.na(y))+1]

istenen çıktıyı verir:

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


3
bu oldukça zarif. Her durumda işe yarayıp yaramadığından emin değilim ama kesinlikle benim için çalıştı!
ABT

13

Kullanılabilen data.tableişlevi kullanabilirsiniz .nafilldata.table >= 1.12.3

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

Vektörünüz a öğesinde bir sütunsa data.table, referans olarak aşağıdakileri kullanarak da güncelleyebilirsiniz 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

Birden NAfazla sütununuz varsa ...

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

... tek seferde başvuru ile doldurabilirsiniz:

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

Bunu not et:

Şu anda yalnızca çift ve tamsayı veri türleri data.table 1.12.6desteklenmektedir [ ].

İşlev büyük olasılıkla yakında genişletilecektir; geçici bir çözüm bulduğunuz karakter, faktör ve diğer türler için açık nafill, setnafill konusuna bakın .


5

Düzenli paket bunu yapmanın basit bir yolunu sunuyor:

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

# first, transform it into a data.frame

y = 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

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

3

na.locf( NASon Gözlem İletildi) işlevleri sunan bir grup paket vardır :

  • xts - xts::na.locf
  • zoo - zoo::na.locf
  • imputeTS - imputeTS::na.locf
  • spacetime - spacetime::na.locf

Ayrıca bu işlevin farklı adlandırıldığı diğer paketler.


2

Brandon Bertelsen'in Rcpp katkılarını takip etmek. Benim için, NumericVector sürümü işe yaramadı: sadece ilk NA'nın yerini aldı. Çünküina vektörün fonksiyonun başında sadece bir kez değerlendirilmesidir.

Bunun yerine, IntegerVector işleviyle tam olarak aynı yaklaşım uygulanabilir. Aşağıdakiler benim için çalıştı:

library(Rcpp)
cppFunction('NumericVector na_locf_numeric(NumericVector x) {
  R_xlen_t n = x.size();
  for(R_xlen_t i = 0; i<n; i++) {
    if(i > 0 && !R_finite(x[i]) && R_finite(x[i-1])) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

Bir CharacterVector sürümüne ihtiyacınız varsa, aynı temel yaklaşım da işe yarar:

cppFunction('CharacterVector na_locf_character(CharacterVector x) {
  R_xlen_t n = x.size();
  for(R_xlen_t i = 0; i<n; i++) {
    if(i > 0 && x[i] == NA_STRING && x[i-1] != NA_STRING) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

int n = x.size () ve için (int i = 0; i <n; i ++) yerine double kullanılmalıdır. R'de bir vektör c ++ int boyutundan daha büyük olabilir.
İstatistikler0007

Bu işlev "R_xlen_t" döndürüyor gibi görünüyor. R uzun vektör desteği ile derlenmişse, bu ptrdiff_t; değilse, bu bir int. Düzeltme için teşekkürler!
Evan Cortens

1

İşte @ AdamO'nun çözümünün bir modifikasyonu. Bu daha hızlı çalışır, çünkü na.omitişlevi atlar . Bu, NAvektördeki değerlerin üzerine yazılır y(satır başları hariç NA).

   z  <- !is.na(y)                  # indicates the positions of y whose values we do not want to overwrite
   z  <- z | !cumsum(z)             # for leading NA's in y, z will be TRUE, otherwise it will be FALSE where y has a NA and TRUE where y does not have a NA
   y  <- y[z][cumsum(z)]

0

Aşağıdakileri denedim:

nullIdx <- as.array(which(is.na(masterData$RequiredColumn)))
masterData$RequiredColumn[nullIdx] = masterData$RequiredColumn[nullIdx-1]

nullIdx, masterData $ RequiredColumn öğesinin Null / NA değerine sahip olduğu yerde idx numarasını alır. Bir sonraki satırda, karşılık gelen Idx-1 değeri, yani her NULL / NA'dan önceki son iyi değerle değiştiriyoruz


Birden fazla ardışık eksik değer varsa bu işe yaramaz - 1 NA NAdönüşür 1 1 NA. Ayrıca, as.array()gereksiz olduğunu düşünüyorum .
Gregor Thomas

0

Diğer önerilerden daha etkili olup olmadığından emin olmasam da, bu benim için çalıştı.

rollForward <- function(x){
  curr <- 0
  for (i in 1:length(x)){
    if (is.na(x[i])){
      x[i] <- curr
    }
    else{
      curr <- x[i]
    }
  }
  return(x)
}

0
fill.NAs <- function(x) {is_na<-is.na(x); x[Reduce(function(i,j) if (is_na[j]) i else j, seq_len(length(x)), accumulate=T)]}

fill.NAs(c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))

[1] NA  2  2  2  2  3  3  4  4  4

Azalt, benzer görevler için yararlı olabilecek hoş bir işlevsel programlama konseptidir. Ne yazık ki R'de repeat.beforeyukarıdaki cevaptan ~ 70 kat daha yavaştır .


0

Ben şahsen bu fonksiyonu kullanıyorum. Ne kadar hızlı veya yavaş olduğunu bilmiyorum. Ama işini kütüphaneler kullanmak zorunda kalmadan yapıyor.

replace_na_with_previous<-function (vector) {
        if (is.na(vector[1])) 
            vector[1] <- na.omit(vector)[1]
        for (i in 1:length(vector)) {
            if ((i - 1) > 0) {
                if (is.na(vector[i])) 
                    vector[i] <- vector[i - 1]
            }
        }
        return(vector)
    }

bu işlevi bir veri çerçevesinde uygulamak istiyorsanız, veri kareniz df olarak adlandırılırsa

df[]<-lapply(df,replace_na_with_previous)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.