快速提取由另一个向量中的索引分组的向量中元素的频率?

时间:2016-09-20 06:50:44

标签: arrays matlab vectorization

假设我有一个矩阵A,其第一列包含一些商品ID,第二列包含0或1。

A=[3    1
   1    0
   4    0
   3    0
   1    1
   2    1
   3    1
   4    0
   2    0
   4    1
   3    1
   4    0
   2    1
   1    1
   2    0];

我想找到哪个项目ID最多1,并逐个从A中提取其条目。所以,我这样做的方法是,我创建一个矩阵B从A中提取所有1个条目,在freq_item{1}中找到最常出现的项目ID B,然后从该ID的A中提取所有条目。然后,删除最频繁项目的所有实例,并搜索下一个最常用的项目。如果2个或更多项目具有相同数量的1,则选择比率大于1的项目:

B = A(A(:,2)==1,:);
for i=1:size(unique(A(:,1)),1)
   freq_item{i} = A(A(:,1)==mode(B(:,1)),:);
   B = B(B(:,1)~=mode(B(:,1)),:);
end

所以,输出是:

freq_item{1,1}=[3     1
                3     0
                3     1
                3     1]

freq_item{1,2}=[1     0           
                1     1
                1     1]

freq_item{1,3}=[2     1
                2     0
                2     1
                2     0]

freq_item{1,4}=[4     0  
                4     0         
                4     1
                4     0]

但是这段代码需要有引入中间矩阵B的开销。是否有代码可以在不需要中间矩阵B的情况下完成此任务,并且至少与上述代码一样快(即,其时间复杂度小于或等于上面编写的代码的时间复杂度)?

3 个答案:

答案 0 :(得分:5)

accumarray的另一项工作:

%// prep
subs = A(:,1);
vals = A(:,2);

%// find id with amximum occurences
[~, id] = max( accumarray(subs,vals) )

%// find indices of that id
idx = find(A == id)

%// filter output
out = A(idx,:)

或更短

[~, id] = max( accumarray(A(:,1),A(:,2)) )
out = A(find(A == id),:)

扩展版

%// prep
subs = A(:,1);
vals = A(:,2);

%// find id with maximum occurences and mean values
sums = accumarray(subs,vals)
ratios = accumarray(subs,vals,[],@mean)
rows = 1:numel(sums)

%// distributing 
unsorted = accumarray(subs,1:numel(subs),[],@(x) {A(x,:)} )

%// sorting indices
[~,idx] = sortrows([sums(:),ratios(:),rows(:)],[-1 -2 3])
sorted = unsorted(idx)
sorted{1,2} =

     3     0
     3     1
     3     1
     3     1

sorted{2,2} =

     1     0
     1     1
     1     1

sorted{3,2} =

     2     0
     2     0
     2     1
     2     1

sorted{4,2} =

     4     1
     4     0
     4     0
     4     0

答案 1 :(得分:1)

首先,找到最常见的ID

mode(A(logical(A(:,2))))

此语句使用逻辑索引仅考虑那些,以及mode函数来返回最常出现的值。

你也可以这样说:

mode(A( A(:,2) == 1 ))

然后,提取与该值对应的行

A( A(:,1) == mode(A( A(:,2) == 1 )),:)

这里我们将第一列(A(:,1))与最常见的ID进行比较,后者返回一个布尔向量。然后使用逻辑索引来提取每个匹配的行和相应的列。

回答问题的扩展版本

[B,IX] = sort(histc(A( A(:,2) == 1 ),unique(A(:,1))),'descend');
freq={}; 
for a=IX'; 
   freq{end+1}=A(A(:,1) == subsref(unique(A(:,1)),struct('type','()','subs',{{a}})),:); 
end

答案 2 :(得分:1)

这是另一种选择,主要使用histcounts。很难将其与@MayeulC解决方案进行比较,因为它确实做了更多的事情。至于@thewaywewalk答案,一般情况下,如果A大于histcounts might be a better choice而不是accumarray。但是,这里的答案(使用for循环)始终更快(并且随着A更大,它会变得更好)。

这是非中间变量 - 非常慢和丑陋的版本:

output = arrayfun(@(k) A(A(:,1)==k,:),...
    subsref(sortrows([...
    histcounts(A(logical(A(:,2)),1),min(A(:,1)):max(A(:,1))+1);
    histcounts(A(logical(A(:,2)),1),min(A(:,1)):max(A(:,1))+1)./...
    histcounts(A(:,1),min(A(:,1)):max(A(:,1))+1);
    min(A(:,1)):max(A(:,1))].',[-1 -2]),struct('type','()','subs',{{...
    subsref(sortrows([...
    histcounts(A(logical(A(:,2)),1),min(A(:,1)):max(A(:,1))+1);
    histcounts(A(logical(A(:,2)),1),min(A(:,1)):max(A(:,1))+1)./...
    histcounts(A(:,1),min(A(:,1)):max(A(:,1))+1);
    min(A(:,1)):max(A(:,1))].',[-1 -2]),struct('type','()','subs',{{':',2}}))>0,3}}))...
    ,'UniformOutput',false);

这是更具可读性和更快的版本:

ID_range = (min(A(:,1)):max(A(:,1))+1);     % the IDs
one_count = histcounts(A(logical(A(:,2)),1),ID_range); % the count of 1's
zero_count = histcounts(A(:,1),ID_range);    % the count of 0's
sortedIDs = sortrows([one_count; one_count./zero_count; ID_range(1:end-1)].',[-1 -2]);
output = cell(numel(nonzeros(sortedIDs(:,1))),1);
for k = 1:numel(output)
    output{k} = A(A(:,1)==sortedIDs(k,3),:);
end

要测试这个,你需要另一个矩阵,它的ID尚未按照这种方式排序:

A = [3 1;
    5 0;
    4 0;
    3 0;
    5 1;
    7 1; 
    3 1; 
    4 0;
    5 0; 
    4 1; 
    3 1;
    4 0; 
    7 1; 
    5 1; 
    7 0];

我们得到输出:

output{1} =
     3     1
     3     0
     3     1
     3     1
output{2} =
     7     1
     7     1
     7     0
output{3} =
     5     0
     5     1
     5     0
     5     1
output{4} =
     4     0
     4     0
     4     1
     4     0