Zamiana NAs W R na najbliższą wartość
Szukam czegoś podobnego do na.locf()
w zoo
, ale zamiast zawsze używać previous non-NA
chcę użyć nearest non-NA
. Niektóre przykładowe dane:
dat <- c(1, 3, NA, NA, 5, 7)
Zastąpienie NA
przez na.locf
(3 przenosi się do przodu):
library(zoo)
na.locf(dat)
# 1 3 3 3 5 7
I na.locf
z fromLast
ustawionym na TRUE
(5 jest przenoszone do tyłu):
na.locf(dat, fromLast = TRUE)
# 1 3 5 5 5 7
Ale chciałbym, aby była najbliższa NIE-NA
. W moim przykładzie oznacza to, że 3 należy przenieść do przodu do pierwszego NA
, a 5 należy przenieść do tyłu do drugiego NA
:
1 3 3 5 5 7
Mam zakodowane rozwiązanie, ale chciałem się upewnić, że nie wymyślam koła na nowo. Czy coś już tu krąży?
Dla twojej wiadomości, mój obecny kod jest następujący. Być może, jeśli nic innego, ktoś może zasugerować, jak uczynić go bardziej wydajnym. Czuję, że brakuje mi oczywistego sposobu, aby to poprawić: {]}
na.pos <- which(is.na(dat))
if (length(na.pos) == length(dat)) {
return(dat)
}
non.na.pos <- setdiff(seq_along(dat), na.pos)
nearest.non.na.pos <- sapply(na.pos, function(x) {
return(which.min(abs(non.na.pos - x)))
})
dat[na.pos] <- dat[non.na.pos[nearest.non.na.pos]]
Aby odpowiedzieć na pytania smci poniżej:
- nie, każdy wpis może być NA
- jeśli wszystkie są NA, zostaw je tak jak jest Nie. Moje obecne rozwiązanie domyślnie ma wartość lefthand najbliższą, ale to nie ma znaczenia
- te rzędy to zazwyczaj kilkaset tysięcy elementów, więc teoretycznie górna granica wynosiłaby kilkaset tysięcy. W rzeczywistości byłoby to nie więcej niż kilka tu i tam, zazwyczaj jeden.
Update więc okazuje się, że idziemy w innym kierunku w sumie, ale to była nadal ciekawa dyskusja. Dzięki wszystkim!
6 answers
Tutaj jest bardzo szybki. Wykorzystuje findInterval
aby dowiedzieć się, jakie dwie pozycje powinny być brane pod uwagę dla każdej NA
w oryginalnych danych:
f1 <- function(dat) {
N <- length(dat)
na.pos <- which(is.na(dat))
if (length(na.pos) %in% c(0, N)) {
return(dat)
}
non.na.pos <- which(!is.na(dat))
intervals <- findInterval(na.pos, non.na.pos,
all.inside = TRUE)
left.pos <- non.na.pos[pmax(1, intervals)]
right.pos <- non.na.pos[pmin(N, intervals+1)]
left.dist <- na.pos - left.pos
right.dist <- right.pos - na.pos
dat[na.pos] <- ifelse(left.dist <= right.dist,
dat[left.pos], dat[right.pos])
return(dat)
}
I tu testuję:
# sample data, suggested by @JeffAllen
dat <- as.integer(runif(50000, min=0, max=10))
dat[dat==0] <- NA
# computation times
system.time(r0 <- f0(dat)) # your function
# user system elapsed
# 5.52 0.00 5.52
system.time(r1 <- f1(dat)) # this function
# user system elapsed
# 0.01 0.00 0.03
identical(r0, r1)
# [1] TRUE
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-10 13:06:20
Kod poniżej. Pierwsze pytanie nie było do końca dobrze zdefiniowane, poprosiłem o te wyjaśnienia:
- czy jest zagwarantowane, że przynajmniej pierwsze i / lub ostatnie wpisy nie są na? [Nie]
- co zrobić, jeśli wszystkie wpisy w wierszu są NA? [Leave as-is]
- czy zależy ci na tym, jak więzy są dzielone, czyli jak traktować środek NA w
1 3 NA NA NA 5 7
? [Don ' t-care / left] - Czy masz górną krawędź (s) na najdłuższym przylegającym przęśle NAs z rzędu? (Myślę o rozwiązaniu rekurencyjnym, jeśli S jest małe. Lub rozwiązanie dataframe z
ifelse
jeśli S jest duże i liczba wierszy i cols jest duże.) [najgorszy przypadek S może być patologicznie duży, stąd rekurencja nie powinna być używana]
nearest.non.na.pos
i seryjny przydział dat[na.pos] <- dat[non.na.pos[nearest.non.na.pos]]
Dla dużej szczeliny o długości G wystarczy obliczyć, że pierwszy (G/2, zaokrąglić) przedmioty wypełniają się od lewej, reszta od prawej. (Mogę napisać odpowiedź używając ifelse
ale wyglądałoby podobnie.)
Czy Twoje kryteria runtime , wydajność big-O, zużycie pamięci tymczasowej lub czytelność kodu?
Możliwe poprawki:
- wystarczy obliczyć
N <- length(dat)
once - common-case speed enhance:
if (length(na.pos) == 0)
pomiń wiersz, ponieważ nie ma NAs -
if (length(na.pos) == length(dat)-1)
(rzadki) przypadek, w którym istnieje tylko jeden nie-NA wpis stąd wypełniamy nim cały wiersz
Rozwiązanie konturu:
Niestety na.locf nie działa na całej ramce danych, musisz użyć sapply, row-wise:
na.fill_from_nn <- function(x) {
row.na <- is.na(x)
fillFromLeft <- na.locf(x, na.rm=FALSE)
fillFromRight <- na.locf(x, fromLast=TRUE, na.rm=FALSE)
disagree <- rle(fillFromLeft!=fillFromRight)
for (loc in (disagree)) { ... resolve conflicts, row-wise }
}
sapply(dat, na.fill_from_nn)
Alternatywnie, ponieważ jak mówisz sąsiadujące NAs są rzadkie, zrób szybką i głupią ifelse
/ align = "left" / To będzie działać mądrze ramki danych = > sprawia, że wspólna sprawa szybko. Następnie obsłuż wszystkie inne przypadki za pomocą pętli for-loop. (Wpłynie to na tiebreak na środkowych elementach w długi czas NAs, ale mówisz, że cię to nie obchodzi.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-09 22:10:31
Nie mogę wymyślić oczywistego prostego rozwiązania, ale po przyjrzeniu się sugestiom (szczególnie sugestii smci użycia rle
) wymyśliłem skomplikowaną funkcję, która wydaje się być bardziej wydajna.
To jest kod, wyjaśnię poniżej:
# Your function
your.func = function(dat) {
na.pos <- which(is.na(dat))
if (length(na.pos) == length(dat)) {
return(dat)
}
non.na.pos <- setdiff(seq_along(dat), na.pos)
nearest.non.na.pos <- sapply(na.pos, function(x) which.min(abs(non.na.pos - x)))
dat[na.pos] <- dat[non.na.pos[nearest.non.na.pos]]
dat
}
# My function
my.func = function(dat) {
nas=is.na(dat)
if (!any(!nas)) return (dat)
t=rle(nas)
f=sapply(t$lengths[t$values],seq)
a=unlist(f)
b=unlist(lapply(f,rev))
x=which(nas)
l=length(dat)
dat[nas]=ifelse(a>b,dat[ ifelse((x+b)>l,x-a,x+b) ],dat[ifelse((x-a)<1,x+b,x-a)])
dat
}
# Test
n = 100000
test.vec = 1:n
set.seed(1)
test.vec[sample(test.vec,n/4)]=NA
system.time(t1<-my.func(test.vec))
system.time(t2<-your.func(test.vec)) # 10 times speed improvement on my machine
# Verify
any(t1!=t2)
Moja funkcja opiera się na rle
. Czytam powyższe komentarze, ale wygląda na to, że rle
działa dobrze dla NA
. Najłatwiej jest to wyjaśnić małym przykładem.
Jeśli zacznę od wektor:
dat=c(1,2,3,4,NA,NA,NA,8,NA,10,11,12,NA,NA,NA,NA,NA,18)
Potem dostaję pozycje wszystkich NAs:
x=c(5,6,7,8,13,14,15,16,17)
Następnie, dla każdego "biegu" NAs tworzę sekwencję od 1 do długości BIEGU:
a=c(1,2,3,1,1,2,3,4,5)
Potem robię to jeszcze raz, ale odwracam sekwencję:
b=c(3,2,1,1,5,4,3,2,1)
Teraz mogę po prostu porównać wektory a i b: Jeśli a b to spójrz do przodu i chwyć wartość W x + b. reszta to tylko obsługa przypadków narożnych, gdy masz wszystkie nas lub na działa na końcu lub na początku. wektor.
Prawdopodobnie istnieje lepsze, prostsze rozwiązanie, ale mam nadzieję, że to pomoże Ci zacząć.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:06
Oto moje zadanie. Nigdy nie lubię widzieć pętli for W R, ale w przypadku wektora słabo-NA wygląda na to, że będzie on w rzeczywistości bardziej wydajny(wskaźniki wydajności poniżej). Treść kodu znajduje się poniżej.
#get the index of all NA values
nas <- which(is.na(dat))
#get the Boolean map of which are NAs, used later to determine which values can be used as a replacement, and which are just filled-in NA values
namask <- is.na(dat)
#calculate the maximum size of a run of NAs
length <- getLengthNAs(dat);
#the furthest away an NA value could be is half of the length of the maximum NA run
windowSize <- ceiling(length/2)
#loop through all NAs
for (thisIndex in nas){
#extract the neighborhood of this NA
neighborhood <- dat[(thisIndex-windowSize):(thisIndex+windowSize)]
#any already-filled-in values which were NA can be replaced with NAs
neighborhood[namask[(thisIndex-windowSize):(thisIndex+windowSize)]] <- NA
#the center of this neighborhood
center <- windowSize + 1
#compute the difference within this neighborhood to find the nearest non-NA value
delta <- center - which(!is.na(neighborhood))
#find the closest replacement
replacement <- delta[abs(delta) == min(abs(delta))]
#in case length > 1, just pick the first
replacement <- replacement[1]
#replace with the nearest non-NA value.
dat[thisIndex] <- dat[(thisIndex - (replacement))]
}
Podobał mi się kod, który zaproponowałeś, ale zauważyłem, że obliczamy delta pomiędzy każdą wartością NA I każdym innym indeksem nie-na w macierzy. Myślę, że to był największy występ. Zamiast tego, po prostu wyodrębnić minimalną wielkość sąsiedztwa lub okna wokół każdy NA i znajdź najbliższą wartość inną niż NA w tym oknie.
Wydajność skaluje się liniowo na liczbę NAs i rozmiar okna - gdzie rozmiar okna jest (pułap) o połowę krótszy od maksymalnego uruchomienia NAs. Aby obliczyć długość maksymalnego uruchomienia serwera NAs, możesz użyć następującej funkcji:
getLengthNAs <- function(dat){
nas <- which(is.na(dat))
spacing <- diff(nas)
length <- 1;
while (any(spacing == 1)){
length <- length + 1;
spacing <- diff(which(spacing == 1))
}
length
}
Porównanie Wydajności
#create a test vector with 10% NAs and length 50,000.
dat <- as.integer(runif(50000, min=0, max=10))
dat[dat==0] <- NA
#the a() function is the code posted in the question
a <- function(dat){
na.pos <- which(is.na(dat))
if (length(na.pos) == length(dat)) {
return(dat)
}
non.na.pos <- setdiff(seq_along(dat), na.pos)
nearest.non.na.pos <- sapply(na.pos, function(x) {
return(which.min(abs(non.na.pos - x)))
})
dat[na.pos] <- dat[non.na.pos[nearest.non.na.pos]]
dat
}
#my code
b <- function(dat){
#the same code posted above, but with some additional helper code to sanitize the input
if(is.null(dat)){
return(NULL);
}
if (all(is.na(dat))){
stop("Can't impute NAs if there are no non-NA values.")
}
if (!any(is.na(dat))){
return(dat);
}
#starts with an NA (or multiple), handle these
if (is.na(dat[1])){
firstNonNA <- which(!is.na(dat))[1]
dat[1:(firstNonNA-1)] <- dat[firstNonNA]
}
#ends with an NA (or multiple), handle these
if (is.na(dat[length(dat)])){
lastNonNA <- which(!is.na(dat))
lastNonNA <- lastNonNA[length(lastNonNA)]
dat[(lastNonNA+1):length(dat)] <- dat[lastNonNA]
}
#get the index of all NA values
nas <- which(is.na(dat))
#get the Boolean map of which are NAs, used later to determine which values can be used as a replacement, and which are just filled-in NA values
namask <- is.na(dat)
#calculate the maximum size of a run of NAs
length <- getLengthNAs(dat);
#the furthest away an NA value could be is half of the length of the maximum NA run
#if there's a run at the beginning or end, then the nearest non-NA value could possibly be `length` away, so we need to keep the window large for that case.
windowSize <- ceiling(length/2)
#loop through all NAs
for (thisIndex in nas){
#extract the neighborhood of this NA
neighborhood <- dat[(thisIndex-windowSize):(thisIndex+windowSize)]
#any already-filled-in values which were NA can be replaced with NAs
neighborhood[namask[(thisIndex-windowSize):(thisIndex+windowSize)]] <- NA
#the center of this neighborhood
center <- windowSize + 1
#compute the difference within this neighborhood to find the nearest non-NA value
delta <- center - which(!is.na(neighborhood))
#find the closest replacement
replacement <- delta[abs(delta) == min(abs(delta))]
#in case length > 1, just pick the first
replacement <- replacement[1]
#replace with the nearest non-NA value.
dat[thisIndex] <- dat[(thisIndex - (replacement))]
}
dat
}
#nograpes' answer on this question
c <- function(dat){
nas=is.na(dat)
if (!any(!nas)) return (dat)
t=rle(nas)
f=sapply(t$lengths[t$values],seq)
a=unlist(f)
b=unlist(lapply(f,rev))
x=which(nas)
l=length(dat)
dat[nas]=ifelse(a>b,dat[ ifelse((x+b)>l,x-a,x+b) ],dat[ifelse((x-a)<1,x+b,x-a)])
dat
}
#run 10 times each to get average performance.
sum <- 0; for (i in 1:10){ sum <- sum + system.time(a(dat))["elapsed"];}; cat ("A: ", sum/10)
A: 5.059
sum <- 0; for (i in 1:10){ sum <- sum + system.time(b(dat))["elapsed"];}; cat ("B: ", sum/10)
B: 0.126
sum <- 0; for (i in 1:10){ sum <- sum + system.time(c(dat))["elapsed"];}; cat ("C: ", sum/10)
C: 0.287
Tak wygląda ten kod (przynajmniej w tych warunkach), oferuje około 40x speedup od oryginalnego kodu posted in pytanie, and a 2.2 x speedup over @nograpes' answer below (though I imagine an rle
solution would certainly be faster in some situations-including a more NA-rich vector).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-10 01:06:34
Prędkość jest około 3-4x wolniejsza od wybranej odpowiedzi. Mój jest dość prosty. Jest to również rzadka pętla while.
f2 <- function(x){
# check if all are NA to skip loop
if(!all(is.na(x))){
# replace NA's until they are gone
while(anyNA(x)){
# replace from the left
x[is.na(x)] <- c(NA,x[1:(length(x)-1)])[is.na(x)]
# replace from the right
x[is.na(x)] <- c(x[-1],NA)[is.na(x)]
}
}
# return original or fixed x
x
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-05 05:09:32
Lubię wszystkie rygorystyczne rozwiązania. Chociaż nie bezpośrednio to, o co pytano, znalazłem ten post szukając rozwiązania do wypełniania wartości NA interpolacją. Po przejrzeniu tego postu odkryłem na.wypełnienie na obiekcie zoo (wektor, czynnik lub macierz):
Z
Z1
Zwróć uwagę na płynne przejście przez wartości NA
1.0 2.0 3.0 4.0 5.0 6.0 5.0 4.0 3.0 2.0 3.0 4.0 5.0 6.0 5.3 4.6 4.0 6.0 7.0 7.0
Być może to pomoże
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 09:11:51