# Linear Algebra¶

Original Tutorial may be found here.

In :
import numpy as np


This Tutorial will use also the Linear Algebra (linalg) sub-module (sitting inside numpy).

Refer to the for full numpy.linalg utilities.

## Simple Array Operations¶

In :
a = np.array([[1.0, 2.0],
[3.0, 4.0]])

In :
a

Out:
array([[ 1.,  2.],
[ 3.,  4.]])
In :
a.transpose()

Out:
array([[ 1.,  3.],
[ 2.,  4.]])
In :
a

Out:
array([[ 1.,  2.],
[ 3.,  4.]])

Compute the inverse. This MathIsFun.com article provides some excellent examples in explaining inverse of a matrix.

In :
a_inv = np.linalg.inv(a)

In :
a_inv

Out:
array([[-2. ,  1. ],
[ 1.5, -0.5]])

We know that, multiplying (dot multiplication) a matrix and its inverse gives us an identify matrix (see the MathIsFun.com website!).

In :
np.dot(a, a_inv)

Out:
array([[  1.00000000e+00,   1.11022302e-16],
[  0.00000000e+00,   1.00000000e+00]])

See? :)

In :
u = np.eye(2) # unit 2x2 matrix; "eye" represents "I"  (Identify Matrix)

In :
u

Out:
array([[ 1.,  0.],
[ 0.,  1.]])
In :
j = np.array([[0.0, -1.0],
[1.0, 0.0]])

In :
j

Out:
array([[ 0., -1.],
[ 1.,  0.]])
In :
np.dot(j, j) # matrix product

Out:
array([[-1.,  0.],
[ 0., -1.]])

Use the trace() function to return the sum along the diagonals of the array. See this NumPy Doc.

In :
np.trace(u) # trace

Out:
2.0

Say we have three matrices:

ax = y

in which a and y are known.

We want to solve x.

In :
a = np.array([[1.0, 2.0],
[3.0, 4.0]])

In :
y = np.array([[5.], [7.]])

In :
a

Out:
array([[ 1.,  2.],
[ 3.,  4.]])
In :
y

Out:
array([[ 5.],
[ 7.]])
In :
x = np.linalg.solve(a, y)

In :
x

Out:
array([[-3.],
[ 4.]])

To prove that this is correct, this must be true:

ax = y
In :
s = (np.dot(a,x) == y)

In :
s

Out:
array([[ True],
[ True]], dtype=bool)
In :
s.all()

Out:
True

Or just visualize it:

In :
np.dot(a,x)

Out:
array([[ 5.],
[ 7.]])
In :
y

Out:
array([[ 5.],
[ 7.]])

It certain looks like:

ax = y

Now, eigenvalue...

In :
j

Out:
array([[ 0., -1.],
[ 1.,  0.]])
In :
np.linalg.eig(j)

Out:
(array([ 0.+1.j,  0.-1.j]),
array([[ 0.70710678+0.j        ,  0.70710678-0.j        ],
[ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))

See this article to learn more about eigenvalue and eigenvector.

## Matrix Class¶

Here is a short intro to the Matrix class.

In :
A = np.matrix('1.0 2.0; 3.0 4.0')

In :
A

Out:
matrix([[ 1.,  2.],
[ 3.,  4.]])
In :
type(A)

Out:
numpy.matrixlib.defmatrix.matrix
In :
A

Out:
matrix([[ 1.,  2.],
[ 3.,  4.]])
In :
A.T  # Transpose

Out:
matrix([[ 1.,  3.],
[ 2.,  4.]])
In :
X = np.matrix('5.0 7.0')

In :
X

Out:
matrix([[ 5.,  7.]])
In :
Y = X.T

In :
Y

Out:
matrix([[ 5.],
[ 7.]])
In :
A*Y   # matrix multiplication

Out:
matrix([[ 19.],
[ 43.]])
In :
A.I   # inverse

Out:
matrix([[-2. ,  1. ],
[ 1.5, -0.5]])
In :
np.linalg.solve(A,Y) # solving linear equation

Out:
matrix([[-3.],
[ 4.]])

## Indexing: Comparing Matrices and 2D Arrays¶

Note that there are some important differences between NumPy arrays and matrices. NumPy provides two fundamental objects: an N-dimensional array object and a universal function object. Other objects are built on top of these. In particular, matrices are 2-dimensional array objects that inherit from the NumPy array object. For both arrays and matrices, indices must consist of a proper combination of one or more of the following: integer scalars, ellipses, a list of integers or boolean values, a tuple of integers or boolean values, and a 1-dimensional array of integer or boolean values. A matrix can be used as an index for matrices, but commonly an array, list, or other form is needed to accomplish a given task.

As usual in Python, indexing is zero-based. Traditionally we represent a 2D array or matrix as a rectangular array of rows and columns, where movement along axis 0 is movement across rows, while movement along axis 1 is movement across columns.

Let's make an array and matrix to slice:

In :
A = np.arange(12)

In :
A

Out:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In :
A.shape = (3,4)

In :
A

Out:
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
In :
M = np.mat(A.copy())  # np.mat Interpret the input as a matrix.

In :
M

Out:
matrix([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
In :
print type(A)," ",type(M)

<type 'numpy.ndarray'>   <class 'numpy.matrixlib.defmatrix.matrix'>

In :
print A

[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]

In :
print M

[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]


Now, let's take some simple slices. Basic slicing uses slice objects or integers.

For example, the evaluation of A[:] and M[:] will appear familiar from Python indexing, however it is important to note that slicing NumPy arrays does not make a copy of the data; slicing provides a new view of the same data.

In :
print A[:]; print A[:].shape

[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
(3L, 4L)

In :
print M[:]; print M[:].shape

[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
(3L, 4L)


Now for something that differs from Python indexing: you may use commaseparated indices to index along multiple axes at the same time.

In :
print A[:,1]; print A[:,1].shape

[1 5 9]
(3L,)

In :
print M[:,1]; print M[:,1].shape

[

]
(3L, 1L)


Notice the difference in the last two results.

Use of a single colon for the 2D array produces a 1-dimensional array, while for a matrix it produces a 2-dimensional matrix. A slice of a matrix will always produce a matrix.

For example, a slice M[2,:] produces a matrix of shape (1,4).

In contrast, a slice of an array will always produce an array of the lowest possible dimension.

For example, if C were a 3-dimensional array, C[...,1] produces a 2D array while C[1,:,1] produces a 1-dimensional array.

From this point on, we will show results only for the array slice if the results for the corresponding matrix slice are identical.

Lets say that we wanted the 1st and 3rd column of an array. One way is to slice using a list:

In :
A

Out:
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
In :
A[:,[1,3]]

Out:
array([[ 1,  3],
[ 5,  7],
[ 9, 11]])
In :
A[:,].take([1,3],axis=1)

Out:
array([[ 1,  3],
[ 5,  7],
[ 9, 11]])

If we wanted to skip the first row, we could use:

In :
A[1:,].take([1,3],axis=1)

Out:
array([[ 5,  7],
[ 9, 11]])

Or we could simply use A[1:,[1,3]]. Yet another way to slice the above is to use a cross product:

In :
 A[1:,[1,3]]

Out:
array([[ 5,  7],
[ 9, 11]])
In :
A

Out:
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])

Now let's do something a bit more complicated. Lets say that we want to retain all columns where the first row is greater than 1. One way is to create a boolean index:

In :
A[0,:] > 1

Out:
array([False, False,  True,  True], dtype=bool)
In :
A[:, A[0,:] > 1]

Out:
array([[ 2,  3],
[ 6,  7],
[10, 11]])

Just what we wanted! But indexing the matrix is not so convenient.

In :
M

Out:
matrix([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
In :
M[0,:] > 1

Out:
matrix([[False, False,  True,  True]], dtype=bool)
In :
M[:, M.A[0,:]>1]   # Note the M.A

Out:
matrix([[ 2,  3],
[ 6,  7],
[10, 11]])

If we wanted to conditionally slice the matrix in two directions, we must adjust our strategy slightly. Instead of:

In :
A[A[:,0]>2,A[0,:]>1]

Out:
array([ 6, 11])
In :
M[M.A[:,0]>2,M.A[0,:]>1]

Out:
matrix([[ 6, 11]])

we need to use the cross product ix_():

In :
A[np.ix_(A[:,0]>2,A[0,:]>1)]

Out:
array([[ 6,  7],
[10, 11]])
In :
M[np.ix_(M.A[:,0]>2,M.A[0,:]>1)]

Out:
matrix([[ 6,  7],
[10, 11]])

# Conclusion¶

We have now completed lesson 7 - Linear Algebra.