4
votes

I've got a three dimensional array in Matlab. The first dimension is time, the second is Humidity, and the third is Temperature. If a Temperature value is < 0, I want every subsequent temperature value to be turned to NaN.

For example if the array is:

>> sampl = randn(4,3,2)

sampl(:,:,1) =

  0.79487     0.71017     -0.39167
  0.51754     -1.3068     0.84166
  0.49461     0.74159     0.082784
  0.66393     1.4677      0.31467


sampl(:,:,2) =

  0.78981      1.3096        1.0434
 -0.80122      0.16037      -1.0682
 -0.32565      -2.1182      -0.31723
  0.28468      0.70708      1.4797

What's the most efficient way to turn this into:

sampl(:,:,1) = 

  0.79487     0.71017     NaN
  0.51754     NaN         NaN
  0.49461     NaN         NaN
  0.66393     NaN         NaN


sampl(:,:,2) =

  0.78981     1.3096      1.0434
  NaN         0.16037     NaN
  NaN         NaN         NaN
  NaN         NaN         NaN

Specifically, for a particular slice, we want to process along each column, and as soon as we encounter a negative number in one column, we want that location to be NaN as well as all row locations for that same column that follow this NaN value to also be NaN.

3
Can you further explain how you went from your sample input to sample output? It doesn't seem to correspond with your problem statement. - rayryeng
Never mind, I think I figured it out. For a particular slice, we want to process along each column, and as soon as you encounter a negative number in one column, do you want that location to be NaN as well as all row locations for that same column that follow this NaN value? - rayryeng
That's exactly right. Better explanation than I gave. - siegel
Ah ok :) lol. Yeah I'm trying to figure out what it was you were trying to get at. Seems I have a knack for doing that. Divakar calls me a "mentalist" because of it. With that, I'm writing an answer. Give me a few moments. - rayryeng
You're welcome! I wrote an answer, and also included this explanation in your post. Hope that's ok! Let me know if my answer is acceptable. - rayryeng

3 Answers

7
votes

Another easy way is to find those locations that are negative in the original matrix, creating another matrix that sets those values toNaN, invoke a cumsum or a cumulative sum along all the rows for each column in each slice of this new matrix, then set the corresponding locations in this cumsum result to NaN in the original matrix to obtain the final result:

>> out = sampl;
>> out(out < 0) = NaN;
>> out = cumsum(out);
>> sampl(isnan(out)) = NaN

sampl(:,:,1) =

    0.7949    0.7102       NaN
    0.5175       NaN       NaN
    0.4946       NaN       NaN
    0.6639       NaN       NaN


sampl(:,:,2) =

    0.7898    1.3096    1.0434
       NaN    0.1604       NaN
       NaN       NaN       NaN
       NaN       NaN       NaN

The reason why cumsum is useful here is because we would essentially examine each column independently along its rows and keep accumulating over all of the rows for each column which has valid entries until we hit a NaN value for a column. After this value, subsequent values in the cumsum would become NaN for each column in each slice independently. As such, after we hit the first NaN in a column, no matter what values we encounter after (NaN or a valid number), the result in the cumsum would still be NaN. This effectively propagates NaN values after we encounter the first negative in a column for your matrix. The last bit is to find those locations in this matrix and set the corresponding locations in the original matrix to NaN, thus giving our result.

4
votes

Here is a solution using accumarray.

First, get the number of rows and reshape sampl to get a 2D array; it's easier to work with:

NumRow = size(sampl,1);

a = reshape(sampl,NumRow,[])

a looks like this:

a =

    0.7949    0.7102   -0.3917    0.7898    1.3096    1.0434
    0.5175   -1.3068    0.8417   -0.8012    0.1604   -1.0682
    0.4946    0.7416    0.0828   -0.3256   -2.1182   -0.3172
    0.6639    1.4677    0.3147    0.2847    0.7071    1.4797

Then find the first row index, for each column, that is negative:

[row,col] = find(a<0);
b = accumarray(col,row,[],@min);

Now b looks like this:

b =

     0
     2
     1
     2
     3
     2

Before inserting NaN's, change the 0 so that whole columns are not filled with NaN's using the colon operator (see next step):

b(b==0) = NumRow+1;

Finally loop through your array and insert NaN's starting from the corresponding index in b until the last row for every column. Also reshape a to get the same size as your initial array:

for k = 1:size(a,2)
a(b(k):NumRow,k) = NaN;
end

out = reshape(a,size(sampl))

Out:

out(:,:,1) =

    0.7949    0.7102       NaN
    0.5175       NaN       NaN
    0.4946       NaN       NaN
    0.6639       NaN       NaN


out(:,:,2) =

    0.7898    1.3096    1.0434
       NaN    0.1604       NaN
       NaN       NaN       NaN
       NaN       NaN       NaN

Here is the whole code that you can copy/paste to run:

clear
clc

NumRow = size(sampl,1);

a = reshape(sampl,NumRow,[])

[row,col] = find(a<0);
b = accumarray(col,row,[],@min)

b(b==0) = NumRow+1;

for k = 1:size(a,2)
a(b(k):NumRow,k) = NaN;
end

out = reshape(a,size(sampl))
4
votes

Missing a bsxfun-based solution, anyone?

[val, ind] = max(sampl<0); %// ind gives row index of first negative value, if any
ind(~val) = inf; %// if no negative values, set ind to inf so it has no effect
sampl(bsxfun(@ge, (1:size(sampl,1)).', ind)) = NaN; %'// logical indexing to fill NaNs