Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Here's my stab at it. I never like to see a for loop in R, but in the case of a sparsely-NA vector, it looks like it will actually be more efficient (performance metrics below). The gist of the code is below.</p> <pre><code> #get the index of all NA values nas &lt;- 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 &lt;- is.na(dat) #calculate the maximum size of a run of NAs length &lt;- getLengthNAs(dat); #the furthest away an NA value could be is half of the length of the maximum NA run windowSize &lt;- ceiling(length/2) #loop through all NAs for (thisIndex in nas){ #extract the neighborhood of this NA neighborhood &lt;- dat[(thisIndex-windowSize):(thisIndex+windowSize)] #any already-filled-in values which were NA can be replaced with NAs neighborhood[namask[(thisIndex-windowSize):(thisIndex+windowSize)]] &lt;- NA #the center of this neighborhood center &lt;- windowSize + 1 #compute the difference within this neighborhood to find the nearest non-NA value delta &lt;- center - which(!is.na(neighborhood)) #find the closest replacement replacement &lt;- delta[abs(delta) == min(abs(delta))] #in case length &gt; 1, just pick the first replacement &lt;- replacement[1] #replace with the nearest non-NA value. dat[thisIndex] &lt;- dat[(thisIndex - (replacement))] } </code></pre> <p>I liked the code you proposed, but I noticed that we were calculating the delta between every NA value and every other non-NA index in the matrix. I think this was the biggest performance hog. Instead, I just extract the minimum-sized neighborhood or window around each NA and find the nearest non-NA value within that window.</p> <p>So the performance scales linearly on the number of NAs and the window size -- where the window size is (the ceiling of) half the length of the maximum run of NAs. To calculate the length of the maximum run of NAs, you can use the following function:</p> <pre><code>getLengthNAs &lt;- function(dat){ nas &lt;- which(is.na(dat)) spacing &lt;- diff(nas) length &lt;- 1; while (any(spacing == 1)){ length &lt;- length + 1; spacing &lt;- diff(which(spacing == 1)) } length } </code></pre> <h2>Performance Comparison</h2> <pre><code>#create a test vector with 10% NAs and length 50,000. dat &lt;- as.integer(runif(50000, min=0, max=10)) dat[dat==0] &lt;- NA #the a() function is the code posted in the question a &lt;- function(dat){ na.pos &lt;- which(is.na(dat)) if (length(na.pos) == length(dat)) { return(dat) } non.na.pos &lt;- setdiff(seq_along(dat), na.pos) nearest.non.na.pos &lt;- sapply(na.pos, function(x) { return(which.min(abs(non.na.pos - x))) }) dat[na.pos] &lt;- dat[non.na.pos[nearest.non.na.pos]] dat } #my code b &lt;- 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 &lt;- which(!is.na(dat))[1] dat[1:(firstNonNA-1)] &lt;- dat[firstNonNA] } #ends with an NA (or multiple), handle these if (is.na(dat[length(dat)])){ lastNonNA &lt;- which(!is.na(dat)) lastNonNA &lt;- lastNonNA[length(lastNonNA)] dat[(lastNonNA+1):length(dat)] &lt;- dat[lastNonNA] } #get the index of all NA values nas &lt;- 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 &lt;- is.na(dat) #calculate the maximum size of a run of NAs length &lt;- 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 &lt;- ceiling(length/2) #loop through all NAs for (thisIndex in nas){ #extract the neighborhood of this NA neighborhood &lt;- dat[(thisIndex-windowSize):(thisIndex+windowSize)] #any already-filled-in values which were NA can be replaced with NAs neighborhood[namask[(thisIndex-windowSize):(thisIndex+windowSize)]] &lt;- NA #the center of this neighborhood center &lt;- windowSize + 1 #compute the difference within this neighborhood to find the nearest non-NA value delta &lt;- center - which(!is.na(neighborhood)) #find the closest replacement replacement &lt;- delta[abs(delta) == min(abs(delta))] #in case length &gt; 1, just pick the first replacement &lt;- replacement[1] #replace with the nearest non-NA value. dat[thisIndex] &lt;- dat[(thisIndex - (replacement))] } dat } #nograpes' answer on this question c &lt;- 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&gt;b,dat[ ifelse((x+b)&gt;l,x-a,x+b) ],dat[ifelse((x-a)&lt;1,x+b,x-a)]) dat } #run 10 times each to get average performance. sum &lt;- 0; for (i in 1:10){ sum &lt;- sum + system.time(a(dat))["elapsed"];}; cat ("A: ", sum/10) A: 5.059 sum &lt;- 0; for (i in 1:10){ sum &lt;- sum + system.time(b(dat))["elapsed"];}; cat ("B: ", sum/10) B: 0.126 sum &lt;- 0; for (i in 1:10){ sum &lt;- sum + system.time(c(dat))["elapsed"];}; cat ("C: ", sum/10) C: 0.287 </code></pre> <p>So it looks like this code (at least under these conditions), offers about a 40X speedup from the original code posted in the question, and a 2.2X speedup over @nograpes' answer below (though I imagine an <code>rle</code> solution would certainly be faster in some situations -- including a more NA-rich vector).</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload