4

I have a large column vector y containing integer values from 1 to 10. I wanted to convert it to a matrix where each row is full of 0s except for a 1 at the index given by the value at the respective row of y.

This example should make it clearer:

y = [3; 4; 1; 10; 9; 9; 4; 2; ...]

% gets converted to:

Y = [
    0 0 1 0 0 0 0 0 0 0;
    0 0 0 1 0 0 0 0 0 0;
    1 0 0 0 0 0 0 0 0 0;
    0 0 0 0 0 0 0 0 0 1;
    0 0 0 0 0 0 0 0 1 0;
    0 0 0 0 0 0 0 0 1 0;
    0 0 0 1 0 0 0 0 0 0;
    0 1 0 0 0 0 0 0 0 0;
    ...
    ]

I have written the following code for this (it works):

m = length(y);
Y = zeros(m, 10);
for i = 1:m
    Y(i, y(i)) = 1;
end

I know there are ways I could remove the for loop in this code (vectorizing). This post contains a few, including something like:

Y = full(sparse(1:length(y), y, ones(length(y),1)));

But I had to convert y to doubles to be able to use this, and the result is actually about 3x slower than my "for" approach, using 10.000.000 as the length of y.

  1. Is it likely that doing this kind of vectorization will lead to better performance for a very large y? I've read many times that vectorizing calculations leads to better performance (not only in MATLAB), but this kind of solution seems to result in more calculations.

  2. Is there a way to actually improve performance over the for approach in this example? Maybe the problem here is simply that acting on doubles instead of ints isn't the best thing for comparison, but I couldn't find a way to use sparse otherwise.

Community
  • 1
  • 1
CanisLupus
  • 583
  • 5
  • 15
  • maybe if you change the array names from `y` and `Y` to some thing different like `x` and `y`, that could help a little. once I was using `ECG` as a name and it got my code working so slow, until I realized that `ecg` is a MATLAB function. – Rashid Nov 02 '14 at 20:01
  • That is good advice ;) Maybe it's a bit confusing for readers too. Now I can't change it or all the questions would need modifications, but I'll remember that next time. – CanisLupus Nov 02 '14 at 21:05

3 Answers3

3

Here is a test to comapre:

function [t,v] = testIndicatorMatrix()
    y = randi([1 10], [1e6 1], 'double');
    funcs = {
        @() func1(y);
        @() func2(y);
        @() func3(y);
        @() func4(y);
    };

    t = cellfun(@timeit, funcs, 'Uniform',true);
    v = cellfun(@feval, funcs, 'Uniform',false);
    assert(isequal(v{:}))
end
function Y = func1(y)
    m = numel(y);
    Y = zeros(m, 10);
    for i = 1:m
        Y(i, y(i)) = 1;
    end
end

function Y = func2(y)
    m = numel(y);
    Y = full(sparse(1:m, y, 1, m, 10, m));
end

function Y = func3(y)
    m = numel(y);
    Y = zeros(m,10);
    Y(sub2ind([m,10], (1:m).', y)) = 1;
end

function Y = func4(y)
    m = numel(y);
    Y = zeros(m,10);
    Y((y-1).*m + (1:m).') = 1;
end

I get:

>> testIndicatorMatrix
ans =
    0.0388
    0.1712
    0.0490
    0.0430

Such a simple for-loop can be dynamically JIT-compiled at runtime, and would run really fast (even slightly faster than vectorized code)!

Amro
  • 123,847
  • 25
  • 243
  • 454
  • 1
    Good benchmarking! +1. Could you use `Y(m,10) = 0;` instead for last two `funcs`, as that must speed it up a bit. – Divakar Nov 02 '14 at 20:17
  • 1
    I think the OP is optimizing prematurely; I doubt this task of creating the indicator matrix is the bottleneck in the code! All of them run in a fraction of a second for a vector with millions of elements :) – Amro Nov 02 '14 at 20:20
  • Yeah, but I guess for the context of this problem and see how the approaches mentioned thus far fair, that would be fair I guess. Damn, too many "fair" used there :) – Divakar Nov 02 '14 at 20:22
  • right. I guess we should mention this question: http://stackoverflow.com/questions/14169222/faster-way-to-initialize-arrays-via-empty-matrix-multiplication-matlab – Amro Nov 02 '14 at 20:24
  • Actually, the original source was this one I think - http://undocumentedmatlab.com/blog/preallocation-performance I mean I found out about this first there. – Divakar Nov 02 '14 at 20:26
  • yes, there's part 2 as well: http://undocumentedmatlab.com/blog/allocation-performance-take-2 – Amro Nov 02 '14 at 20:26
  • The creation of the matrix is indeed not a bottleneck, but I was curious to find what I could do to vectorize it while keeping performance, and why the simple cycle was winning in this case :) I find this answer to be the most comprehensive one, even though Divakar's version seems to win for larger input. This is a good list of approaches, so thank you all for them! – CanisLupus Nov 02 '14 at 20:54
  • By the way, good to know about `Y(m,10) = 0;` if the need arises ;) – CanisLupus Nov 02 '14 at 20:57
  • @CanisLupus But it's not winning anymore, check out the vectorized approach mentioned in the other solution ? :) That `Y(m,10) = 0;` thing made all the difference in fact. – Divakar Nov 02 '14 at 20:58
  • I finished editing my comment prematurely by accident, so I assume you're saying the for approach is not winning anymore (the last two sentences were missing). Yes, you're right :) – CanisLupus Nov 02 '14 at 21:00
  • @CanisLupus No, `for-loop` isn't winning! That's what I am saying! Check out the benchmark edits and results mentioned in the [other solution](http://stackoverflow.com/a/26703682/3293881)? – Divakar Nov 02 '14 at 21:02
  • indeed, that can be a faster way to initialize a matrix of zeros. But personally I wouldn't use it in production code. Whichever one you choose (for-loop or the vectorized code), I'd always initialize the matrix normally using `zeros`. IMO I would rather pick readable idiomatic code rather than relying on a particular implementation detail that can change [between versions](http://undocumentedmatlab.com/blog/allocation-performance-take-2#comment-243075). As Donald Knuth [once said](http://c2.com/cgi/wiki?PrematureOptimization): "Premature optimization is the root of all evil" :) – Amro Nov 02 '14 at 21:09
  • @CanisLupus Take your pick. Performance or production-level guarantee? ;) I mean if you know what you are doing, you should be good there! I think it's a personal opinion maybe. Agreed on the Donald's comment though :) Oh by the way, if you encapsulate it into functions, you should be okay too there! – Divakar Nov 02 '14 at 21:15
  • Yes, I agree with using `zeros` in the general case. Unless performance is really key (even then the difference is not much). I should add that in my machine the `for-loop` version always wins by a fair margin, even when against Divakar's method (which uses `Y(m,10) = 0;`), and does so consistently even when input gets larger (tested up to 3e6) :) – CanisLupus Nov 02 '14 at 21:25
1

It seems you are looking for that full numeric matrix Y as the output. So, you can try this approach -

m = numel(y);
Y1(m,10) = 0; %// Faster way to pre-allocate zeros than using function call `zeros`
  %// Source - http://undocumentedmatlab.com/blog/preallocation-performance
linear_idx = (y-1)*m+(1:m)'; %//'# since y is mentioned as a column vector, 
                              %// so directly y can be used instead of y(:)
Y1(linear_idx)=1; %// Y1 would be the desired output

Benchmarking

Using Amro's benchmark post and increasing the datasize a bit -

y = randi([1 10], [1.5e6 1], 'double');

And finally doing the faster pre-allocation scheme mentioned earlier of using Y(m,10)=0; instead of Y = zeros(m,10);, I got these results on my system -

>> testIndicatorMatrix
ans =
    0.1798
    0.4651
    0.1693
    0.1457

That is the vectorized approach mentioned here (the last one in the benchmark suite) is giving you more than 15% performance improvement over your for-loop code (the first one in the benchmark suite). So, if you are using large datasizes and intend to get full versions of sparse matrices, this approach would make sense (in my personal opinion).

Community
  • 1
  • 1
Divakar
  • 218,885
  • 19
  • 262
  • 358
0

Does something like this not work for you?

tic;
N = 1e6;
y = randperm( N );
Y = spalloc( N, N, N );
inds = sub2ind( size(Y), y(:), (1:N)' );
Y = sparse( 1:N, y, 1, N, N, N );
toc

The above outputs

Elapsed time is 0.144683 seconds.

AnonSubmitter85
  • 933
  • 7
  • 14