2

I am looking for an efficient, vectorised way to expand a column vector of category numbers into a matrix of normalised features.

For example, I might have:

octave:16> false_vals
false_vals =

  -0.10000  -0.20000  -0.50000

octave:17> true_vals
true_vals =

   0.90000   0.80000   0.50000

octave:18> cats
cats =

   1
   2
   3

And I would like to combine them to create a matrix where each row was the same as false_vals but with the index position identified by cats set to the corresponding value from true_vals. For the example, that would look like this:

octave:19> X
X =

   0.90000  -0.20000  -0.50000
  -0.10000   0.80000  -0.50000
  -0.10000  -0.20000   0.50000

I found this question and answer that I can extend to give me a random allocation:

octave:28> X = repmat(false_vals,3,1)
X =

  -0.10000  -0.20000  -0.50000
  -0.10000  -0.20000  -0.50000
  -0.10000  -0.20000  -0.50000

octave:29> Y = repmat(true_vals,3,1)
Y =

   0.90000   0.80000   0.50000
   0.90000   0.80000   0.50000
   0.90000   0.80000   0.50000

octave:30> L = ( rand(3,3) > 0.5 ) % This stands in for the actual logical matrix I want
L =

   1   1   1
   1   1   1
   0   0   1

octave:31> X(L) = Y(L)
X =

   0.90000   0.80000   0.50000
   0.90000   0.80000   0.50000
  -0.10000  -0.20000   0.50000

But I am then stuck on how to replace the rand above with some function that can turn my cats vector into

L = 
 1  0  0
 0  1  0
 0  0  1

I.e. I know I can do what I want with a logical matrix, but don't know how to get from my vector cats to the correct logical matrix (for each row of L, column position identified by value of that row in cats vector should be true, so I can use it in the last statement).

Community
  • 1
  • 1
Neil Slater
  • 26,512
  • 6
  • 76
  • 94

2 Answers2

2

You can repeat the row with repmat and then replace the desired values using linear indexing:

n = numel(cats);
result = repmat(false_vals, n, 1); %// repeat row n times
result((cats.'-1)*n+(1:n)) = true_vals; %'// replace desired values
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
  • This works for that L being stated, but I think OP's general case requirement was for a rand based solution. +1 for a good starting point! – Divakar Oct 27 '14 at 13:24
  • OK, got it, the linear indexing is row first, which really confused me for a while. – Neil Slater Oct 27 '14 at 17:56
1

Code

N = 3; %// size of false_vals and true_vals

%// Expand false_vals to create X
X = repmat(false_vals,N,1)

%// Find linear indices for expanded matrix, where true_vals are to be put
%// For this, you would need two things - row and column indices.
%// 1. Row indices would be just [1:N], as we are looking to get one replacement
%// per row.
%// 2. Column indices would be either of 1 or 2 or .. N
ind = round((N-1)*rand(1,N)) %// ind would be column indices minus 1
true_vals_idx = N*ind+[1:N] %// [1:N] represent the row indices

%// Replace the elements at those linearly indexed positions with true_vals 
X(true_vals_idx) = true_vals

If along with the earlier criteria of one replacement per row, you would also like to have one replacement per column, you can modify the ind calculation part a bit like so -

%// Create unique numbers for the interval [1,N], which could also be calculated with 
%// randperm, but that being slow has been replaced by a sort-based implementation
[~,ind] = sort(rand(N,1)) 
true_vals_idx = N*(ind-1)+[1:N]

%// .. Rest of the code stays the same

Note: The sort based implementation to "re-create" randperm was based on this solution.

Community
  • 1
  • 1
Divakar
  • 218,885
  • 19
  • 262
  • 358
  • 1
    This is useful, and includes the answer I need, but I may have made things more complex by mentioning `rand()`. I am using it in my question to stand in for "I know I need a logical matrix here, but don't know how to create the correct one from `cats`" - I will edit and try to make clearer. – Neil Slater Oct 27 '14 at 15:41
  • @NeilSlater If that's the case, Luis's solution has to be the most efficient one. – Divakar Oct 27 '14 at 16:05