import numpy as np
print("numpy: ", np.__version__)numpy: 2.3.1
\(~\)
\(~\)
import numpy as np
print("numpy: ", np.__version__)numpy: 2.3.1
This chapter uses NumPy 2.x. \(~\)Two differences from older (1.x) material are worth knowing:
Scalars taken out of an array are shown with their type, \(~\)e.g. np.int64(3) or np.float64(0.5) instead of 3 or 0.5. \(~\)They behave exactly like the plain numbers
Random numbers are generated with a Generator object created by np.random.default_rng(), \(~\)which replaces the legacy functions such as np.random.rand and np.random.randn. \(~\)Passing a seed makes the results reproducible
rng = np.random.default_rng(seed=2025)show_array()show_array_aggregation()data = np.array([[1, 2], [3, 4], [5, 6]])
dataarray([[1, 2],
[3, 4],
[5, 6]])
type(data)numpy.ndarray
data.ndim2
data.shape(3, 2)
data.size6
data.dtypedtype('int64')
data.nbytes48
d0 = np.array([1, 2, 3], dtype=int)
d0array([1, 2, 3])
d1 = np.array([1, 2, 3], dtype=float)
d1array([1., 2., 3.])
d2 = np.array([1, 2, 3], dtype=complex)
d2array([1.+0.j, 2.+0.j, 3.+0.j])
data = np.array([1, 2, 3], dtype=float)
dataarray([1., 2., 3.])
data = np.array(data, dtype=int)
dataarray([1, 2, 3])
data = np.array([1.6, 2, 3], dtype=float)
data.astype(int)array([1, 2, 3])
d1 = np.array([1, 2, 3], dtype=float)
d2 = np.array([1, 2, 3], dtype=complex)d1 + d2array([2.+0.j, 4.+0.j, 6.+0.j])
(d1 + d2).dtypedtype('complex128')
float) array, NumPy returns nan (and issues a RuntimeWarning: invalid value encountered in sqrt), \(~\)whereas for a complex array it returns the complex rootnp.sqrt(np.array([-1, 0, 1]))array([nan, 0., 1.])
np.sqrt(np.array([-1, 0, 1], dtype=complex))array([0.+1.j, 0.+0.j, 1.+0.j])
data = np.array([1, 2, 3], dtype=complex)
dataarray([1.+0.j, 2.+0.j, 3.+0.j])
data.realarray([1., 2., 3.])
data.imagarray([0., 0., 0.])
np.real(data)array([1., 2., 3.])
np.imag(data)array([0., 0., 0.])
Multidimensional arrays are stored as contiguous data in memory. \(~\)Consider the case of a two-dimensional array, \(~\)containing rows and columns: \(~\)One possible way to store this array as a consecutive sequence of values is to store the rows after each other, and another equally valid approach is to store the columns one after another
The former is called row-major format and the latter is column-major format. Whether to use row-major or column-major is a matter of conventions, and the row-major format is used for example in the C programming language, and Fortran uses the column-major format
A numpy array can be specified to be stored in row-major format, using the keyword argument order='C', and column-major format, using the keyword argument order='F', when the array is created or reshaped. The default format is row-major
In general, the numpy array attribute ndarray.strides defines exactly how this mapping is done. The strides attribute is a tuple of the same length as the number of axes (dimensions) of the array. Each value in strides is the factor by which the index for the corresponding axis is multiplied when calculating the memory offset (in bytes) for a given index expression
data = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
dataarray([[1, 2, 3],
[4, 5, 6]], dtype=int32)
data.strides(12, 4)
data = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32, order='F')
dataarray([[1, 2, 3],
[4, 5, 6]], dtype=int32)
data.strides(4, 8)
data = np.array([1, 2, 3, 4])
data.ndim, data.shape(1, (4,))
data = np.array(((1, 2), (3, 4)))
data.ndim, data.shape(2, (2, 2))
np.zeros((2, 3))array([[0., 0., 0.],
[0., 0., 0.]])
data = np.ones(4)
data, data.dtype(array([1., 1., 1., 1.]), dtype('float64'))
5.4 * np.ones(10)array([5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4])
np.full(10, 5.4) # slightly more efficientarray([5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4])
x1 = np.empty(5)
x1.fill(3.0)
x1array([3., 3., 3., 3., 3.])
np.arange(0, 11, 1)array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
np.linspace(0, 10, 11) # generally recommendedarray([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
np.logspace(0, 2, 10) # 10 data points between 10**0=1 and 10**2=100array([ 1. , 1.66810054, 2.7825594 , 4.64158883,
7.74263683, 12.91549665, 21.5443469 , 35.93813664,
59.94842503, 100. ])
x = np.array([-1, 0, 1])
y = np.array([-2, 0, 2])
X, Y = np.meshgrid(x, y)Xarray([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]])
Yarray([[-2, -2, -2],
[ 0, 0, 0],
[ 2, 2, 2]])
Z = (X + Y)**2
Zarray([[9, 4, 1],
[1, 0, 1],
[1, 4, 9]])
np.empty(3, dtype=float)array([0., 0., 0.])
def f(x):
y = np.ones_like(x) # compute with x and y
return y
x = np.array([[1, 2, 3], [4, 5, 6]])
y = f(x)
yarray([[1, 1, 1],
[1, 1, 1]])
np.identity(4)array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
np.eye(4, k=1)array([[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
[0., 0., 0., 0.]])
np.eye(4, k=-1)array([[0., 0., 0., 0.],
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])
np.diag(np.arange(0, 20, 5))array([[ 0, 0, 0, 0],
[ 0, 5, 0, 0],
[ 0, 0, 10, 0],
[ 0, 0, 0, 15]])
a = np.arange(0, 11)
aarray([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
a[0]np.int64(0)
a[-1]np.int64(10)
a[4]np.int64(4)
a[1:-1]array([1, 2, 3, 4, 5, 6, 7, 8, 9])
a[1:-1:2]array([1, 3, 5, 7, 9])
a[:5]array([0, 1, 2, 3, 4])
a[-5:]array([ 6, 7, 8, 9, 10])
a[::-2]array([10, 8, 6, 4, 2, 0])
f = lambda m, n: n + 10*m# please search for numpy.fromfunction at google
A = np.fromfunction(f, (6, 6), dtype=int)
A array([[ 0, 1, 2, 3, 4, 5],
[10, 11, 12, 13, 14, 15],
[20, 21, 22, 23, 24, 25],
[30, 31, 32, 33, 34, 35],
[40, 41, 42, 43, 44, 45],
[50, 51, 52, 53, 54, 55]])
A[:, 1] # the second columnarray([ 1, 11, 21, 31, 41, 51])
A[1, :] # the second rowarray([10, 11, 12, 13, 14, 15])
A[:3, :3]array([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22]])
A[3:, :3]array([[30, 31, 32],
[40, 41, 42],
[50, 51, 52]])
A[::2, ::2]array([[ 0, 2, 4],
[20, 22, 24],
[40, 42, 44]])
A[1::2, 1::3]array([[11, 14],
[31, 34],
[51, 54]])
Subarrays that are extracted from arrays using slice operations are alternative views of the same underlying array data. That is, \(~\)they are arrays that refer to the same data in memory as the original array, \(~\)but with a different strides configuration
When elements in a view are assigned new values, \(~\)the values of the original array are therefore also updated. For example,
B = A[1:5, 1:5]
Barray([[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34],
[41, 42, 43, 44]])
B[:, :] = 0
Aarray([[ 0, 1, 2, 3, 4, 5],
[10, 0, 0, 0, 0, 15],
[20, 0, 0, 0, 0, 25],
[30, 0, 0, 0, 0, 35],
[40, 0, 0, 0, 0, 45],
[50, 51, 52, 53, 54, 55]])
copy method of the ndarray instanceC = B[1:3, 1:3].copy()
Carray([[0, 0],
[0, 0]])
C[:, :] = 1
Carray([[1, 1],
[1, 1]])
Barray([[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
A = np.linspace(0, 1, 11)
Aarray([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
A[np.array([0, 2, 4])]array([0. , 0.2, 0.4])
A[[0, 2, 4]]array([0. , 0.2, 0.4])
A > 0.5array([False, False, False, False, False, False, True, True, True,
True, True])
A[A > 0.5]array([0.6, 0.7, 0.8, 0.9, 1. ])
A = np.arange(10)
indices = [2, 4, 6]B = A[indices]B[0] = -1
Aarray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
A[indices] = -1
Aarray([ 0, 1, -1, 3, -1, 5, -1, 7, 8, 9])
A = np.arange(10)B = A[A > 5]B[0] = -1
Aarray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
A[A > 5] = -1
Aarray([ 0, 1, 2, 3, 4, 5, -1, -1, -1, -1])
show_array((4, 4), ':, :')
show_array((4, 4), '0')
show_array((4, 4), '1, :')
show_array((4, 4), ':, 2')
show_array((4, 4), '0:2, 0:2')
show_array((4, 4), '0:2, 2:4')
show_array((4, 4), '::2, ::2')
show_array((4, 4), '1::2, 1::2')
show_array((4, 4), ':, [0, 3]')
show_array((4, 4), '[1, 3], [0, 3]')
show_array((4, 4), ':, [False, True, True, False]')
show_array((4, 4), '1:3, [False, True, True, False]')
data = np.array([[1, 2], [3, 4]])
data1 = np.reshape(data, (1, 4))
data1array([[1, 2, 3, 4]])
data1[0, 1] = -1
dataarray([[ 1, -1],
[ 3, 4]])
data2 = data.reshape(4)
data2array([ 1, -1, 3, 4])
data2[1] = -2
dataarray([[ 1, -2],
[ 3, 4]])
data = np.array([[1, 2], [3, 4]])
data1 = np.ravel(data)
data1array([1, 2, 3, 4])
data1[0] = -1
dataarray([[-1, 2],
[ 3, 4]])
ndarray method flatten performs the same function, \(~\)but returns a copy instead of a viewdata2 = data.flatten()
data2array([-1, 2, 3, 4])
data2[0] = -2
dataarray([[-1, 2],
[ 3, 4]])
data = np.arange(0, 5)
data.shape(5,)
column = data[:, np.newaxis]
columnarray([[0],
[1],
[2],
[3],
[4]])
column.shape(5, 1)
row = data[np.newaxis, :]
rowarray([[0, 1, 2, 3, 4]])
row.shape(1, 5)
row[0, 0] = -1
dataarray([-1, 1, 2, 3, 4])
np.expand_dims(data, axis=1) array([[-1],
[ 1],
[ 2],
[ 3],
[ 4]])
row = np.expand_dims(data, axis=0)
rowarray([[-1, 1, 2, 3, 4]])
row[0, 0] = 0
dataarray([0, 1, 2, 3, 4])
data = np.arange(5)
dataarray([0, 1, 2, 3, 4])
np.vstack((data, data, data))array([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])
np.hstack((data, data, data))array([0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4])
data = data[:, np.newaxis]
data.shape(5, 1)
np.hstack((data, data, data))array([[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4]])
data1 = np.array([[1, 2], [3, 4]])
data2 = np.array([[5, 6]])np.concatenate((data1, data2), axis=0)array([[1, 2],
[3, 4],
[5, 6]])
np.concatenate((data1, data2.T), axis=1)array([[1, 2, 5],
[3, 4, 6]])
x = np.array([[1, 2], [3, 4]])
y = np.array([[5, 6], [7, 8]])x + yarray([[ 6, 8],
[10, 12]])
y - xarray([[4, 4],
[4, 4]])
x * yarray([[ 5, 12],
[21, 32]])
y / xarray([[5. , 3. ],
[2.33333333, 2. ]])
x * 2array([[2, 4],
[6, 8]])
2**xarray([[ 2, 4],
[ 8, 16]])
y / 2array([[2.5, 3. ],
[3.5, 4. ]])
(y / 2).dtypedtype('float64')
a = np.array([[11, 12, 13], [21, 22, 23], [31, 32, 33]])
b = np.array([[1, 2, 3]])a + barray([[12, 14, 16],
[22, 24, 26],
[32, 34, 36]])
show_array_broadcasting(a, b)
a + b.Tarray([[12, 13, 14],
[23, 24, 25],
[34, 35, 36]])
show_array_broadcasting(a, b.T)
x = np.array([1, 2, 3, 4]).reshape(2, 2)
x.shape(2, 2)
z = np.array([[2, 4]])
z.shape(1, 2)
x / zarray([[0.5, 0.5],
[1.5, 1. ]])
zz = np.vstack((z, z))
zzarray([[2, 4],
[2, 4]])
x / zzarray([[0.5, 0.5],
[1.5, 1. ]])
z = np.array([[2], [4]])
z.shape(2, 1)
x / zarray([[0.5 , 1. ],
[0.75, 1. ]])
zz = np.concatenate([z, z], axis=1)
zzarray([[2, 2],
[4, 4]])
x / zzarray([[0.5 , 1. ],
[0.75, 1. ]])
x = z = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
x = x + y # x is reassigned to a new arrayx, z(array([ 6, 8, 10, 12]), array([1, 2, 3, 4]))
x = z = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
x += y # the values of array x are updated in placex, z(array([ 6, 8, 10, 12]), array([ 6, 8, 10, 12]))
x = np.linspace(-1, 1, 11)
xarray([-1. , -0.8, -0.6, -0.4, -0.2, 0. , 0.2, 0.4, 0.6, 0.8, 1. ])
y = np.sin(np.pi * x)np.round(y, decimals=4)array([-0. , -0.5878, -0.9511, -0.9511, -0.5878, 0. , 0.5878,
0.9511, 0.9511, 0.5878, 0. ])
np.add(np.sin(x)**2, np.cos(x)**2)array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
np.sin(x)**2 + np.cos(x)**2array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
def heaviside(x):
return 1 if x > 0 else 0heaviside(-1)0
heaviside(1.5)1
if statement cannot decide on a single truth value and raises an error:x = np.linspace(-5, 5, 11)
heaviside(x)--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[158], line 2 1 x = np.linspace(-5, 5, 11) ----> 2 heaviside(x) Cell In[155], line 2, in heaviside(x) 1 def heaviside(x): ----> 2 return 1 if x > 0 else 0 ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
heaviside = np.vectorize(heaviside)
heaviside(x)array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
def heaviside(x): # much better way
return 1 * (x > 0)
heaviside(x)array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
data = rng.normal(size=(15, 15))np.mean(data)np.float64(-0.052411960291318914)
data.mean()np.float64(-0.052411960291318914)
data = rng.normal(size=(5, 10, 15))data.sum(axis=0).shape(10, 15)
data.sum(axis=(0, 2)).shape(10,)
data.sum()np.float64(10.439944354499389)
data = np.arange(9).reshape(3, 3)
dataarray([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
data.sum()np.int64(36)
show_array_aggregation(data, None)
data.sum(axis=0)array([ 9, 12, 15])
show_array_aggregation(data, 0)
data.sum(axis=1)array([ 3, 12, 21])
show_array_aggregation(data, 1)
a = np.array([1, 2, 3, 4])
b = np.array([4, 3, 2, 1])a < barray([ True, True, False, False])
np.all(a < b)np.False_
np.any(a < b)np.True_
x = np.array([-2, -1, 0, 1, 2])x > 0array([False, False, False, True, True])
1 * (x > 0)array([0, 0, 0, 1, 1])
x * (x > 0)array([0, 0, 0, 1, 2])
def pulse(x, position, height, width):
return height * (x >= position) * (x <= (position + width)) x = np.linspace(-5, 5, 31)pulse(x, position=-2, height=1, width=5)array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0])
pulse(x, position=1, height=2, width=2)array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2,
2, 2, 2, 0, 0, 0, 0, 0, 0])
x = np.linspace(-4, 4, 9)
xarray([-4., -3., -2., -1., 0., 1., 2., 3., 4.])
np.where(x < 0, x**2, x**3)array([16., 9., 4., 1., 0., 1., 8., 27., 64.])
np.select([x < -1, x < 2, x>= 2], [x**2, x**3, x**4])array([ 16., 9., 4., -1., 0., 1., 16., 81., 256.])
np.choose([0, 0, 0, 1, 1, 1, 2, 2, 2], [x**2, x**3, x**4])array([ 16., 9., 4., -1., 0., 1., 16., 81., 256.])
x[np.abs(x) > 2]array([-4., -3., 3., 4.])
a = np.unique([1, 2, 3, 3])
aarray([1, 2, 3])
b = np.unique([2, 3, 4, 4, 5, 6, 5])
barray([2, 3, 4, 5, 6])
np.isin(a, b)array([False, True, True])
1 in a, 1 in b(True, False)
np.all(np.isin(a, b)) # to test if a is a subset of bnp.False_
np.union1d(a, b)array([1, 2, 3, 4, 5, 6])
np.intersect1d(a, b)array([2, 3])
np.setdiff1d(a, b)array([1])
np.setdiff1d(b, a)array([4, 5, 6])
data = np.arange(9).reshape(3, 3)
dataarray([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
np.transpose(data)array([[0, 3, 6],
[1, 4, 7],
[2, 5, 8]])
data = rng.standard_normal((1, 2, 3, 4, 5))data.shape(1, 2, 3, 4, 5)
data.T.shape(5, 4, 3, 2, 1)
A = np.arange(1, 7).reshape(2, 3)
Aarray([[1, 2, 3],
[4, 5, 6]])
B = np.arange(1, 7).reshape(3, 2)
Barray([[1, 2],
[3, 4],
[5, 6]])
np.dot(A, B)array([[22, 28],
[49, 64]])
np.dot(B, A)array([[ 9, 12, 15],
[19, 26, 33],
[29, 40, 51]])
A @ B # matrix multiplication operator (Python 3.5+)array([[22, 28],
[49, 64]])
B @ Aarray([[ 9, 12, 15],
[19, 26, 33],
[29, 40, 51]])
A = np.arange(9).reshape(3, 3)
Aarray([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
x = np.arange(3)
xarray([0, 1, 2])
np.dot(A, x)array([ 5, 14, 23])
A.dot(x)array([ 5, 14, 23])
A @ xarray([ 5, 14, 23])
A = rng.random((3, 3))
B = rng.random((3, 3))Ap = np.dot(B, np.dot(A, np.linalg.inv(B)))Ap = B.dot(A.dot(np.linalg.inv(B)))B @ A @ np.linalg.inv(B)array([[ 2.64337421, -8.88362722, 6.27413065],
[ 1.48592164, -4.77142485, 3.31850032],
[ 1.68086145, -5.09870616, 3.62734534]])
np.inner(x, x)np.int64(5)
np.dot(x, x)np.int64(5)
y = x[:, np.newaxis]
yarray([[0],
[1],
[2]])
np.dot(y.T, y)array([[5]])
\[\begin{pmatrix} a_0 b_0 & a_0b_1 & \cdots & a_0 b_N \\ a_1 b_0 & \cdots & \cdots & a_1 b_N \\ \vdots & \ddots & & \vdots \\ a_M b_0 & & \ddots & a_M b_N \\ \end{pmatrix} \]
x = np.array([1, 2, 3])np.outer(x, x)array([[1, 2, 3],
[2, 4, 6],
[3, 6, 9]])
np.kron(x, x)array([1, 2, 3, 2, 4, 6, 3, 6, 9])
np.kron(x[:, np.newaxis], x[np.newaxis, :])array([[1, 2, 3],
[2, 4, 6],
[3, 6, 9]])
np.kron(np.ones((2, 2)), np.identity(2))array([[1., 0., 1., 0.],
[0., 1., 0., 1.],
[1., 0., 1., 0.],
[0., 1., 0., 1.]])
np.kron(np.identity(2), np.ones((2, 2)))array([[1., 1., 0., 0.],
[1., 1., 0., 0.],
[0., 0., 1., 1.],
[0., 0., 1., 1.]])
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])np.einsum("n,n", x, y)np.int64(70)
np.inner(x, y)np.int64(70)
A = np.arange(9).reshape(3, 3)
B = A.Tnp.einsum("mk,kn", A, B)array([[ 5, 14, 23],
[ 14, 50, 86],
[ 23, 86, 149]])
np.all(np.einsum("mk,kn", A, B) == np.dot(A, B))np.True_