Group data in a cell array - matlab

I have the following data:
Names={A1 A2 B1 B2 C1 C2 C3}
Doserate=(2.2 3.4 6.4 3.4 2.3 4.5 7.5)
Time=(5 7.8 9 3.5 10.2 5.6 7.8)
The order of Doserate and Time is such they correspond to Names. I would like to make groups starting with the same letter so that I can perform calculations using Doserate and Time corresponding to that group. Names can vary to even more letters (A-Z) or more numbers like (A1-A30).
How can I group these entries?

Names={'A1' 'A2' 'B1' 'B2' 'C1' 'C2' 'C3'};
first_letter = blanks(numel(Names));
for ii = 1:numel(Names)
first_letter(ii) = Names{ii}(1); % Grab the first letter
end
[a, b, c] = unique(first_letter)
a =
ABC
b =
2 4 7
c =
1 1 2 2 3 3 3
You can use a loop to extract the first character in each cell entry (you can probably use cellfun() as well) and then a call to unique() to extract the unique characters. Its third output, named c in my example, will be your groups. Thus Doserate(c(c==2)) will return all Doserate entries where Names start with B.

To extract one or more letters from the start of your names, you can use regex:
Names = {'A1' 'A2' 'B1' 'B2' 'C1' 'C2' 'C3'};
N = regexp( Names, '^[a-zA-Z]+', 'match', 'once' )
% >> N = {'A', 'A', 'B', 'B', 'C', 'C', 'C'};
Then you can use findgroups to group them
[gidx,gnames] = findgroups(N)
gidx =
1 1 2 2 3 3 3
gnames =
{'A', 'B', 'C'}
i.e. now anything where gidx == 2 matches the group 'B' (2nd element in gnames).

Related

Collapsing and averaging redundant entries in MATLAB table

I have the following MATLAB table
item_a item_b score
a b 1
a b 1
b c 3
d e 2
d e 1
d e 0
I want to average the redundant rows. The desired result is as follows:
item_a item_b score
a b (1+1)/2
b c 3
d e (2+1+0)/3
This is a classic scenario for the findgroups, split-apply workflow.
Given your table named t:
% Find mean values.
G = findgroups(t.item_a);
meanValues = splitapply(#mean,t.score,G);
% Create new table.
[~,i] = unique(G);
newTable = t(i,:);
newTable.score = meanValues
newTable contains the desired table.
See this documentation page for more examples.
This is what I got. You can tweak with the final results. There is a similar example on MATLAB documentation. Here are two key functions, accumarray and unique. Note that this solution works only for array inputs not cell data types. By manipulating data types, you can also find the solution for table and cell data types. Otherwise, I think for loop will be necessary.
items = ['a' 'b'
'a' 'b'
'b' 'c'
'd' 'e'
'd' 'e'
'd' 'e' ];
scores = [1 1 3 2 1 0]';
[items_unique,ia,ic] = unique(items,'rows');
score_mean = accumarray(ic,scores, [], #mean);
result = {items_unique score_mean};

How can using the existing fields, the value of another field achieved?

I have a Table as follows:
cl c2 c3 .....
r1 x A 4
r2 y B 5
r3 z C 2
.
.
.
r(1,2,3) are label of rows and c(1,2,3) are label of columns. I have a field of c1,c2 and I want c3. For example I have y and B, so I want achieve to '5';
I read 'Find , sub2ind' Functions but I do not know how can I use them For this case.
You can use simple logical indexing to accomplish this. You want the third column when the value of the first column is 'y' and the value of the second column is 'B'
t = table({'x'; 'y'; 'z'}, {'A'; 'B'; 'C'}, [4; 5; 2], 'VariableNames', {'c1', 'c2', 'c3'});
value = t.c3(ismember(t.c1, 'y') & ismember(t.c2, 'B'))
% 5

Matlab vactor table to normal table extraction

I have a table name output which contains the dimention like this:
cat values s1 s2 sub_cat
1 [1x2 double] 0.66584 3.1383 {2x1 cell}
values are such as:
cat values s1 s2 sub_cat
1 2.5 3.4 0.555 3.999 emozi-1
emozi-3
2 2.9 7.1 5.0 2 khazal-11
kha-9
How can i re-arrange this table like this(remove vector to normal):
cat values s1 s2 sub-cat
1 2.5 0.555 3.999 emozi-1
1 3.4 0.555 3.999 emozi-3
2 2.9 5.0 2 khazal-11
2 7.1 5.0 2 kha-9
Can anyone help to do this in matlab?
I think what you're after is a table stack operation. It's a bit tricky because I think you're trying to stack two table variables simultaneously (without getting all the combinations), so here's what I think you need:
%# Sample table data
t = table([1;2], [1, 2; 3, 4], [0.1; 0.2], {'a', 'b'; 'c', 'd'}, ...
'VariableNames', {'cat', 'values', 's1', 'sub_cat'});
%# Combine corresponding columns of 'values' and 'sub_cat' so
%# that we've got something we can stack
t2 = table(t.cat, [num2cell(t.values(:,1)), t.sub_cat(:,1)], ...
[num2cell(t.values(:,2)), t.sub_cat(:,2)], ...
t.s1, 'VariableNames', {'cat', 'vs1', 'vs2', 's1'});
%# Actually call 'stack'
t3 = stack(t2, {'vs1', 'vs2'});
%# Unpick the variables in 't3' into something more useful
t4 = table(t3.cat, t3.s1, cell2mat(t3.vs1_vs2(:,1)), ...
t3.vs1_vs2(:,2), 'VariableNames', ...
{'cat', 's1', 'values', 'sub_cat'})

Reshape Matlab table

I have the following table
name = ['A' 'A' 'A' 'B' 'B' 'C' 'C' 'C' 'C' 'D' 'D' 'E' 'E' 'E']';
value = randn(14, 1);
T = table(name, value);
i,e.
T =
name value
____ _________
A 0.0015678
A -0.76226
A 0.98404
B -1.0942
B 0.71249
C 1.688
C 1.4001
C -0.9278
C -1.3725
D 0.11563
D 0.076776
E 1.0568
E 1.1972
E 0.29037
I want to transform it in the following way: take the first two cells in value corresponding to different values in name and put it in the 5x2 matrix. This matrix would have rows corresponding to different names A,B,C,D,E and columns corresponding to values, e.g. the first two rows are
0.0015678 -0.76226
-1.0942 0.71249
This can be done with accumarray using a custom function. The first step is to convert the name column of T into a numeric vector; and then accumarray can be applied.
This approach requires T being sorted according to column 1, because only in this case is accumarray guaranteed to preserve order (as indicated in its documentation). So if T may not be sorted (although it is in your example), sort it first using sortrows.
T = sortrows(T, 1); %// you can remove this line if T is guaranteed to be sorted
[~, ~, names] = unique(T(:,1)); %// names as a numeric vector
result = cell2mat(accumarray(names, T.value, [], #(x) {x([1 2]).'}));
First figure out where each name has values located in the table, then cycle through each name and place the first two values encountered for each name into individual cell arrays. Once you're done, reshape the matrix to 5 x 2 as you have said. As such, do something like this:
names = unique(T.name); %// 1
ind = arrayfun(#(x) find(T.name == x), names, 'uni', 0); %// 2
vals = cellfun(#(x) T.value(x(1:2)), ind, 'uni', 0); %// 3
m = [vals{:}].'; %// 4
Let's go through each line of code slowly.
Line #1
The first line finds all unique names through unique and we store them into names.
Line #2
The next line goes through all of the unique names and finds those locations / rows in the table that share that particular name. I use arrayfun and go through each name in names, find those rows that share the same name as one we are looking for, and place those row locations into individual cells; these are stored into ind. To find the locations of each valid name in our table, I use find and the locations are placed into a column vector. As such, we will have five column vectors where each column vector is placed into an individual cell. These column vectors will tell us which rows match a particular name located in your table.
Line #3
The next line uses cellfun to go through each of the cells in ind and extracts the first two row locations that share a particular name, indexes into the value field for your table to pull those two values, and these are placed as two-element vectors into individual cells for each name.
Line #4
The last line of code simply unrolls each two-element vector. The first two elements of each name get stored into columns. To get them into rows, I simply transpose the unrolling. The output matrix is stored into m.
If you want to see what the output looks like, this is what I get when I run the above code with your example table:
m =
0.0016 -0.7623
-1.0942 0.7125
1.6880 1.4001
0.1156 0.0768
1.0568 1.1972
Be advised that I only showed the first 5 digits of precision so there is some round-off at the end. However, this is only for display purposes and so what I got is equivalent to what your expect for the output.
Hope this helps!
If you want use the tables, you could try something like this:
count = 1;
U = unique(table2array(T(:,1)));
for ii = 1:size(U,1)
A = find(table2array(T(:,1)) == U(ii));
A = A(1:2);
B(count,1:2) = table2array(T(A,2));
count = count + 1;
end
Personally, I would find this simpler to do with your name and value arrays and forget about the table. If it is a requirement then I understand, however I will provide my solution still. It may provide some insight either way.
count = 1;
U = unique(name);
for ii = 1:size(U,1)
A = find(name == U(ii));
A = A(1:2);
B(count,1:2) = value(A);
count = count + 1;
end
Quick and dirty, but hopefully it's good enough. Good luck.
Another solution that is more manageable and easily scalable exists. Since MATLAB R2013b you can use a specialized function for pivoting a table (which is what you want to do): unstack.
In order to get exactly what you wanted, you need to add an extra variable to your table that will indicate replications:
name = ['A' 'A' 'A' 'B' 'B' 'C' 'C' 'C' 'C' 'D' 'D' 'E' 'E' 'E']';
value = randn(14, 1);
rep = [1, 2, 3, 1, 2, 1, 2, 3, 4, 1, 2, 1, 2, 3];
T = table(name, value, rep);
T =
name value rep
____ _________ ___
A 0.53767 1
A 1.8339 2
A -2.2588 3
B 0.86217 1
B 0.31877 2
C -1.3077 1
C -0.43359 2
C 0.34262 3
C 3.5784 4
D 2.7694 1
D -1.3499 2
E 3.0349 1
E 0.7254 2
E -0.063055 3
Then you just use unstack like this:
pivotTable = unstack(T, 'value','name')
pivotTable =
rep A B C D E
___ _______ _______ ________ _______ _________
1 0.53767 0.86217 -1.3077 2.7694 3.0349
2 1.8339 0.31877 -0.43359 -1.3499 0.7254
3 -2.2588 NaN 0.34262 NaN -0.063055
4 NaN NaN 3.5784 NaN NaN
Afterwards, it's a matter of re-arranging the table if you still want to.
The easiest way is to first convert the table into a matrix form and then reshape it by using the "reshape" function in Matlab.
matrix = t{:,:};% t-- your table variable
reshape_matrix = reshape(matrix ,[2,3]) % [2,3]--> the size of the matrix you desire
These two steps can be done by one line of code
reshape_matrix = reshape(t{:,:},[2,3]);

MATLAB: Conditional summation

I have two arrays of the following form:
v1 = [ 1 2 3 4 5 6 7 8 9 ... ]
c2 = { 'a' 'a' 'a' 'b' 'b' 'c' 'c' 'c' 'c' ... }
(all values are examples only, no pattern can be assumed in the real data. v1 and c2 have the same size)
I want to obtain a vector containing the summation of the components of v1 corresponding to equal values in c2. In the example above, the first component of the resulting vector would be 1+2+3, the second 4+5, and so on.
I know I can do it in a loop of the form:
uni_c2 = unique(c2);
result = zeros(size(uni_c2));
for i = 1:numel(uni_c2)
result(i) = sum( v1(strcmp(uni_c2(i),c2)) );
end
Is there a single command or a vectorized way of doing the same operation?
You can do this in two lines:
[b, m, n] = unique(c2)
result = accumarray(n', v1)
The elements of result correspond to the strings in the cell array b.
This is vectorized but a bad idea for very large vectors. For some problems a "vectorized" solution is worse than a for loop.
>> v1 = [ 1 2 3 4 5 6 7 8 9];
>> c2 = 'aaabbcccc'-'a'
c2 =
0 0 0 1 1 2 2 2 2
>> N = repmat(c2',1,max(c2)-min(c2)+1) == repmat([min(c2):max(c2)],size(c2,2),1);
>> v1*N
ans =
6 9 30
I think a very general (and vectorized) solution is something like this:
v1 = [ 1 2 3 4 5 6 7 8 9 ]
c2 = { 'a' 'a' 'a' 'b' 'b' 'c' 'c' 'c' 'c' }
uniqueValuesInC2 = unique(c2);
conditionalSumOfV1 = #(x)(sum(v1(strcmp(c2, x))));
result = cellfun(conditionalSumOfV1, uniqueValuesInC2)
Perhaps my solution needs a bit of an explanation to the untrained eye:
So first you actually need to compute the different possible values in c2, which is done by unique.
The conditionalSumOfV1 function takes an argument x, it compares every element in c2 with x and selects the corresponding elements in v1 and sums them.
Finally cellfun is comparable to a foreach construct in some other languages: the function conditionalSum is evaluated for every value in the cell array you provide (in this case: every unique value in c2) and stores it in the output array. For other types of container variables (arrays, structs), MATLAB has equivalent foreach-like constructs: arrayfun, structfun.
This will work for contents of c2 that are longer than a single character and it does not require a large repmat operation as stardt's solution. I do however have my doubts when it comes to long arrays where c2 has only a few duplicate values., but I guess that will be a hard case for most algorithms. If you are in such a case, you might need to take a look at the extra outputs of unique or write your own alternative to unique (i.e. write for loops, preferably in a compiled language/MEX).