A document from MCS 275 Spring 2022, instructor David Dumas. You can also get the notebook file.

# Quick intro to numpy¶

### MCS 275 Spring 2022 - David Dumas¶

This is a quick tour of some numpy features. For more detail see:

## Import the module¶

None of the code below will work without this!

In [1]:
import numpy as np
np.__version__

Out[1]:
'1.22.2'

## Making and using arrays¶

In [74]:
# all zeros, specified shape
np.zeros( (2,3) )

Out[74]:
array([[0., 0., 0.],
[0., 0., 0.]])
In [75]:
# all ones, specified shape
np.ones( (8,) )

Out[75]:
array([1., 1., 1., 1., 1., 1., 1., 1.])
In [ ]:
# array with every entry equal to a given constant
np.full( (3,3), 1.7 )  # shape, value

In [ ]:
A = np.ones( (4,4) )
A.ndim  # the dimension of A

In [ ]:
A.shape # the shape of A

In [ ]:
A.size # the number of elements in A

In [ ]:
A.dtype # the data type (np.ones gives float64 by default)

In [ ]:
B = np.full( (2,3), 6 )  # Python int given, converted to int64
B.dtype

In [ ]:
# Build array from an iterable
C = np.array( [[5,6,7,8],[9,10,11,12]] )

In [ ]:
print(C.ndim)
print(C.dtype)
print(C.size)

In [ ]:
# np.random.random(shape) gives array of uniformly distributed
# random floats 0<=x<1
np.random.random((5,8))

In [ ]:
np.ones((2,2),dtype="bool")  # numpy supports boolean arrays
# coerces 0 to False and 1 to True

In [76]:
Abyte = np.zeros((4,3),dtype="uint8")  # Array of 1-byte values
Abyte

Out[76]:
array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]], dtype=uint8)
In [77]:
Abyte[1,1] = 10
Abyte[3,2] = 300
Abyte

Out[77]:
array([[ 0,  0,  0],
[ 0, 10,  0],
[ 0,  0,  0],
[ 0,  0, 44]], dtype=uint8)

### Arithmetic progressions¶

• np.arange is start, stop, step
• np.linspace is first, last, nstep
In [39]:
np.arange(3,13,2) # stop will not be included

Out[39]:
array([ 3,  5,  7,  9, 11])
In [40]:
np.arange(3,5,0.1)

Out[40]:
array([3. , 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4. , 4.1, 4.2,
4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9])
In [43]:
np.linspace(2,6,4)

Out[43]:
array([2.        , 3.33333333, 4.66666667, 6.        ])

## Accessing elements (indexing and slices)¶

In [83]:
A = np.array(range(24)).reshape((4,6))   # 0..23 in a vector, but then convert to 4x6 matrix
v = np.arange(1,5,1.2) # vector (array with ndim=1)

In [84]:
A

Out[84]:
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]])
In [ ]:
v

In [ ]:
v[1] # element at index 1 (zero-based)

In [ ]:
v[-2] # second to last element

In [ ]:
A[2,0]  # row 2, column 0

In [85]:
A[:,2]  # column 2 of A  (remember, 0-based numbering!)
# I think of this as A[anything,2]

Out[85]:
array([ 2,  8, 14, 20])
In [86]:
A[1,:]  # row 1 of A
# I think of this as A[1,anything]

Out[86]:
array([ 6,  7,  8,  9, 10, 11])
In [87]:
A[1] # another way to specify row 1 of A
# missing indices are treated as ":"

Out[87]:
array([ 6,  7,  8,  9, 10, 11])
In [ ]:
A[::2,1:3]  # all rows of even index, columns 1 and 2


## Vector math¶

Vectors are 1D arrays

In [114]:
v = np.array([1.5,2.5,1])
v

Out[114]:
array([1.5, 2.5, 1. ])
In [115]:
w = np.array([-0.5,3,0])
w

Out[115]:
array([-0.5,  3. ,  0. ])
In [117]:
2.1*v # elementwise multiplication

Out[117]:
array([3.15, 5.25, 2.1 ])
In [118]:
v+w

Out[118]:
array([1. , 5.5, 1. ])
In [119]:
v*w   # There's a good chance this isn't what you want.

Out[119]:
array([-0.75,  7.5 ,  0.  ])
In [120]:
v.dot(w)

Out[120]:
6.75

## Matrices¶

Matrices are 2D arrays

In [121]:
M = np.eye(3)
M

Out[121]:
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
In [122]:
M[1] = np.linspace(10,20,3)
M

Out[122]:
array([[ 1.,  0.,  0.],
[10., 15., 20.],
[ 0.,  0.,  1.]])
In [123]:
6*M

Out[123]:
array([[  6.,   0.,   0.],
[ 60.,  90., 120.],
[  0.,   0.,   6.]])
In [124]:
M.T  # transpose reverses the order of the axes; M.T[j,k] is M[k,j]

Out[124]:
array([[ 1., 10.,  0.],
[ 0., 15.,  0.],
[ 0., 20.,  1.]])

## Ufuncs¶

In [125]:
# Let's make an array to work with
A = np.array(range(1,16)).reshape((3,5))
A

Out[125]:
array([[ 1,  2,  3,  4,  5],
[ 6,  7,  8,  9, 10],
[11, 12, 13, 14, 15]])
In [126]:
1 / A  # reciprocal of each entry in the matrix

Out[126]:
array([[1.        , 0.5       , 0.33333333, 0.25      , 0.2       ],
[0.16666667, 0.14285714, 0.125     , 0.11111111, 0.1       ],
[0.09090909, 0.08333333, 0.07692308, 0.07142857, 0.06666667]])
In [127]:
A**2 # square of each entry

Out[127]:
array([[  1,   4,   9,  16,  25],
[ 36,  49,  64,  81, 100],
[121, 144, 169, 196, 225]])
In [128]:
np.sin(A) # apply sin() to each entry

Out[128]:
array([[ 0.84147098,  0.90929743,  0.14112001, -0.7568025 , -0.95892427],
[-0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849, -0.54402111],
[-0.99999021, -0.53657292,  0.42016704,  0.99060736,  0.65028784]])
In [129]:
# first 11 cubes
np.arange(1,12)**3  # make vector of 1..11 and then cube each entry

Out[129]:
array([   1,    8,   27,   64,  125,  216,  343,  512,  729, 1000, 1331])

If a higher-dimensional array is needed for an operation, produce one by duplication.

In [143]:
A = np.full((3,4),5,dtype="float")
A[1] = 11
A

Out[143]:
array([[ 5.,  5.,  5.,  5.],
[11., 11., 11., 11.],
[ 5.,  5.,  5.,  5.]])
In [144]:
5+A

Out[144]:
array([[10., 10., 10., 10.],
[16., 16., 16., 16.],
[10., 10., 10., 10.]])
In [141]:
v = np.array( [1,2,3,4], dtype="float")
v

Out[141]:
array([1., 2., 3., 4.])
In [145]:
    A   +   v    # add v to each row of A
# 3x4   (3x)4

Out[145]:
array([[ 6.,  7.,  8.,  9.],
[12., 13., 14., 15.],
[ 6.,  7.,  8.,  9.]])
In [147]:
B = np.zeros( (6,6) )
B[::2,1:5] = 7

In [148]:
B

Out[148]:
array([[0., 7., 7., 7., 7., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 7., 7., 7., 7., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 7., 7., 7., 7., 0.],
[0., 0., 0., 0., 0., 0.]])

## Stacking and joining¶

In [149]:
A = np.arange(1,10).reshape((3,3))
A

Out[149]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [150]:
B = np.array( [ [10,20,30], [19,23,56]])
B

Out[150]:
array([[10, 20, 30],
[19, 23, 56]])
In [151]:
C = np.array( [ [2,1], [1,1] ])
C

Out[151]:
array([[2, 1],
[1, 1]])
In [152]:
np.vstack([A,A])

Out[152]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [153]:
np.hstack([A,A])

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

## Aggregation functions¶

In [155]:
v = np.arange(15)
v

Out[155]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
In [156]:
np.sum(v)

Out[156]:
105
In [157]:
np.max(v)

Out[157]:
14
In [158]:
np.mean(v)

Out[158]:
7.0
In [160]:
np.all(v)  # are all of the elements True / nonzero?

Out[160]:
False
In [163]:
np.any(v)  # is there at least one True / nonzero element

Out[163]:
True

## Grids¶

Suppose we want to consider a rectangular grid of points in the plane. Numpy has a function to take a list of x values, a list of y values, and then return all possible pairs of x and y from these lists in a convenient form.

In [171]:
x = np.linspace(-1,1,11)
x

Out[171]:
array([-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ])
In [172]:
y = np.linspace(0,2,6)
y

Out[172]:
array([0. , 0.4, 0.8, 1.2, 1.6, 2. ])
In [174]:
xx,yy = np.meshgrid(x,y)  # will return two len(y) x len(x) arrays
# xx will be constant along columns (values from x along rows)
# yy will be constant along rows (values from y along columns)

In [175]:
xx

Out[175]:
array([[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ],
[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ],
[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ],
[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ],
[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ],
[-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ]])
In [176]:
yy

Out[176]:
array([[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4],
[0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8],
[1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2],
[1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6],
[2. , 2. , 2. , 2. , 2. , 2. , 2. , 2. , 2. , 2. , 2. ]])

In [189]:
# A is the sum of two matrices:
# 4x4 zeros, add [0,1,2,3] to each row, take the transpose
# 4x4 zeros, add [0,2,4,6] to each row
A = (np.zeros((4,4))+np.arange(4)).T + (np.zeros((4,4))+np.arange(0,8,2))

# -3 to 3
v = np.arange(-3,4)

w = np.array([-2,-2,0,0,4,5,5])

In [190]:
A

Out[190]:
array([[0., 2., 4., 6.],
[1., 3., 5., 7.],
[2., 4., 6., 8.],
[3., 5., 7., 9.]])
In [193]:
v

Out[193]:
array([-3, -2, -1,  0,  1,  2,  3])
In [194]:
w

Out[194]:
array([-2, -2,  0,  0,  4,  5,  5])
In [195]:
v==w

Out[195]:
array([False,  True, False,  True, False, False, False])
In [196]:
v>w

Out[196]:
array([False, False, False, False, False, False, False])
In [197]:
v<w

Out[197]:
array([ True, False,  True, False,  True,  True,  True])
In [198]:
A == np.zeros((4,4))

Out[198]:
array([[ True, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False]])
In [199]:
A

Out[199]:
array([[0., 2., 4., 6.],
[1., 3., 5., 7.],
[2., 4., 6., 8.],
[3., 5., 7., 9.]])
In [200]:
mask = np.array( [True, True, False, False, False, True, False])

In [201]:
mask.dtype

Out[201]:
dtype('bool')
In [202]:
v

Out[202]:
array([-3, -2, -1,  0,  1,  2,  3])
In [203]:
v[mask] # 1D array of all the entries in v where mask is True

Out[203]:
array([-3, -2,  2])
In [206]:
mask2d = np.zeros((4,4),dtype="bool")

Out[206]:
array([[ True, False, False, False],
[False, False, False, False],
[False, False, False,  True],
[False, False, False, False]])
In [207]:
A[mask2d]

Out[207]:
array([0., 8.])
In [208]:
v

Out[208]:
array([-3, -2, -1,  0,  1,  2,  3])
In [209]:
mask

Out[209]:
array([ True,  True, False, False, False,  True, False])
In [210]:
v[mask] = 275

In [211]:
v

Out[211]:
array([275, 275,  -1,   0,   1, 275,   3])
In [212]:
# -3 to 3
v = np.arange(-3,4)

In [213]:
v

Out[213]:
array([-3, -2, -1,  0,  1,  2,  3])
In [214]:
v[mask]+=1

In [215]:
v

Out[215]:
array([-2, -1, -1,  0,  1,  3,  3])
In [216]:
v[v<0] = 0

In [217]:
v

Out[217]:
array([0, 0, 0, 0, 1, 3, 3])
In [218]:
A

Out[218]:
array([[0., 2., 4., 6.],
[1., 3., 5., 7.],
[2., 4., 6., 8.],
[3., 5., 7., 9.]])
In [219]:
A[A%2==0] = 42
A

Out[219]:
array([[42., 42., 42., 42.],
[ 1.,  3.,  5.,  7.],
[42., 42., 42., 42.],
[ 3.,  5.,  7.,  9.]])
In [220]:
np.any(A<0)  # Does A have any entries that are negative?

Out[220]:
False

## Pillow integration¶

• np.array(img) just works, if img is a PIL.Image object
• Use PIL.Image.fromarray(A) to make an image from an array
• Shape (height,width) and dtype uint8 for grayscale
• Shaape (height,width,3) and dtype uint8 for color (last axis is red, green, blue)
In [14]:
import numpy as np
from PIL import Image

grid_of_zeros = np.zeros( (256,256) )

# Make the red data: more red as you go to the right
r = np.arange(256) + grid_of_zeros  # broadcasting makes this 256x256

# Make the green data: more green as you go to the left
g = 255 - np.arange(256) + grid_of_zeros # broadcasting makes this 256x256

# Make the blue data: More blue at the top
b = (255 - np.arange(256) + grid_of_zeros).T

# Stack these three "planes" into an array of size 256x256x3
# Note the axis=2 is needed so they are stacked with the size-3 dimension
# being the last one.  If we omit this you get the same data but as 256x256x3
imgdata = np.stack([r,g,b],axis=2).astype("uint8")

# Make an image out of it
Image.fromarray(imgdata)

Out[14]: