in Learning, MATLAB

MATLAB Vectorization Demystified – Part 2: Matrices and Indexing

“MATLAB Vectorization Demystified”: In this series, I try to explain vectorization operation in MATLAB in details step-by-step through several examples. This is part 2 in which “Matrices and Indexing” are explained.

Matrix definition

Every variable in MATLAB is an array that can hold many numbers, characters, strings, date and times, and so on. Arrays in MATLAB can be 2 or 3 dimensional or even higher. Note that the word “matrix” typically refers to a 2D array, whereas an “array” can be n-dimensional. Early versions of MATLAB supported only 2D matrices, not n-dimensional arrays. That’s the reason for different operations for matrix and array in MATLAB, as discussed in here.

Let’s start with 2D arrays or matrices in MATLAB. An M-by-N 2D array can be illustrated as follows:

2D array of any type

Let’s create a 3-by-3 2D matrix. We can use magic(n) function, which returns an n-by-n matrix constructed from the integers 1 to  n^2 .

sampleMatrix1 = magic(3)

sampleMatrix1 =
      8      1      6
      3      5      7
      4      9      2

To return the number of rows and columns of a matrix, we can use:

NoOfRows = size(sampleMatrix1 , 1)

NoOfRows =
3

NoOfCols = size(sampleMatrix1 , 2)

NoOfCols =
3

Alternatively, you can obtain the number of rows and columns with a single command:

[NoOfRows , NoOfCols] = size(sampleMatrix1)

NoOfRows =
3
NoOfCols =
3

There are two ways for indexing an array/matrix in MATLAB, which are subscript and linear indexing. Both ways are explained in the following sections.

Subscript indexing

In this approach, the indices of the rows and columns can be specified for indexing. For instance, if we want to retrieve the element in row 2 and column 3, we can say arrayName(2,3), where arrayName is the name of the array in your code. It can be shown graphically as follows:

2D array indexing using subscript

It can be seen that every element has a unique pair of (row,col) number that can be used for indexing.

Linear indexing

We can also refer to the elements of a matrix with a single subscript instead of a pair of (row,col) number, i.e., arrayName(k) where arrayName is the name of the array in your code. In fact, MATLAB stores matrices and arrays not in the shape that they appear when displayed in Command Window as in the figure above, but as a single column of elements. This single column is composed of all of the columns from the matrix, each appended to the last as shown in the figure below:

Linear indexing for a 2D array

Linear indexing for a 2D array

It can be seen that the elements from top to bottom of the columns from left to right as stacked on top of each other in the linear indexing.

Let’s see the two approaches in action. We want to access the element in 2nd row and 3rd column using both approaches, as shown in the figure below:

Linear and subscript indexing for a 2D array - example

sampleMatrix1(2 , 3)    % subscript indexing

ans =
      7

sampleMatrix1(8)    % 2M+2 = 2*3+2 = 8, linear indexing

ans =
      7

It is obvious that we have reached to the same element. So, it is working 🙂.

MATLAB provides sub2ind() and ind2sub() functions to allow switching between the two indexing approaches. The first command uses the given pair of (row,col) subscripts to calculate the linear subscripts. For instance, let’s find the linear index of (2,3) and (1,3) elements as shown in the figure below:

Converting subscripts to linear indices

Converting subscripts to linear indices

% Matrix size, row indices, column indices
sub2ind(size(sampleMatrix1) , [2 3] , [1 3])  

ans =
      2    9

Alternatively, we can use ind2sub command to find the associated row-column subscripts from a given set of linear indices:

% Matrix size, linear subscripts
[Rows , Cols] = ind2sub(size(sampleMatrix1) , [2 9])    

Rows =
      2    3
Cols =
      1    3

For the rest of this course, we use pair (row,col) subscript indexing unless otherwise mentioned.

Nonconsecutive elements multiple elements indexing using another vector

We can use what we learned in the first lesson about vectors to index a matrix/array. However, in the case of subscript indexing, we need to specify the indices for both rows and columns.

Let’s assume that we have a 4-by-8 matrix with random numbers. We are interested to return the odd elements of rows and even elements of columns:

rng(1,'twister')
% randi() generates uniformly distributed pseudorandom integers
sampleMatrix2 = randi(1000 , 4 , 8)        

sampleMatrix2 =
      418    147    397    205    418    801    877    170
      721    93     539    879    559    969    895    879
      1      187    420    28     141    314    86     99
      303    346    686    671    199    693    40     422

sampleMatrix2(1:2:end , 2:2:end) 

ans =
      1472    205     801     170
      187     28      314     99

From the example above, it is clear that we can use end reserved keyword for both rows and columns. end keyword in the row position points to the number of rows while end in the second position refers to the end of columns. Colon operator : is another reserved keyword which returns all the elements of a specific dimension. Let’s say we want to return all rows and every third column of sampleMatrix2:

sampleMatrix2(: , 3:3:end)

ans =
      397    801
      539    969
      420    314
      686    693

If we are interested in specific rows or columns, we can use new vector to specify the subscripts. Let’s say we want to return 3rd and 4th rows and every third column:

sampleMatrix2([3 4] , 3:3:end)

ans =
      420    314
      686    693

Single-colon indexing

When you index into a standard MATLAB array using a single colon :, MATLAB returns a column vector which follows the linear subscript rule to retrieve the elements from the matrix:

sampleMatrix2N = [1 2 3 ; 4 5 6]

sampleMatrix2N =
      1    2    3
      4    5    6

sampleMatrix2N(:)

sampleMatrix2N =
      1
      4
      2
      5
      3
      6

Logical indexing

Similar to vectors, MATLAB allows logical indexing for matrix/arrays. The principles are the same except that you should implement the logics for both rows and columns.

Let’s say we want to return the row-column subscripts of all the odd elements in sampleMatrix2:

rem(sampleMatrix2 , 2) ~= 0

ans =
      4 × 8 logical array
      0    1    1    1    0    1    1    0
      1    1    1    1    1    1    1    1
      1    1    0    0    1    0    0    1
      1    0    0    1    1    1    0    0

You can see that a logical array is returned with the same number of rows and columns as in sampleMatrix2. Where the element of the matrix is odd, the logical array shows 1 and vice versa.

To return the actual numbers:

% I transposed just for better display
sampleMatrix2(rem(sampleMatrix2 , 2) ~= 0)'     

ans =
      721    1    303    147    93    187    397    539    205    879    671    559    141    199    801    969    693    877    895    879    99

If we are interested in the associated rows and columns indices, we can use find() command:

% I transposed just for better display
find(rem(sampleMatrix2 , 2) ~= 0)'   

ans =
      2    3    4    5    6    7    9    10    13    14    16    18    19    20    21    22    24    25    26    30    31

You can see that what is returned is the linear subscript, not the pair of (row,col) subscripts indices. We already learned how to do the conversion, right?:

[Rows , Cols] = ind2sub(size(sampleMatrix2) , ...
                     find(rem(sampleMatrix2 , 2) ~= 0));
disp(Rows') , disp(Cols')     % just for better display

ans =
      2    3    4    1    2    3    1    2    1    2    4    2    3    4    1    2    4    1    2    2    3
      1    1    1    2    2    2    3    3    4    4    4    5    5    5    6    6    6    7    7    8    8

Let’s say we need the row numbers in which the elements are divisible by 2 and 5:

% I transposed just for better display
sampleMatrix2(rem(sampleMatrix2 , 2) == 0 & ...
              rem(sampleMatrix2 , 5) == 0)' 

ans =
      420    40    170

What really happens if we don’t want to use logical indexing? How does the code look like to run the above operation? Well, we definitely need to run through every element and check with the given condition:

% placeholder to save the results
results = [];        
% for large matrices, pre-allocation of results 
% might cost higher computation time
% index to count the elements in results
Ind = 1;         
% to loop through all the columns   
for i = 1:size(sampleMatrix2 , 2) 
    % to loop through all the rows   
    for j = 1:size(sampleMatrix2 , 1)
        % checking the condition
        if rem(sampleMatrix2(j , i) , 2) == 0 && ...
           rem(sampleMatrix2(j , i) , 5) == 0  
            results(Ind) = sampleMatrix2(j , i);
            Ind = Ind + 1;
        end
    end
end
results

results =
      420    40    170

It yields the same results, but with the cost of more complicated, longer, and uglier code and probably slower for large matrices.

Assigning to elements outside array bounds

When you assign to elements outside the bounds of a numeric array, MATLAB expands the array to include those elements and fills the missing values with 0.

rng(1 , 'twister')
sampleMatrix3 = magic(3)

sampleMatrix3 =
      8    1    6
      3    5    7
      4    9    2

sampleMatrix3(3 , 4) = -10

sampleMatrix3 =
      8    1    6    0
      3    5    7    0
      4    9    2    -10

Let’s see another example which affects the number of rows:

rng(1 , 'twister')
sampleMatrix3 = magic(3)

sampleMatrix4 =
      8    1    6
      3    5    7
      4    9    2

sampleMatrix4(5 , 2) = -20

sampleMatrix3 =
      8    1    6
      3    5    7
      4    9    2
      0    0    0
      0   -20   0

Exercise 1: We have a 3-by-7 matrix which is called sampleMatrixE1. We need to shift the matrix 3 columns to the left without changing the rows structure in a single line of code.

  rng(1 , 'twister')
  sampleMatrixE1 = randi(1000 , 3 , 7)
  

sampleMatrixE1 =
      418    303    187    539    205    671    141
      721    147    346    420    879    418    199
      1      93     397    686    28     559    801

Answer:

  sampleMatrixE1(: , [4:end 1:3])
  

ans =
      539    205    671    141    418    303    187
      420    879    418    199    721    147    346
      686    28     559    801    1      93     397

Another way is to use circshift() function which allows to shift a vector or array by k positions:

   % doc circshift: positive k to shift to right, 
  % negative values to shift to left
  circshift(sampleMatrixE1 , -3 , 2)     
  

ans =
      539    205    671    141    418    303    187
      420    879    418    199    721    147    346
      686    28     559    801    1      93     397

Exercise 2: We have a 4-by-8 numeric array with positive and negative elements. We want to remove the columns in which there is at list 1 negative value while preserving all the rows in only one line of code:

  rng(1 , 'twister')
  sampleMatrixE2 = randi(1000 , 4 , 8) - 100
  

sampleMatrixE2 =
      318    47     297    105    318    701    777    70
      621    -7     439    779    459    869    795    779
      -99    87     320    -72    41     214    -14    -1
      203    246    586    571    99     593    -60    322

Hint: you can use sum() function to solve this.

Answer:

  sampleMatrixE2(: , ~logical(sum(sampleMatrixE2 < 0 , 1)))
  

ans =
      297    318    701
      439    459    869
      320    41     214
      586    99     593

Exercise 3: Assume that we have a 4-by-4 array, which is sampleMatrixE3. We need to return the elements from the rows in which all numbers are larger than 100 AND columns in which all numbers are smaller than 800 in an only one line of code:

  rng(1 , 'twister')
  sampleMatrixE2 = randi(1000 , 4 , 8) - 100
  

sampleMatrixE3 =
      418    147    397    205
      721    93     539    879
      1      187    420    28
      303    346    686    671

Hint: you can use sum() function to solve this.

Answer:

  sampleMatrixE3(~(sum(sampleMatrixE3 > 100 , 2) ~= end) , ...
      ~(sum(sampleMatrixE3 < 800 , 1) ~= end))
  

ans =
      418    147    397
      303    346    686

Exercise 4: We have sampleMatrixE4 and sampleMatrixE5 arrays where the number of rows and columns of the first array is always higher than the second one. Assume that the two arrays are mapped as shown in the figure below:

Exercise 4 of part 2

Exercise 4 of part 2

We want to return the elements of sampleMatrixE4 where the mapped elements of sampleMatrixE5 is greater than 500. You should be able to do it in 2 lines of code.

  rng(1 , 'twister')
  sampleMatrixE4 = randi(1000 , 5 , 5)
  sampleMatrixE5 = randi(1000 , 3 , 3)
  

sampleMatrixE4 =
      418    93     420    671    801
      721    187    686    418    969
      1      346    205    559    314
      303    397    879    141    693
      147    539    28     199    877

sampleMatrixE5 =
      895    170    422
      86     879    958
      40     99     534

Hint: you’d probably need ind2sub() and sub2ind() functions.

Answer:

  [rows , cols] = ind2sub(size(sampleMatrixE5), ...
                    find(sampleMatrixE5 > 500))
  

rows =
      1
      2
      2
      3

cols =
      1
      2
      3
      3

  sampleMatrixE4(sub2ind(size(sampleMatrixE4) , ...
    rows + (size(sampleMatrixE4 , 1) - size(sampleMatrixE5 , 1)) , ...
    cols + (size(sampleMatrixE4 , 2) - size(sampleMatrixE5 , 2))))
  

ans =
      205
      141
      693
      877

Comments

Comment