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
.
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.
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With