I'm trying to average replicate data in MATLAB and running into some difficulty. The variables are depth, Var1, Var2. Sometimes there is a replicate in Var1, sometimes there is a replicate in Var2, sometimes there is a replicate for both Var1 and Var2 for a given depth. So the matrix might look something like this:
1 0.2 1,
2 0.5 3,
2 0.7 NaN,
3 0.1 5,
3 0.7 6,
4 0.3 4,
...
depth is the unique identifier so I would like to create a matrix with [depth, Var1, Var2] that looks like this:
1 0.2 1,
2 0.6 3,
3 0.4 5.5,
4 0.3 4,
...
The function accumarray would work if I had an n-by-2 matrix, but this is n-by-3. Any recommendations on how to proceed?
This should work
a=[1 0.2 1; 2 0.5 3; 2 0.7 NaN; 3 0.1 5; 3 0.7 6; 4 0.3 4];
depths = unique(a(:,1));
b=nan(length(depths),3);
for ct = 1:length(depths)
b(ct,:)=mean(a(a(:,1)==depths(ct),:),1,'omitnan');
end
result
b =
1.0000 0.2000 1.0000
2.0000 0.6000 3.0000
3.0000 0.4000 5.5000
4.0000 0.3000 4.0000
A bit naive implementation with accumarray which loops over the variables.
A = [1 0.2 1
2 0.5 3
2 0.7 NaN
3 0.1 5
3 0.7 6
4 0.3 4];
result = zeros([numel(unique(A(:,1))) size(A,2)]);
result(:,1) = unique(A(:,1));
for ii = 2:size(A,2)
result(:,ii) = accumarray(A(:,1),A(:,ii),[],#mean);
end
Related
How do I normalize each slice of a 3D matrix? I tried like this:
a=rand(1,100,3481);
a= (a - min(a)) ./ (max(a)-min(a)); %
By right each slice of matrix should ranges from 0 to 1. But that is not the case, I don't find 1 in some of the slices. As I inspected, min(a) and max(a) returned the respective value in 3D. Thus it should be of no issue using the code above. Is there something I missed for 3D matrix? Thanks in advance!
We need to find the minimum and maximum values for each of those 2D slices and then we can use bsxfun to do those operations in a vectorized manner with help from permute to let the singleton dims align properly to let bsxfun do its broadcasting job (or use reshape there).
Hence, the implementation would be -
mins = min(reshape(a,[],size(a,3)));
maxs = max(reshape(a,[],size(a,3)));
a_offsetted = bsxfun(#minus, a, permute(mins,[1,3,2]));
a_normalized = bsxfun(#rdivide, a_offsetted, permute(maxs-mins,[1,3,2]))
Sample input, output -
>> a
a(:,:,1) =
2 8 2 2
8 3 8 2
a(:,:,2) =
8 1 1 5
4 9 8 6
a(:,:,3) =
7 9 3 5
6 2 6 5
a(:,:,4) =
9 3 4 9
7 1 9 9
>> a_normalized
a_normalized(:,:,1) =
0 1.0000 0 0
1.0000 0.1667 1.0000 0
a_normalized(:,:,2) =
0.8750 0 0 0.5000
0.3750 1.0000 0.8750 0.6250
a_normalized(:,:,3) =
0.7143 1.0000 0.1429 0.4286
0.5714 0 0.5714 0.4286
a_normalized(:,:,4) =
1.0000 0.2500 0.3750 1.0000
0.7500 0 1.0000 1.0000
My option would be without reshaping as it is sometimes bit difficult to understand. I use min max with the dimension you want want to use for normalization with repmat to clone...:
a=rand(1,100,3481);
a_min2 = min(a,[],2);
a_max2 = max(a,[],2);
a_norm2 = (a - repmat(a_min2,[1 size(a,2) 1]) ) ./ repmat( (a_max2-a_min2),[1 size(a,2) 1]);
or if normalization on 3rd dim...
a_min3 = min(a,[],3);
a_max3 = max(a,[],3);
a_norm3 = (a - repmat(a_min3,[1 1 size(a,3)]) ) ./ repmat( (a_max3-a_min3),[1 1 size(a,3)]);
I have 2 matrices as follows:
a_lab = [4, 3, 5, 6, 7, 1;
1, 2, 3, 4, 6, 1;
4, 5, 3, 2, 1, 5];
a = [0.6, 0.4, 0.1, 0.05, 0.01, 0.2;
0.16, 0.4, 0.1, 0.15, 0.01, 0.22;
0.6, 0.24, 0.11, 0.05, 0.11, 0.2];
I sort 'a' row-wise and obtain the indices:
[a_sorted, idx] = sort(a, 2, 'descend');
0.6000 0.4000 0.2000 0.1000 0.0500 0.0100
0.4000 0.2200 0.1600 0.1500 0.1000 0.0100
0.6000 0.2400 0.2000 0.1100 0.1100 0.0500
I now want to sort a_lab using idx, so the matrices a and a_lab stay in registration. I can sort one row of a_lab using one row of idx, like this:
a_lab_sorted = a_lab(1, idx(1, :));
But, how can I sort all rows of a_lab in one go (without a for loop)? This:
a_lab_sorted = a_lab(:, idx);
...is not correct, it produces a 3x18 matrix. Thanks.
The other question suggested as a duplicate uses a for loop. I am asking for a non-loop solution. Thanks.
You can use a combination of sub2ind and repmat like this:
[m,n] = size(idx);
a_lab_sorted2 = a_lab(sub2ind([m n],repmat(1:m,n,1).',idx))
The indexes for the columns are given by idx and repmat(1:m,n,1).' gives you the indexes of the colums as follows:
1 1 1 1 1 1
2 2 2 2 2 2
3 3 3 3 3 3
These can now be converted by sub2ind which gives you a matrix of linear indexes. With that matrix, you can get your a_lab_sorted.
This answer is based on Andrew Janke's answer here.
You can simply add the length of your row to each row of your idx index matrix.
So
s = size(a_lab);
idx_lab = idx + [0:s(2):(s(1)-1)*s(2)]' % so, idx + [0;6;12]
a_lab_sorted = a_lab(idx_lab);
Using the fact that MATLAB allows you to process all of the values in a matrix using a single arithmetic operator or function.
A = 1 0 0
0 1 0
0 0 1
A = A+1
A = 2 1 1
1 2 1
1 1 2
You need to convert idx to a linear index:
a_lab_sorted = a_lab(bsxfun(#plus, (1:size(a,1)).', (idx-1)*size(a,1)));
I have the following two matrices
c=[1 0 1.05
1 3 2.05
1 6 2.52
1 9 0.88
2 0 2.58
2 3 0.53
2 6 3.69
2 9 0.18
3 0 3.22
3 3 1.88
3 6 3.98]
f=[1 6 3.9
1 9 9.1
1 12 9
2 0 0.3
2 3 0.9
2 6 1.2
2 9 2.5
3 0 2.7]
And the final matrix should be
n=[1 6 2.52 3.9
1 9 0.88 9.1
2 0 2.58 0.3
2 3 0.53 0.9
2 6 3.69 1.2
2 9 0.18 2.5
3 0 3.22 2.7]
The code I used gives as a result only the last row of the previous matrix [n].
for j=1
for i=1:rs1
for k=1
for l=1:rs2
if f(i,j)==c(l,k) && f(i,j+1)==c(l,k+1)
n=[f(i,j),f(i,j+1),f(i,j+2), c(l,k+2)];
end
end
end
end
end
Can anyone help me on this?
Is there something more simple?
Thanks in advance
You should learn to use set operations and avoid loops wherever possible. Here intersect could be extremely useful:
[u, idx_c, idx_f] = intersect(c(:, 1:2) , f(:, 1:2), 'rows');
n = [c(idx_c, :), f(idx_f, end)];
Explanation: by specifying the 'rows' flag, intersect finds the common rows in c and f, and their indices are given in idx_c and idx_f respectively. Use vector subscripting to extract matrix n.
Example
Let's use the example from your question:
c = [1 0 1.05;
1 3 2.05
1 6 2.52
1 9 0.88
2 0 2.58
2 3 0.53
2 6 3.69
2 9 0.18
3 0 3.22
3 3 1.88
3 6 3.98];
f = [1 6 3.9
1 9 9.1
1 12 9
2 0 0.3
2 3 0.9
2 6 1.2
2 9 2.5
3 0 2.7];
[u, idx_c, idx_f] = intersect(c(:, 1:2) , f(:, 1:2), 'rows');
n = [c(idx_c, :), f(idx_f, end)];
This should yield the desired result:
n =
1.0000 6.0000 2.5200 3.9000
1.0000 9.0000 0.8800 9.1000
2.0000 0 2.5800 0.3000
2.0000 3.0000 0.5300 0.9000
2.0000 6.0000 3.6900 1.2000
2.0000 9.0000 0.1800 2.5000
3.0000 0 3.2200 2.7000
According to this answer on Mathworks support you can use join from the statistics toolbox, specifically in your case, an inner join.
Unfortunately I don't have access to my computer with matlab on it, but give it a try and let us know how/if it works.
You can reduce the number of loops by comparing both the first and second columns of at once, then using the "all" function to only collapse the values if they both match. The following snippet replicates the "n" array you had provided.
n = [];
for r1 = 1:size(c, 1)
for r2 = 1:size(f,1)
if all(c(r1, [1 2]) == f(r2, [1 2]))
n(end+1, 1:4) = [c(r1,:) f(r2,3)];
end
end
end
If you insist on doing this in a loop you need to give n the proper dimension according
to the loop counter you are using, or concatenate it to itself of each iteration (this can be very slow for big matrices). For example, writing:
for j=1
for i=1:rs1
for k=1
for l=1:rs2
m=m+1;
if f(i,j)==c(l,k) && f(i,j+1)==c(l,k+1)
n(m,:)=[f(i,j),f(i,j+1),f(i,j+2), c(l,k+2)];
end
end
end
end
end
will save into the m-th row the for numbers when the loop reaches a counter value of m.
However, just be aware that this can be done also without a nested loop and an if condition, in a vectorized way. For example, instead of the condition if f(i,j)==c(l,k)... you can use ismember etc...
How about without any for loops at all (besides in native code)
mf = size(f,1);
mc = size(c,1);
a = repmat(c(:,1:2),1,mf);
b = repmat(reshape((f(:,1:2))',1,[]),mc,1);
match = a == b;
match = match(:, 1 : 2 : 2*mf) & match(:, 2 : 2 : 2*mf);
crows = nonzeros(diag(1:mc) * match);
frows = nonzeros(match * diag(1:mf));
n = [c(crows,:),f(frows,3)]
Let say if I have this data
my_data = [ 10 20 30 40; 0.1 0.7 0.4 0.3; 6 1 2 3; 2 5 4 2];
my_index = logical(my_data(4,:)==2);
What is the simplest way to use 'my_index' to give this output
10.0000 40.0000
0.1000 0.3000
6.0000 3.0000
2.0000 2.0000
my_data(:,my_index)
but I'm suspicious that this is so simple that it doesn't satisfy your (background) requirements ...
I have an output like
a = [1 1.4 2.45 2.22; 2 3 4.2 1]
and I need the output to be like
[1 1 2 2; 2 3 4 1]
I don't want to round it.
fix maybe OK.
If you have both positive and negative numbers, and you just want to delete decimals, fix is a good choice.
b=[1 1.4 2.45 2.22; 2 3 -4.2 1]
b =
1.0000 1.4000 2.4500 2.2200
2.0000 3.0000 -4.2000 1.0000
fix(b)
ans =
1 1 2 2
2 3 -4 1
Use fix rather than round, e.g.
octave-3.4.0:1> a = [1 1.4 2.45 2.22; 2 3 4.8 1]
a =
1.0000 1.4000 2.4500 2.2200
2.0000 3.0000 4.8000 1.0000
octave-3.4.0:2> b = fix(a)
b =
1 1 2 2
2 3 4 1