Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

turn elements to NaN after first negative

Tags:

matlab

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.

like image 869
siegel Avatar asked Dec 06 '22 22:12

siegel


2 Answers

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.

like image 105
rayryeng Avatar answered Jan 13 '23 00:01

rayryeng


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))
like image 21
Benoit_11 Avatar answered Jan 13 '23 02:01

Benoit_11