从分类数组转向二进制矩阵

时间:2015-04-28 02:47:47

标签: arrays matlab performance for-loop vectorization

我有一个数组,其中包含一些属于集合的值。我想在二进制矩阵中转换此数组,此矩阵的每一列将表示该集合的每个可能值,对于与输入数组匹配的列,行值为1,对于所有其他列,行值为0。我认为这个名字就像二元枢轴。

输入数组是表格类型的列

输入数组的示例(前面的示例只是大写字母,导致误解):

'苹果'
'香蕉'
'樱桃'
'火龙果'
'苹果'
'樱桃'

因此,在此示例中,输入可以假设4个不同的值:' Apple',' Banana' Cherry'或者' Dragonfruit',在我的真实场景中它可以超过4个。

示例输出矩阵:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 0 1 0

我已经实现了这种期望的行为,但我想知道是否有更好的方法来执行此操作。以矢量化方式(没有每个类别的for循环)或使用内置函数。

 function [ binMatrix, categs ] = pivotToBinaryMatrix( input )
      categorizedInput = categorical(input);

      categs = categories(categorizedInput);

      binMatrix = zeros(size(atributo, 1), size(categorias, 1));

      for i = 1: size(caters,1)
           binMatrix(:,i) = ismember(categorizedInput, categs(i));
      end
 end

对于包含9个类别的约50,000个条目,它在0.075137秒内执行。

编辑:我改进了示例,因为前面的例子导致了误解。

3 个答案:

答案 0 :(得分:5)

以下是我对这个问题的看法:

input = ['ABCDAB']';
binMatrix = bsxfun(@eq,input,unique(input)');

对于基准测试,我在Windows 7机器,4Gb RAM,Intel i7-2600 CPU 3.4 GHz上运行它,借用@rayryeng初始化代码:

% Generate dictionary from A up to I
ch = char(65 + (0:8));

rng(123);

% Generate 50000 random characters
v = randi(9, 50000, 1);
inputArray = ch(v);

time=0;
for ii=1:100
    tic;
    binMatrix = bsxfun(@eq,inputArray,unique(inputArray)');
    t = toc;
    time=time+t;
end
disp(time/100);

这给了我0.001203秒。有关方法的广泛比较,请参阅@ ryaryeng的答案。

答案 1 :(得分:3)

我将假设你的输入数组是一个像这样的字符的单元格数组:

var array3 = [];
    array3.push(array1[0]);
    array3.push(array2[0]);

您可以使用unique函数的第三个输出将上述内容转换为数字数组。这有什么好处的是inputArray = {'Apple', 'Banana', 'Cherry', 'Dragonfruit', 'Apple', 'Cherry'}; 按排序顺序分配唯一ID ,因此如果您有一个字符单元格数组,它会尊重字符的字典顺序。

接下来,声明一个零的矩阵(就像你上面做的那样)然后用sub2ind索引到矩阵并将值设置为1.

像这样的东西。请记住,我初始化输出略有不同。这是我学会分配一个非常快的零矩阵的技巧。见这里:Faster way to initialize arrays via empty matrix multiplication? (Matlab)

unique

另一种方法是创建一个sparse逻辑数组,我们将正确的行和列位置设置为1,然后使用它来索引我们的零数组并相应地设置值。

类似的东西:

inputArray = {'Apple', 'Banana', 'Cherry', 'Dragonfruit', 'Apple', 'Cherry'};
[~,~,inputNum] = unique(inputArray);
inputNum = inputNum.'; %// To make compatible in dimensions
binMatrix(numel(inputArray), max(inputNum)) = 0;
binMatrix(sub2ind(size(binMatrix), 1:numel(inputArray), inputNum)) = 1;

让我们把它们放在一个计时脚本中。我已经将上述两种方法,加上你的旧方法,加上Divakar(仅第一种方法)和brodroll(非常巧妙的顺便说一句)方法结合在一起。对于Divakar和brodroll的方法,我还使用inputArray = {'Apple', 'Banana', 'Cherry', 'Dragonfruit', 'Apple', 'Cherry'}; [~,~,inputNum] = unique(inputArray); inputNum = inputNum.'; %// To make compatible in dimensions binMatrix = sparse(1:numel(inputArray), inputNum, 1, numel(inputArray), max(inputNum)); binMatrix = full(binMatrix); 和第三个输出,因为您的原始查询具有大写字母,这些大写字母混淆了所有。使用第三个输出可以轻松地将以前的方法转换为新规范。

是的,您的示例和代码不匹配。您的示例已设置,因此每列都是索引,但每行都是。对于时序测试,我将转换你的结果。我在Mac OS X 10.10.3上运行MATLAB R2013a,配备16 GB RAM和Intel i7 2.3 GHz处理器。所以:

unique

我们得到:

clear all;
close all;

%// Generate dictionary
chars = {'Apple', 'Banana', 'Cherry', 'Dragonfruit'};

rng(123);

%// Generate 50000 random words
v = randi(numel(chars), 50000, 1);
inputArray = chars(v);
[~,~,inputNum] = unique(inputArray);
inputNum = inputNum.'; %// To make compatible in dimensions

%// Timing #1 - sub2ind
tic;
binMatrix(numel(inputArray), max(inputNum)) = 0;
binMatrix(sub2ind(size(binMatrix), 1:numel(inputArray), inputNum)) = 1;
t = toc;

clear binMatrix;

%// Timing #2 - sparse
tic;
binMatrix = sparse(1:numel(inputArray), inputNum, 1, numel(inputArray), max(inputNum));
binMatrix = full(binMatrix);
t2 = toc;

clear binMatrix;

%// Timing #3 - ismember and for
tic;
binMatrix = zeros(numel(inputArray), numel(chars));
for i = 1: size(binMatrix,1)
binMatrix(i,:) = ismember(chars, inputArray(i));
end
t3 = toc;

%// Timing #4 - bsxfun
clear binMatrix;
tic;
binMatrix = bsxfun(@eq,inputNum',unique(inputNum)); %// Changed to make dimensions match
t4 = toc;

clear binMatrix;

%// Timing #5 - raw sub2ind
tic;
binMatrix(numel(inputArray), max(inputNum)) = 0;
binMatrix( (inputNum-1)*size(binMatrix,1) + [1:numel(inputArray)] ) = 1;
t5 = toc;

fprintf('Timing using sub2ind: %f seconds\n', t);
fprintf('Timing using sparse: %f seconds\n', t2);
fprintf('Timing using ismember and loop: %f seconds\n', t3);
fprintf('Timing using bsxfun: %f seconds\n', t4);
fprintf('Timing using raw sub2ind: %f seconds\n', t5);

就等级而言:

  1. 原始Timing using sub2ind: 0.004223 seconds Timing using sparse: 0.004252 seconds Timing using ismember and loop: 2.771389 seconds Timing using bsxfun: 0.020739 seconds Timing using raw sub2ind: 0.000773 seconds
  2. sub2ind
  3. sub2ind
  4. sparse
  5. OP的方法

答案 2 :(得分:3)

如果您在输入数组中有非连续字符的情况下不介意所有零列,例如'ABEACF',其中缺少'D',则可以使用此 -

col_idx = inputArray - 'A' + 1;
binMatrix(numel(inputArray), max(col_idx) ) = 0;
binMatrix( (col_idx-1)*size(binMatrix,1) + [1:numel(inputArray)] ) = 1;

如果您确实关心该问题并且不希望使用全零列,则可以使用它的修改版本 -

[~,unq_pos,col_idx] = unique(inputArray,'stable');
binMatrix(numel(inputArray), numel(unq_pos)) = 0;
binMatrix( (col_idx-1)*size(binMatrix,1) + [1:numel(inputArray)].' ) = 1;

基本上这两种方法都使用相同的 hacky 技术进行预分配,如Undocumented MATLAB中所列,并且也列在other answer by @rayryeng中。最重要的是,它使用了sub2ind原始版本。