从分类数组转换为二进制矩阵
Pivot to binary matrix from categorial array
我有一个数组,其中包含一些属于某个集合的值。我想将此数组转换为二进制矩阵,此矩阵的每一列将代表集合的每个可能值,与输入数组匹配的列的行值为 1,其他所有列的行值为 0。我认为它的名称类似于二进制枢轴。
输入数组是table类型的一列
输入数组示例(前面的例子只有大写字母,导致误读):
'Apple'
'Banana'
'Cherry'
'Dragonfruit'
'Apple'
'Cherry'
因此,在此示例中,输入可以采用 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 秒内完成。
编辑:我改进了示例,因为之前的示例导致了误解。
我假设您的输入数组是字符元胞数组,如下所示:
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,然后使用它索引到我们的零数组并相应地设置值。
类似于:
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
和第三个输出,因为您最初的查询有大写字母,这一切都很混乱。使用第三个输出可以轻松地将他们以前的方法转换为您的新规范。
顺便说一句,您的示例和您的代码不匹配。您的示例已设置,因此每一列都是一个索引,但它是每一行。对于时序测试,我将在具有 16 GB RAM 和 Intel i7 的 Mac OS X 10.10.3 上转置您的 result.I'm 运行 MATLAB R2013a 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
排名方面:
- 原始
sub2ind
sub2ind
sparse
bsxfun
- OP的方法
这是我对这个问题的看法:
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的回答。
如果您不介意在输入数组中有非连续字符的情况下所有列为零,例如 '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
and also listed in the 中所列。最重要的是,它使用 raw 版本的 sub2ind
.
我有一个数组,其中包含一些属于某个集合的值。我想将此数组转换为二进制矩阵,此矩阵的每一列将代表集合的每个可能值,与输入数组匹配的列的行值为 1,其他所有列的行值为 0。我认为它的名称类似于二进制枢轴。
输入数组是table类型的一列
输入数组示例(前面的例子只有大写字母,导致误读):
'Apple'
'Banana'
'Cherry'
'Dragonfruit'
'Apple'
'Cherry'
因此,在此示例中,输入可以采用 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 秒内完成。
编辑:我改进了示例,因为之前的示例导致了误解。
我假设您的输入数组是字符元胞数组,如下所示:
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,然后使用它索引到我们的零数组并相应地设置值。
类似于:
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
和第三个输出,因为您最初的查询有大写字母,这一切都很混乱。使用第三个输出可以轻松地将他们以前的方法转换为您的新规范。
顺便说一句,您的示例和您的代码不匹配。您的示例已设置,因此每一列都是一个索引,但它是每一行。对于时序测试,我将在具有 16 GB RAM 和 Intel i7 的 Mac OS X 10.10.3 上转置您的 result.I'm 运行 MATLAB R2013a 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
排名方面:
- 原始
sub2ind
sub2ind
sparse
bsxfun
- OP的方法
这是我对这个问题的看法:
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的回答。
如果您不介意在输入数组中有非连续字符的情况下所有列为零,例如 '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
and also listed in the sub2ind
.