【问题标题】:Pivot to binary matrix from categorial array从分类数组转向二进制矩阵
【发布时间】:2015-07-06 17:06:44
【问题描述】:

我有一个数组,其中包含一些属于一个集合的值。我想将此数组转换为二进制矩阵,该矩阵的每一列将代表集合的每个可能值,与输入数组匹配的列的行值为 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

它在 0.075137 秒内完成了 9 个类别的大约 50.000 个条目。

编辑:我已经改进了示例,因为之前的示例导致了误解。

【问题讨论】:

  • 我们有帮助吗?如果我们接受了,请考虑接受我们的答案之一。
  • 我已编辑我的帖子以回复您的编辑。它只需要很小的编辑,但我们的方法仍然比您的方法快。要使用我们的任何方法,您只需调用unique 并将第三个输出用于我们的任何算法。

标签: arrays matlab performance for-loop vectorization


【解决方案1】:

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

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 的回答。

【讨论】:

  • 我猜你的答案读得太多了。必须在某个时候学习它;)
  • 做了一些运行时测试,看起来效率很高,我可以想到this reason,使用它做得很好!
  • 酷!您是否尝试像 OP 那样在 50000 长度的向量上使用它?这仅在六元素向量上完成。
  • @Divakar - 进行了时序测试。 bsxfun 较慢。我会更新我的帖子。原始sub2ind 获胜!
  • 很好,@rayryeng 仅对六元素数组进行了计时。我会尽快编辑,以免误导!
【解决方案2】:

我将假设您的输入数组是一个字符元胞数组,如下所示:

inputArray = {'Apple', 'Banana', 'Cherry', 'Dragonfruit', 'Apple', 'Cherry'};

您可以使用unique 函数的第三个输出将上述内容转换为数值数组。这样做的好处在于 unique 分配了一个唯一的 ID,按排序顺序,因此,如果您有一个字符元胞数组,它会遵守字符的字典顺序。

接下来,声明一个零矩阵(就像您在上面所做的那样),然后使用sub2ind 对矩阵进行索引并将值设置为 1。

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

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;

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

类似:

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);

让我们将所有这些放在一个计时脚本中。我已经合并了上面的两种方法,加上你的旧方法,加上 Divakar 的(只有第一种方法)和 brodroll 的(非常巧妙的顺便说一句)方法。对于 Divakar 和 brodroll 的方法,我还使用了 unique 和第三个输出,因为您的原始查询有大写字母,这一切都令人困惑。使用第三个输出可以轻松地将他们以前的方法转换为您的新规范。

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

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);

我们得到:

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

在排名方面:

  1. 原始sub2ind
  2. sub2ind
  3. sparse
  4. bsxfun
  5. OP的方法

【讨论】:

  • @Divakar - 哦,我知道。我一直使用那个预分配技巧。我什至不再使用zeros。如果你很好奇,我添加了计时测试,我还添加了使用sparse 方法。看起来sparse 稍微快了一点。
  • 嗯!有趣,可能对未来有用的知识
  • @rayryeng 感谢您的测试,特别感谢那个黑客。必须付诸行动!
  • 使用了一些更骇人听闻的动作来摆脱 sub2ind 并继续使用预分配技巧!希望,没关系! :)
  • @rayryeng 对于v20000000 的数据量更大,我得到了这些值 - pastebin.com/fxx16JaY。我跳过了 ismember,因为计算时间太长。
【解决方案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 MATLABother answer by @rayryeng 中所列。最重要的是,它使用sub2ind原始 版本。

【讨论】:

    猜你喜欢
    • 2019-05-02
    • 2015-06-01
    • 2018-12-07
    • 2014-11-29
    • 1970-01-01
    • 2022-01-23
    • 2011-09-26
    • 2013-05-28
    • 1970-01-01
    相关资源
    最近更新 更多