Appending to an array of repeated elements in matl

2019-09-11 07:51发布

The data variable is a 3x9 array, where the first row is voltage, the second row is counts, and the third row is error. I want to average the counts/errors at each voltage so that the processed array is something like

combined = [1,2,3;.99,.1.2,1.3;.2,.3,.5]

My attempt so far is:

data = [1,1,1,2,2,2,3,3,3;...
       .98,.99,.98,1.1,1.2,1.2,1.3,1.3,1.4;...
       .3,.2,.2,.3,.3,.4,.5,.4,.5];
volt = data(1,:);
v_uniq=unique(volt);%Array of unique voltage values

for k=1:length(v_uniq)
    volt_i=find(volt==v_uniq(k));%Indices for set of repeating values
    combined_m(k)=mean(data(2,volt_i(1):volt_i(end)).')';%averaged means
    combined_e(k)=mean(data(3,volt_i(1):volt_i(end)).')';%averaged error
    combined(k) = [combined_m(k);combined_e(k)];
    %combined(k)=combined(k);
end

The challenging part is appending the combined array after each iteration, since v_uniq is an array itself and causes problems when trying to iterate through.

Is there some way to append the combined array without having to index v_uniq? Or if nor, is there another, simpler way to approach this problem?

1条回答
Juvenile、少年°
2楼-- · 2019-09-11 08:22

If you want to get your code working, using Daniel's suggestion in his comment to you above will work. Simply replace the last line of your for loop code to this:

combined(:,k) = [combined_m(k);combined_e(k)];

You want to insert a column that shows the combined counts and errors for each unique voltage.


However, I would recommend using accumarray instead of your for loop approach, and use the voltage as the key and use the other two rows of your matrix as values. How accumarray works is that you have two arrays: keys and values. For each value that is stored in keys, we see what the corresponding number is in values, and we place this number into a bin that is indexed by keys. Once we do all of this binning, you then use a function that combines all of these entries per bin together. By default accumarray uses sum, and so you'd just sum all of the values that get mapped to each bin together.

However, for your case you would use mean as the function to apply over each of the bins to find the average error and counts for each unique voltage. Something like this:

data = [1,1,1,2,2,2,3,3,3;...
.98,.99,.98,1.1,1.2,1.2,1.3,1.3,1.4;...
.3,.2,.2,.3,.3,.4,.5,.4,.5];

ave_counts = accumarray(data(1,:).', data(2,:).', [], @mean);
ave_error = accumarray(data(1,:).', data(3,:).', [], @mean);

combined = [ave_counts ave_error];

In accumarray, the first argument is the keys, which are the voltages in our case, and values are either the counts or the errors - essentially those values that need to be combined.

ave_counts will contain the average counts per unique voltage while ave_error will contain the average error per unique voltage. We can then combine these into a 2 column matrix like the last line of code to create combined, as what was seen in your code.

Because your voltages are already ordered in an increasing way, this will mean that each element corresponds to that voltage exactly. After running this, this is what I get for ave_counts and ave_error:

>> ave_counts

ave_counts =

    0.9833
    1.1667
    1.3333

>> ave_error

ave_error =

    0.2333
    0.3333
    0.4667

This says that for voltage = 1, the average count and average error are 0.9833 and 0.2333 respectively. You can verify this by calculating this by hand. The first three counts and errors are for voltage = 1, and if we calculate the average error and counts, we get:

(0.98+0.99+0.98) / 3 = 0.9833 <-- Average count for voltage = 1
(0.3+0.2+0.3) / 3 = 0.2333 <-- Average error for voltage = 1

Similarly for voltage = 2:

(1.1+1.2+1.2) / 3 = 1.1667 <-- Average count for voltage = 2
(0.3+0.3+0.4) / 3 = 0.3333 <-- Average error for voltage = 2

Finally for voltage = 3:

(1.3+1.3+1.4) / 3 = 1.3333 <-- Average count for voltage = 3
(0.5+0.4+0.5) / 3 = 0.4667 <-- Average error for voltage = 3

The quantities that we calculated above by hand are exactly what accumarray outputs for both quantities.


Caveat

accumarray is only designed to take in integer keys. If you have floating-point values, you can still use accumarray, but you'd have to do some pre-processing first. What I would do is use unique and use the third output specifically. The third output takes each unique value in the input and assigns an integer ID to it. Any values that are the same get assigned the same integer ID, which is thus ideal as input into accumarray. You would also need the first output to keep track of what voltages are being averaged per row of the combined matrix.

You would thus use the third output of unique as input into accumarray. Therefore, do something like this:

[voltages,~,id] = unique(data(1,:));
ave_counts = accumarray(id, data(2,:).', [], @mean);
ave_error = accumarray(id, data(3,:).', [], @mean);
combined = [ave_counts ave_error];

In this case, each element of ave_counts and ave_error will correspond to the same element stored in voltages. As such, voltages(1) will correspond to the first row of combined, voltages(2) will correspond to the second row of combined, and so on.

For completeness, if we ran the above with your example data, this is what we get for voltages and combined:

>> voltages

voltages =

     1     2     3

>> combined

combined =

    0.9833    0.2333
    1.1667    0.3333
    1.3333    0.4667

Therefore, voltage = 1 gives us an average count of 0.9833 with an average error of 0.2333, and so on.

查看更多
登录 后发表回答