Lec 2-3 - Create arrays, array shapes

import numpy as np
np.ones, np.ones_like, np.zeros, np.eye, np.arange, np.linspace, np.logspace
(<function numpy.ones(shape, dtype=None, order='C', *, like=None)>,
 <function ones_like at 0x7f1f9c7c3230>,
 <function numpy.zeros>,
 <function numpy.eye(N, M=None, k=0, dtype=<class 'float'>, order='C', *, like=None)>,
 <function numpy.arange>,
 <function linspace at 0x7f1f9c619730>,
 <function logspace at 0x7f1f9c619930>)
np.ones(3)
array([1., 1., 1.])
np.ones(3).shape
(3,)
np.zeros(4)
array([0., 0., 0., 0.])
np.eye(4)
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
np.eye(4).shape
(4, 4)
np.zeros_like(np.eye(4))
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
np.zeros_like(np.eye(4)).shape
(4, 4)
np.ones_like(np.eye(4))
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])
np.arange(7)
array([0, 1, 2, 3, 4, 5, 6])
np.linspace(4, 8, num=9)
array([4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. ])
np.logspace(0, 2, num=3)
array([  1.,  10., 100.])

Specify entries manually with a list

np.array([1.0, 2.0, 3.3])
array([1. , 2. , 3.3])
A = np.array([[1.0, 2.0, 3.0],
          [1.1, 2.2, 3.3]])
A
array([[1. , 2. , 3. ],
       [1.1, 2.2, 3.3]])
A.shape
(2, 3)

Random numbers

np.random.uniform(size=(3, 4))
array([[0.45277498, 0.71551166, 0.22367996, 0.2476122 ],
       [0.34208851, 0.77209602, 0.68308149, 0.32963277],
       [0.35762772, 0.791859  , 0.21868476, 0.33329781]])
np.random.normal(size=(3, 4))
array([[-1.72524041,  2.32250734, -0.29485922,  0.64230775],
       [-1.73508202,  2.3038945 ,  0.64756682, -1.36955389],
       [-0.65310368, -0.58493861, -0.84548246,  0.46837203]])

Chanding dtype

o = np.ones(3, dtype=np.int8)
o.dtype
dtype('int8')

Accessing array by single cells

o[1]
1
o[2]
1
A 
array([[1. , 2. , 3. ],
       [1.1, 2.2, 3.3]])
A[0, 0]
1.0
A[0, 1]
2.0
A[1, 2]
3.3
A[1, 3]
IndexError: index 3 is out of bounds for axis 1 with size 3

Accessing arrays with slices

B = np.arange(24).reshape((4, 6))
B
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
# The two middle rows:
B[1:3, :]
array([[ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17]])
# The two middle rows, only second column
B[1:3, 1]
array([ 7, 13])
# Use two columns (two ":") to skip some integers periodically
B
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
B[:, 0:7:2] # keep every other column
array([[ 0,  2,  4],
       [ 6,  8, 10],
       [12, 14, 16],
       [18, 20, 22]])
B[:, ::2] # keep every other column
array([[ 0,  2,  4],
       [ 6,  8, 10],
       [12, 14, 16],
       [18, 20, 22]])
B[:, 0:7:3] # keep one in three columns
array([[ 0,  3],
       [ 6,  9],
       [12, 15],
       [18, 21]])

Comparing arrays

The tol (precision under which two numbers will be considered equal) can be modified, see the rtol and atol argument at https://numpy.org/doc/stable/reference/generated/numpy.isclose.html

np.isclose, np.allclose # these functions help to compare floats arrays for equality.
(<function isclose at 0x7f1f9c7ccaf0>, <function allclose at 0x7f1f9c7cc970>)
np.isclose(np.exp(-20), 0.)
True
np.exp(-3000) == 0.
True
np.exp(-40) == 0.
False

Operations along axis:

np.sum, np.mean
(<function sum at 0x7f1f9c7b7af0>, <function mean at 0x7f1f9c7c1a30>)
B
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
B.sum()
276
B.sum(axis=0)
array([36, 40, 44, 48, 52, 56])
np.sum(B, axis=0)
array([36, 40, 44, 48, 52, 56])
np.mean(B, axis=1)
array([ 2.5,  8.5, 14.5, 20.5])

Adding, multiplying arrays componentwise

C = np.ones_like(B)
C
array([[1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1]])
B
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
B + C
array([[ 1,  2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11, 12],
       [13, 14, 15, 16, 17, 18],
       [19, 20, 21, 22, 23, 24]])
B + 3*C
array([[ 3,  4,  5,  6,  7,  8],
       [ 9, 10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19, 20],
       [21, 22, 23, 24, 25, 26]])

Componentwise multiplicaiont (this is not the matrix product)

C * B
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])

Matrix product

C @ B # fails because C is (4, 6) and B is (4, 6)
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 4 is different from 6)
C.shape, B.T.shape
((4, 6), (6, 4))
C @ B.T
array([[ 15,  51,  87, 123],
       [ 15,  51,  87, 123],
       [ 15,  51,  87, 123],
       [ 15,  51,  87, 123]])
B.T @ C
array([[36, 36, 36, 36, 36, 36],
       [40, 40, 40, 40, 40, 40],
       [44, 44, 44, 44, 44, 44],
       [48, 48, 48, 48, 48, 48],
       [52, 52, 52, 52, 52, 52],
       [56, 56, 56, 56, 56, 56]])

Shape

np.transpose, np.newaxis, np.expand_dims, np.reshape, np.swapaxes, np.moveaxis
(<function transpose at 0x7f1f9c7b5830>,
 None,
 <function expand_dims at 0x7f1f9c50f570>,
 <function reshape at 0x7f1f9c7b4330>,
 <function swapaxes at 0x7f1f9c7b5670>,
 <function moveaxis at 0x7f1f9c7cc430>)
B.shape
(4, 6)
B.T
array([[ 0,  6, 12, 18],
       [ 1,  7, 13, 19],
       [ 2,  8, 14, 20],
       [ 3,  9, 15, 21],
       [ 4, 10, 16, 22],
       [ 5, 11, 17, 23]])
B.T.shape
(6, 4)
B[np.newaxis, :, :].shape
(1, 4, 6)
B[:, :, np.newaxis].shape
(4, 6, 1)
B[:, np.newaxis, :].shape
(4, 1, 6)

Concatenate, stacking arrays

np.stack, np.vstack, np.hstack, np.column_stack
(<function stack at 0x7f1f9c7c2df0>,
 <function vstack at 0x7f1f9c7c2a30>,
 <function hstack at 0x7f1f9c7c2c30>,
 <function column_stack at 0x7f1f9c50f670>)
A = np.random.normal(size=(2, 3))
AA= np.random.normal(size=(2, 3))
B = np.random.normal(size=(2, 2))
C = np.random.normal(size=(3, 3))

The following concatenation/stacking operations are allowed

stack by creating a new axis

np.stack([A, AA]).shape
(2, 2, 3)
print(np.stack([A, AA, A, AA]).shape)
np.stack([A, AA, A, AA])
(4, 2, 3)
array([[[ 1.09640081,  2.30891361,  0.86050472],
        [-0.53307374, -0.79932614,  0.03586915]],

       [[ 0.81326335,  0.78246611, -0.01831325],
        [ 0.91955669,  0.71259824,  0.51169635]],

       [[ 1.09640081,  2.30891361,  0.86050472],
        [-0.53307374, -0.79932614,  0.03586915]],

       [[ 0.81326335,  0.78246611, -0.01831325],
        [ 0.91955669,  0.71259824,  0.51169635]]])
# the first index of the newly created axis (first new axis) contais A
# the second index of the newly created axis (first new axis) contais AA
assert np.allclose(np.stack([A, AA])[0], A)
assert np.allclose(np.stack([A, AA])[1], AA)
np.stack([A, AA, A**2, 7*A]).shape
(4, 2, 3)

vertical stacking, without creating a new axis

np.vstack([A, AA]).shape
(4, 3)
np.stack([A, AA])
array([[[ 1.09640081,  2.30891361,  0.86050472],
        [-0.53307374, -0.79932614,  0.03586915]],

       [[ 0.81326335,  0.78246611, -0.01831325],
        [ 0.91955669,  0.71259824,  0.51169635]]])
np.vstack([A, AA])
array([[ 1.09640081,  2.30891361,  0.86050472],
       [-0.53307374, -0.79932614,  0.03586915],
       [ 0.81326335,  0.78246611, -0.01831325],
       [ 0.91955669,  0.71259824,  0.51169635]])
A.shape, C.shape
((2, 3), (3, 3))
np.vstack([A, C]).shape
(5, 3)
A.shape, B.shape
((2, 3), (2, 2))
np.vstack([A, B]).shape
ValueError: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 3 and the array at index 1 has size 2

Horizontal stacking

np.hstack([A, AA]).shape
(2, 6)
np.hstack([A, AA, B]).shape
(2, 8)
A.shape, C.shape
((2, 3), (3, 3))
# Horizontal stacking fails
np.hstack([A, C])
ValueError: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 0, the array at index 0 has size 2 and the array at index 1 has size 3

column_stack performs horizontal stacking, allowing both 2D and 1D arrays of the form (n, p) and (n, )

vec = np.random.uniform(size=(2, ))
vec.shape, A.shape
((2,), (2, 3))
np.column_stack([A, vec]).shape
(2, 4)
# same as first reshaping vec to (2, 1)
assert np.allclose(np.column_stack([A, vec]),
                   np.column_stack([A, vec.reshape((2, 1))]))

Concatenate lets us specify an axis along which to stack

A.shape, B.shape
((2, 3), (2, 2))
np.concatenate([A, B], axis=1).shape
(2, 5)
A.shape, C.shape
((2, 3), (3, 3))

Selecting columns or rows based on values/comparison

U = np.arange(24).reshape((4, 6))
U
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
U.sum(axis=1)
array([ 15,  51,  87, 123])
U.sum(axis=1) > 60
array([False, False,  True,  True])
# rows with row-sum at last 60
U[ U.sum(axis=1) > 60, :]
array([[12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])

Columns with a minimum element smaller or equal to 2

U
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
U[:, U.min(axis=0) <= 2]
array([[ 0,  1,  2],
       [ 6,  7,  8],
       [12, 13, 14],
       [18, 19, 20]])

Matrix multiplication, matrix inversion, matrix determinant

M = np.random.uniform(size=(2,2)).round(2) # a two-by-two matrix
M
array([[0.72, 0.21],
       [0.4 , 0.32]])
M.T
array([[0.72, 0.4 ],
       [0.21, 0.32]])
M @ M # matrix product
array([[0.6024, 0.2184],
       [0.416 , 0.1864]])
np.linalg.inv, np.linalg.det, np.linalg.matrix_rank, np.linalg.solve
(<function inv at 0x7f1f9c6d1730>,
 <function det at 0x7f1f9c6d2770>,
 <function matrix_rank at 0x7f1f9c6d2370>,
 <function solve at 0x7f1f9c6879f0>)
np.linalg.inv(M)
array([[ 2.18579235, -1.43442623],
       [-2.73224044,  4.91803279]])
np.linalg.inv(M) @ M
array([[ 1.00000000e+00, -5.55111512e-17],
       [ 2.22044605e-16,  1.00000000e+00]])
np.allclose(np.linalg.inv(M) @ M, np.eye(2))
True

Matrix operations and 3+ dimensions: Stacks of matrix

A stack of three 2x2 matrices

MM = np.random.normal(size=(2, 2)).round(2)
MMM = np.random.normal(size=(2, 2)).round(2)
S = np.stack([M, MM, MM])
S
array([[[ 0.72,  0.21],
        [ 0.4 ,  0.32]],

       [[ 1.62, -0.29],
        [ 0.17,  0.77]],

       [[ 1.62, -0.29],
        [ 0.17,  0.77]]])

Matrix operations apply to a single matrix, or simultaneously to each matrix in the stack

np.linalg.det(M)
0.1464
np.linalg.det(MM)
1.2967000000000002
np.linalg.det(MMM)
-0.24600000000000002
np.linalg.det(S)
array([0.1464, 1.2967, 1.2967])
np.linalg.inv(S)
array([[[ 2.18579235, -1.43442623],
        [-2.73224044,  4.91803279]],

       [[ 0.59381507,  0.22364464],
        [-0.13110203,  1.24932521]],

       [[ 0.59381507,  0.22364464],
        [-0.13110203,  1.24932521]]])

Broadcasting: automatic expansion of arrays that do not have the same size

V = np.arange(12).reshape(3, 4)
u3 = np.arange(3)*0.1   # vector size 3
u4 = np.arange(4)*0.01  # vector size 4
V
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
u3
array([0. , 0.1, 0.2])

matrix product: matrix (3, 4) times vector (4, )

V @ u4
array([0.14, 0.38, 0.62])
V + u3
ValueError: operands could not be broadcast together with shapes (3,4) (3,) 
u3.reshape((3, 1))
array([[0. ],
       [0.1],
       [0.2]])
V
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
V + u3.reshape((3, 1)) # broadcasting u3 horizontally along axis of length 1
array([[ 0. ,  1. ,  2. ,  3. ],
       [ 4.1,  5.1,  6.1,  7.1],
       [ 8.2,  9.2, 10.2, 11.2]])
# Same as copying u3 four times:
np.allclose(
        V + u3.reshape((3, 1)),
        V + np.column_stack([u3, u3, u3, u3]))
True
V * u4.reshape((1, 4)) # broadcasting u4 vertically along axis of length 1
array([[0.  , 0.01, 0.04, 0.09],
       [0.  , 0.05, 0.12, 0.21],
       [0.  , 0.09, 0.2 , 0.33]])
# Same as the sum of three u4 stacked vertically:
np.allclose(
        V * u4.reshape((1, 4)),
        V * np.stack([u4, u4, u4 ]))
True
np.random.normal(size=(4, 4)) * np.random.normal(size=( 4, 1))
array([[-1.05536828e+00, -1.45665816e+00, -2.67210738e-02,
        -1.30359472e+00],
       [-3.18971599e+00, -2.55942816e+00, -3.02727644e+00,
        -9.09620851e-02],
       [ 3.02577245e-03, -6.85844562e-03,  1.84479952e-03,
         5.14571677e-03],
       [ 6.86403995e-01, -3.58362051e-01,  6.80766710e-01,
        -4.92289423e-01]])
np.random.normal(size=(4, 4)) * np.random.normal(size=( 4, 1))
array([[ 0.39426168, -0.31273592, -2.69650199, -1.83475489],
       [ 1.02741182,  0.51285263, -1.34163647, -0.62279637],
       [-0.06590297,  0.23226254, -0.10689879,  0.17579929],
       [-0.43327821,  0.33308693,  0.05473837,  0.4973698 ]])
np.random.normal(size=(2, 3, 4)) * np.random.normal(size=(1, 1, 4))
array([[[ 8.16248889e-02, -1.34795777e-01, -2.57715064e+00,
         -2.23339041e+00],
        [-1.94035393e-01,  3.23548528e-01,  3.22602216e-05,
         -1.27705988e+00],
        [ 2.53878986e-03, -5.39723529e-02,  1.24454397e+00,
         -1.26687821e-01]],

       [[ 5.43656925e-02,  3.77687041e-01,  1.78233734e+00,
         -1.81216835e+00],
        [-1.10908238e-01, -3.24302457e-01,  2.68640291e+00,
         -2.37375553e+00],
        [ 1.66429948e-02, -1.49013948e-02,  3.58979607e-02,
         -9.55966914e-01]]])

Multiply many matrices:

S = np.stack([np.random.randn(2, 2) for a in range(5)]).round(2)
S.shape
S
array([[[ 1.45, -1.05],
        [-1.15, -0.77]],

       [[-0.08, -0.49],
        [-1.78, -0.92]],

       [[-0.87,  0.21],
        [-2.  ,  0.56]],

       [[-2.09,  0.6 ],
        [ 1.03,  2.39]],

       [[-0.39,  0.29],
        [-0.25, -0.74]]])
S
array([[[ 1.45, -1.05],
        [-1.15, -0.77]],

       [[-0.08, -0.49],
        [-1.78, -0.92]],

       [[-0.87,  0.21],
        [-2.  ,  0.56]],

       [[-2.09,  0.6 ],
        [ 1.03,  2.39]],

       [[-0.39,  0.29],
        [-0.25, -0.74]]])

Computing inverses of the five matrices one by one

S[0]
array([[ 1.45, -1.05],
       [-1.15, -0.77]])
S[0, :, :]
array([[ 1.45, -1.05],
       [-1.15, -0.77]])
np.linalg.inv(S[0])
array([[ 0.3313253 , -0.45180723],
       [-0.49483649, -0.62392427]])

With many matrices

S = np.stack([np.random.randn(2, 2) for a in range(100000)])
S.shape
(100000, 2, 2)
from timeit import default_timer as timer
# fast
start = timer()
np.linalg.inv(S)
end = timer()
print(end - start)
0.01585862999490928
# slow
start = timer()
[np.linalg.inv(mat) for mat in S]
end = timer()
print(end - start)
0.39238803800253663