Matlab 字符串元胞数组:查找重复的字符串并操作相应的数据

Matlab cell arrays of strings: finding repeated strings and manipulating corresponding data

我有一大组数据(约 100 万个条目),存储为一个包含许多列和许多行的单元格。我的问题是我需要识别同时出现的条目,然后操作其他列以便在不丢失所有信息的情况下删除具有重复日期的行。

可以这样初始化此类数据的一个子集的示例;

data = {'10:30', 100; '10:30', 110; '10:31', 115;'10:32', 110}

也就是说,我有一个单元格,其中有一列字符串(代表时间),另一列(真实数据中有很多)是双精度数。

我的代码应该注意到重复的 10:30(我可能有很多这样的重复),然后能够将相应的双精度值(100 和 110)作为某些函数 f(100,110) 的输入,然后从数据中删除重复的行。

即如果这个函数是平均的,我应该有一个看起来像

的输出
data =
       '10:30' [105]
       '10:31' [115]
       '10:32' [110]

如果循环足够快,这将相当简单,但对于我的数据集,即使尝试涉及循环的解决方案也毫无意义。

我已经达到了

[uniqueElements, firstUniquePosition, commonSets] = unique(data(:,1));

经过多次摆弄,产生了一些看起来有用的信息,

uniqueElements = 

    '10:30'
    '10:31'
    '10:32'

firstUniquePosition =

     1
     3
     4

commonSets =

     1
     1
     2
     3

但我不太明白如何制作一个允许我操作具有共同日期的元素的矢量化语句。

我想它会在某个时候涉及 cellfun,但我对 matlab 的功能了解不够,无法在没有朝正确方向推动的情况下实现它。

这是 accumarray 的工作:

[times,~,subs] = unique(data(:,1));
idx = 1:size(data,1);
meanOfCommonTimes = accumarray(subs(:),idx(:),[],@(x) mean( [data{x,2}] ))

output = [times num2cell(meanOfCommonTimes)]

output = 

    '10:30'    [105]
    '10:31'    [115]
    '10:32'    [110]

谈论 100 万个元素和性能:考虑使用 datenum 函数将时间数据存储为数值。

times = datenum(data(:,1),'hh:mm');

并将您的数据保存在双数组中:

vals = cell2mat(data(:,2));

计算将快 10 倍

[~,~, subs] = unique(times);
meanOfCommonTimes = accumarray(subs(:),vals(:),[],@mean);

但请注意。转换也需要相当长的时间。如果您稍后进行大量计算,它就可以了。


基准

function [t] = bench()
    data = {'10:30', 100; '10:30', 110; '10:31', 115;'10:32', 110};
    data = [repmat(data, 200000, 1)]; % I use a matrix rather than a cell array for the simplicity of randomly generating example data

    % functions to compare
    fcns = {
        @() thewaywewalk(data);
        @() Cecilia(data);
    };

    thewayw = timeit(fcns{1})
    Ceci = timeit(fcns{2})
end

function Z = Cecilia(data)
    [uniqueElements, ~, commonSets] = unique(data(:,1));

    num_unique = length(uniqueElements);
    Z = zeros(num_unique, 1);
    for ii = 1:num_unique
        Z(ii) = mean([data{commonSets==ii, 2}]);
    end
end
function Z = thewaywewalk(data)
    [~,~,subs] = unique(data(:,1));
    idx = 1:size(data,1);
    Z = accumarray(subs(:),idx(:),[],@(x) mean( [data{x,2}] ));
end

对于具有 800000 行的数组,结果几乎相同。

thewayw =  1.1483
Ceci = 1.0957

但同样,accumarray 会从之前的 double 转换中获利很高,但在这种情况下循环的性能应该保持不变。

方法

一个循环的执行有多糟糕取决于你有多少个独特的日期,而不是有多少个数据点。如果您的唯一日期数量较少,您可以执行以下操作

data = {'10:30', 100; '10:30', 110; '10:31', 115;'10:32', 110};
[uniqueElements, firstUniquePosition, commonSets] = unique(data(:,1));

num_unique = length(uniqueElements);
mean_of_times = zeros(num_unique, 1);
for ii = 1:num_unique
    mean_of_times(ii) = mean([data{commonSets==ii, 2}]);
end

output = [uniqueElements num2cell(mean_of_times)]

output = 

    '10:30'    [105]
    '10:31'    [115]
    '10:32'    [110]

基准测试

那么 for 循环有多糟糕?我测试了多达 20,000 个唯一日期,行数是原来的 100 倍,总共有 2,000,000 行。这是我实验的情节。弄清楚可能有点棘手,但 accumarray 点都在 x 轴下方。

这是实验代码

figure; hold on;
kk = 100; %Make 100 times as many rows as dates
for jj = 5000:5000:20000
    dates = 1:jj;
    times = rand(jj*kk, 1);
    % I use a matrix rather than a cell array for the simplicity of randomly generating example data
    data = [repmat(dates, 1, kk)' times];
    data = data(randperm(jj*kk), :); %Shuffle data rows

    [uniqueElements,~,commonSets] = unique(data(:,1));

    %thewaywewalk's solution using accumarray
    tic;
    idx = 1:size(data,1);
    accumarray(commonSets(:),idx(:),[],@(x) mean( [data(x,2)] ));
    stopwatch = toc;
    plot(jj, stopwatch, 'b.'); 

    %my solution using a for loop
    tic;
    num_unique = length(uniqueElements);
    mean_of_times = zeros(num_unique, 1);
    for ii = 1:num_unique
        mean_of_times(ii) = mean([data(commonSets==ii, 2)]);
    end
    stopwatch = toc;
    plot(jj, stopwatch, 'r.'); 
end

与行相比,此实验仅测试 1% 的唯一日期。对于更独特的日期,for 循环会更慢。 thewaywewalk 的答案基准针对具有 3 个唯一日期的数据集。即使只有如此少量的相似日期,accumarray 和 for 循环也具有相似的运行时间。结论?使用 accumarray.