【问题标题】:Split up a matrix based on numbers in two columns根据两列中的数字拆分矩阵
【发布时间】:2015-02-12 02:17:33
【问题描述】:

我有一个大矩阵,我想根据两列的值在其中分隔行。例如:

M=[ 1 3 5 6; 3 6 5 4; 1 8 5 1; 4 6 5 7; 3 6 4 5; 3 6 4 4]

我希望行被同时在第 1 列和第 3 列中看到的常见值分隔。这是预期的输出:

A= [1 3 5 6; 1 8 5 1]
B= [3 6 5 4]
C= [4 6 5 7]
D= [3 6 4 5; 3 6 4 4]

我尝试了以下方法。但是,它仅将其分隔为一列:

A = arrayfun(@(x) M(M(:,1) == x, :), unique(M(:,1)), 'uniformoutput', false)

【问题讨论】:

    标签: matlab


    【解决方案1】:

    据我了解,您希望提取矩阵的行,其中它们在第一列和第三列中都共享共同值。您当然可以使用arrayfun 方法,但您需要修改调用unique 的方式。具体来说,在您要检查的列上使用 'rows' 标志,然后在您的 arrayfun 调用中,使用 bsxfun 结合 all 来检查那些包含您想要的列元素的每个唯一组合的行,所以:

    >> M=[ 1 3 5 6; 3 6 5 4; 1 8 5 1; 4 6 5 7; 3 6 4 5; 3 6 4 4];
    >> r = unique(M(:,[1 3]), 'rows', 'stable');
    >> A = arrayfun(@(x) M(all(bsxfun(@eq, M(:,[1 3]), r(x,:)),2), :), 1:size(r,1),'uni', 0);
    >> celldisp(A);
    
    A{1} =
    
         1     3     5     6
         1     8     5     1
    
    A{2} =
    
         3     6     5     4
    
    A{3} =
    
         4     6     5     7
    
    A{4} =
    
         3     6     4     5
         3     6     4     4
    

    请务必注意,我使用'stable' 标志,因为unique 默认情况下排序唯一条目。 'stable' 确保我们按照遇到它们的顺序 找到唯一条目,就像在您的问题中看到的所需输出一样。顺便说一句,'uni''UniformOutput' 的缩写。从长远来看,它会帮你省去打字的麻烦:)。

    第三行很拗口,但很容易解释。首先,看一下这句话:

    bsxfun(@eq, M(:,[1 3]), r(x,:))
    

    我们在这里要做的是获取r 的每一行,这是取自M 的第1 列和第3 列的值的唯一配对,并查看M 的一行中的每个值是否相等从r 到相应的位置。为了找到匹配项,我们需要确保此结果的一行中的 所有 值都等于 1,这就是为什么我们需要在这里使用 all 并查看列:

    all(bsxfun(@eq, M(:,[1 3]), r(x,:)),2)
    

    一旦我们找到与r 中的特定行匹配的这些行,我们就会使用这些行并索引到我们的矩阵M 并提取满足r 中的行的行,我们在M 中寻找因此:

    M(all(bsxfun(@eq, M(:,[1 3]), r(x,:)),2), :)
    

    x 从 1 迭代到 r 中的行数,并且在每次迭代中,我们每次都从 r 中提取一个唯一行。最终结果将是一个元胞数组,它根据第一列和第三列之间的共同元素对 M 的行进行分组。


    如果您希望这样做更有效率,您可以按照 Divakar 的建议进行操作,只使用 unique 的第三个输出。这也会使事情更容易阅读:

    >> M=[ 1 3 5 6; 3 6 5 4; 1 8 5 1; 4 6 5 7; 3 6 4 5; 3 6 4 4];
    >> [~,~,r] = unique(M(:,[1 3]), 'rows', 'stable');
    >> A = arrayfun(@(x) M(r == x, :), 1:max(r),'uni', 0);
    >> celldisp(A);
    
    A{1} =
    
         1     3     5     6
         1     8     5     1
    
    A{2} =
    
         3     6     5     4         
    
    A{3} =
    
         4     6     5     7
    
    A{4} =
    
         3     6     4     5
         3     6     4     4
    

    当然更具可读性!现在发生的情况是,unique 的第三个输出分配了一个唯一 ID,从 1 到您拥有的作为unique 输入的唯一值。然后,我们只需找到最大 ID,并从 1 迭代到最大 ID,在每次迭代中,此索引用作从矩阵中提取对应于第一列和第三列的每个唯一组合的行的一种方式M.

    【讨论】:

    • 除非您可以使用来自unique 的第三个输出,因此避免使用bsxfun
    • @Divakar 嗯!有趣的提示。谢谢!
    • 谢谢大家的帮助!!效果很好!!
    • @TomG - 没问题。祝你好运!
    【解决方案2】:

    对我来说,分区意味着 MATLAB 中的 accumarray。第一步与rayryeng​​em>的解决方案基本相同:使用unique获取每一行的新位置。第二步是:基于这些新位置:获取M 的行并将它们放入一个单元格中。

    [~,~,I] = unique(M(:,[1 3]), 'rows', 'stable');
    A = accumarrayStable(I, 1:length(I), [], @(J) {M(J,:)});
    

    由于这些行位置不会被排序,我们需要一个稳定版本的accumarray,我从this answer 获取。 如果您不关心每个A{i} 中的行顺序,则不需要这个,但可以立即使用更快的accumarray

    function A = accumarrayStable(subs, val, varargin)
    [subs(:,end:-1:1), I] = sortrows(subs(:,end:-1:1));
    A = accumarray(subs, val(I), varargin{:});
    

    总体而言,与arrayfun 解决方案相比,使用此解决方案的速度应该提高 5-10 倍,当然这取决于您的矩阵大小。

    【讨论】:

    • 看起来确实很快!干得好。
    猜你喜欢
    • 1970-01-01
    • 2020-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-13
    • 1970-01-01
    相关资源
    最近更新 更多