Matrices

We know that the notation [1, 2, 3] is used to create an array. In fact, this notation denotes a special type of array, called a (column) vector in Julia, as shown in the following screenshot:

To create this as a row vector (1 2 3), use the notation [1 2 3] with spaces instead of commas. This array is of type 1 x 3 Array{Int64,2}, so it has two dimensions. (The spaces used in [1, 2, 3] are for readability only, we could have written this as [1,2,3]).

A matrix is a two- or multidimensional array (in fact, a matrix is an alias for the two-dimensional case). We can write this as follows:

Array{Int64, 1} == Vector{Int64} #> true
Array{Int64, 2} == Matrix{Int64} #> true

As matrices are so prevalent in data science and numerical programming, Julia has an amazing range of functionalities for them.

To create a matrix, use space-separated values for the columns and semicolon-separated for the rows:

// code in Chapter 5matrices.jl:
matrix = [1 2; 3 4] 
    2x2 Array{Int64,2}:
    1  2
    3  4

So, the column vector from the beginning can also be written as [1; 2; 3]. However, you cannot use commas and semicolons together.

To get the value from a specific element in the matrix, you need to index it by row and then by column, for example, matrix[2, 1] returns the value 3 (second row, first column).

Using the same notation, one can calculate products of matrices such as [1 2] * [3 ; 4]; this is calculated as [1 2] * [3 4], which returns the value 11 (which is equal to 1*3 + 2*4). In contrast to this, conventional matrix multiplication is defined with the operator .*:

[1 2] .* [3 ; 4] 
# 2 Array{Int64,2}: 
#  3  6 
#  4  8  

To create a matrix from random numbers between 0 and 1, with three rows and five columns, use ma1 = rand(3, 5), which shows the following results:

3x5 Array{Float64,2}:
 0.0626778  0.616528  0.60699   0.709196  0.900165
 0.511043   0.830033  0.671381  0.425688  0.0437949
 0.0863619  0.621321  0.78343   0.908102  0.940307

The ndims function can be used to obtain the number of dimensions of a matrix. Consider the following example:

 julia> ndims(ma1) #> 2  
 julia> size(ma1) #> a tuple with the dimensions (3, 5)  

To get the number of rows (3), run the following command:

julia>   size(ma1,1) #> 3  

The number of columns (5) is given by:

julia> size(ma1,2) #> 5 
julia> length(ma1) #> 15, the number of elements 

That's why you will often see this: nrows, ncols = size(ma), where ma is a matrix, nrows is the number of rows, and ncols is the number of columns.

If you need an identity matrix, where all the elements are zero, except for the elements on the diagonal that are 1.0, use the I function (from the LinearAlgebra package) with the argument 3 for a 3 x 3 matrix:

using LinearAlgebra    
  idm = Matrix(1.0*I, 3, 3)     
#> 3x3 Array{Float64,2}:
 1.0  0.0  0.0
 0.0  1.0  0.0
 0.0  0.0  1.0

You can easily work with parts of a matrix, known as slices; these are similar to those used in Python and NumPy as follows:

  • idm[1:end, 2] or shorter idm[:, 2] returns the entire second column
  • idm[2, :] returns the entire second row
  • idmc = idm[2:end, 2:end] returns the output as follows:
2x2 Array{Float64,2}
    1.0  0.0
    0.0  1.0
  • idm[2, :] .= 0 sets the entire second row to 0
  • idm[2:end, 2:end] = [ 5 7 ; 9 11 ] will change the matrix as follows:
1.0  0.0  0.0
0.0  5.0  7.0
0.0  9.0  11.0

Slicing operations create views into the original array rather than copying the data, so a change in the slice changes the original array or matrix.

Any multidimensional matrix can also be seen as a one-dimensional vector in column order, as follows:

a = [1 2;3 4] 
2 Array{Int64,2}: 
 1  2 
 3  4 
 
a[:] 
4-element Array{Int64,1}: 
 1 
 3 
 2 
 4 

To make an array of arrays (a jagged array), use an Array initialization, and then push! each array in its place, for example:

jarr = (Array{Int64, 1})[]
push!(jarr, [1,2])
push!(jarr, [1,2,3,4])
push!(jarr, [1,2,3]) 
#=>
3-element Array{Array{Int64,1},1}:
 [1,2]
 [1,2,3,4]
 [1,2,3]  

If ma is a matrix, say [1 2; 3 4], then ma' is the transpose matrix, that is [1 3; 2 4]:

ma:   1  2            ma'  1   3
      3  4                 2   4  

ma' is an operator notation for the transpose(ma) function.

Multiplication is defined between matrices, as in mathematics, so ma * ma' returns the 2 x 2 matrix or type Array{Int64,2} as follows:

5    11
11   25

If you need element-wise multiplication, use ma .* ma', which returns 2 x 2 Array{Int64,2}:

1   6
6  16 

The inverse of a matrix ma (if it exists) is given by the inv(ma) function. The inv(ma) function returns 2 x 2 Array{Float64,2}:

-2.0   1.0
1.5   -0.5

The inverse means that ma * inv(ma) produces the identity matrix:

1.0   0.0
0.0   1.0
Trying to take the inverse of a singular matrix (a matrix that does not have a well-defined inverse) will result in LAPACKException or SingularException, depending on the matrix type. Suppose you want to solve the ma1 * X = ma2 equation, where ma1, X, and ma2 are matrices. The obvious solution is X = inv(ma1) * ma2. However, this is actually not that good. It is better to use the built-in solver, where X = ma1 ma2. If you have to solve the X * ma1 = ma2 equation, use the solution X = ma2 / ma1. Solutions that use / and are much more numerically stable, and also much faster.

If v = [1.,2.,3.] and w = [2.,4.,6.], and you want to form a 3 x 2 matrix with these two column vectors, then use hcat(v, w) (for horizontal concatenation) to produce the following output:

1.0  2.0
2.0  4.0
3.0  6.0

vcat(v,w) (for vertical concatenation) results in a one-dimensional array with all the six elements with the same result as append!(v, w).

Thus, hcat concatenates vectors or matrices along the second dimension (columns), while vcat concatenates along the first dimension (rows). The more general cat can be used to concatenate multidimensional arrays along arbitrary dimensions.

There is an even simpler literal notation: to concatenate two matrices a and b with the same number of rows to a matrix c, just execute c = [a b]. now b is appended to the right of a. To put b beneath c, use c = [a; b]. The following is a concrete example, a = [1 2; 3 4]and b = [5 6; 7 8]:

a

b

c = [a b]

c = [a; b]

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

 

 

The reshape function changes the dimensions of a matrix to new values if this is possible, for example:

reshape(1:12, 3, 4) #> returns a 3x4 array with the values 1 to 12
3x4 Array{Int64,2}:
 1  4  7  10
 2  5  8  11
 3  6  9  12
   
a = rand(3, 3)  #> produces a 3x3 Array{Float64,2}
3x3 Array{Float64,2}:
 0.332401   0.499608  0.355623
 0.0933291  0.132798  0.967591
 0.722452   0.932347  0.809577
    
reshape(a, (9,1)) #> produces a 9x1 Array{Float64,2}:
9x1 Array{Float64,2}:
 0.332401
 0.0933291
 0.722452
 0.499608
 0.132798
 0.932347
 0.355623
 0.967591
 0.809577
    
reshape(a, (2,2)) #> does not succeed:
ERROR: DimensionMismatch("new dimensions (2,2) must be consistent 
with array size 9")

When working with arrays that contain arrays, it is important to realize that such an array contains references to the contained arrays, not their values. If you want to make a copy of an array, you can use the copy() function, but this produces only a shallow copy with references to the contained arrays. In order to make a complete copy of the values, we need to use the deepcopy() function.

The following example makes this clear:

x = Array{Any}(undef, 2) #> 2-element Array{Any,1}: #undef #undef 
x[1] = ones(2) #> 2-element Array{Float64} 1.0 1.0 
x[2] = trues(3) #> 3-element BitArray{1}: true true true 
x #> 2-element Array{Any,1}: [1.0,1.0] Bool[true,true,true] 
a = x  
b = copy(x) 
c = deepcopy(x) # Now if we change x: x[1] = "Julia" x[2][1] = false x #> 2-element Array{Any,1}: "Julia" Bool[false,true,true] a #> 2-element Array{Any,1}: "Julia" Bool[false,true,true] isequal(a, x) #> true, a is identical to x b #> 2-element Array{Any,1}: [1.0,1.0] Bool[false,true,true] isequal(b, x) #> false, b is a shallow copy of x c #> 2-element Array{Any,1}: [1.0,1.0] Bool[true,true,true] isequal(c, x) #> false

The value of a remains identical to x when this changes, because it points to the same object in the memory. The deep copy c function remains identical to the original x. The b value retains the changes in a contained array of x, but not if one of the contained arrays becomes another array.

To further increase performance, consider using the statically-sized and immutable vectors and matrices from the ImmutableArrays package, which is a lot faster, certainly for small matrices, and particularly for vectors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.97.216