55-102 Numpy
55-102 Numpy
Authors: Emmanuelle Gouillart, Didrik Pinte, Gaël Varoquaux, and Pauli Virtanen
This chapter gives an overview of NumPy, the core tool for performant numerical computing with Python.
Section contents
50
Scipy lecture notes, Edition 2020.2
In [3]: a = np.arange(1000)
In [6]: np.con*?
np.concatenate
np.conj
np.conjugate
np.convolve
Import conventions
The recommended convention to import numpy is:
• 2-D, 3-D, . . . :
[[3],
[4]]])
>>> c.shape
(2, 2, 1)
• Create a simple two dimensional array. First, redo the examples from above. And then create
your own: how about odd numbers counting backwards on the first row, and even numbers on
the second?
• Use the functions len(), numpy.shape() on these arrays. How do they relate to each other?
And to the ndim attribute of the arrays?
• Evenly spaced:
• or by number of points:
• Common arrays:
Tip: Different data-types allow us to store data more compactly in memory, but most of the time we
simply work with floating point numbers. Note that, in the example above, NumPy auto-detects the
data-type from the input.
Bool
>>> e = np.array([True, False, False, True])
>>> e.dtype
dtype('bool')
Strings
Much more
• int32
• int64
• uint32
• uint64
Or the notebook:
$ jupyter notebook
>>> %matplotlib
The inline is important for the notebook, so that plots are displayed in the notebook and not in a new
window.
Matplotlib is a 2D plotting package. We can import its functions as below:
And then use (note that you have to use show explicitly if you have not enabled interactive plots with
%matplotlib):
• 1D plotting:
See also:
More in the: matplotlib chapter
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[0], a[2], a[-1]
(0, 2, 9)
Warning: Indices begin at 0, like other Python sequences (and C/C++). In contrast, in Fortran
or Matlab, indices begin at 1.
>>> a[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
>>> a = np.diag(np.arange(3))
>>> a
array([[0, 0, 0],
[0, 1, 0],
[0, 0, 2]])
>>> a[1, 1]
1
>>> a[2, 1] = 10 # third line, second column
>>> a
array([[ 0, 0, 0],
[ 0, 1, 0],
[ 0, 10, 2]])
>>> a[1]
array([0, 1, 0])
Note:
• In 2D, the first dimension corresponds to rows, the second to columns.
• for multidimensional a, a[0] is interpreted by taking all elements in the unspecified dimensions.
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[2:9:3] # [start:end:step]
array([2, 5, 8])
>>> a[:4]
array([0, 1, 2, 3])
All three slice components are not required: by default, start is 0, end is the last and step is 1:
>>> a[1:3]
array([1, 2])
>>> a[::2]
array([0, 2, 4, 6, 8])
>>> a[3:]
array([3, 4, 5, 6, 7, 8, 9])
>>> a = np.arange(10)
>>> a[5:] = 10
>>> a
array([ 0, 1, 2, 3, 4, 10, 10, 10, 10, 10])
>>> b = np.arange(5)
>>> a[5:] = b[::-1]
>>> a
array([0, 1, 2, 3, 4, 4, 3, 2, 1, 0])
• Try the different flavours of slicing, using start, end and step: starting from a linspace, try to
obtain odd numbers counting backwards, and even numbers counting forwards.
• Reproduce the slices in the diagram above. You may use the following expression to create the
array:
>>> np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
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]])
Skim through the documentation for np.tile, and use this function to construct the array:
[[4, 3, 4, 3, 4, 3],
[2, 1, 2, 1, 2, 1],
[4, 3, 4, 3, 4, 3],
[2, 1, 2, 1, 2, 1]]
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b = a[::2]
>>> b
array([0, 2, 4, 6, 8])
>>> np.may_share_memory(a, b)
True
>>> b[0] = 12
>>> b
array([12, 2, 4, 6, 8])
>>> a # (!)
array([12, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a = np.arange(10)
>>> c = a[::2].copy() # force a copy
>>> c[0] = 12
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.may_share_memory(a, c)
False
This behavior can be surprising at first sight. . . but it allows to save both memory and time.
• For each integer j starting from 2, cross out its higher multiples:
>>> N_max = int(np.sqrt(len(is_prime) - 1))
>>> for j in range(2, N_max + 1):
... is_prime[2*j::j] = False
Tip: NumPy arrays can be indexed with slices, but also with boolean or integer arrays (masks). This
method is called fancy indexing. It creates copies not views.
>>> np.random.seed(3)
>>> a = np.random.randint(0, 21, 15)
>>> a
array([10, 3, 8, 0, 19, 10, 11, 9, 10, 6, 0, 20, 12, 7, 14])
>>> (a % 3 == 0)
array([False, True, False, True, False, False, False, True, False,
(continues on next page)
Indexing with a mask can be very useful to assign a new value to a sub-array:
>>> a[a % 3 == 0] = -1
>>> a
array([10, -1, 8, -1, 19, 10, 11, -1, 10, -1, -1, 20, -1, 7, 14])
Indexing can be done with an array of integers, where the same index is repeated several time:
Tip: When a new array is created by indexing with an array of integers, the new array has the same
shape as the array of integers:
>>> a = np.arange(10)
>>> idx = np.array([[3, 4], [9, 7]])
>>> idx.shape
(2, 2)
>>> a[idx]
array([[3, 4],
[9, 7]])
Section contents
• Elementwise operations
• Basic reductions
• Broadcasting
• Array shape manipulation
• Sorting data
• Summary
>>> b = np.ones(4) + 1
>>> a - b
array([-1., 0., 1., 2.])
>>> a * b
array([2., 4., 6., 8.])
>>> j = np.arange(5)
>>> 2**(j + 1) - j
array([ 2, 3, 6, 13, 28])
These operations are of course much faster than if you did them in pure python:
>>> a = np.arange(10000)
>>> %timeit a + 1
10000 loops, best of 3: 24.3 us per loop
>>> l = range(10000)
>>> %timeit [i+1 for i in l]
1000 loops, best of 3: 861 us per loop
• Try simple arithmetic elementwise operations: add even elements with odd elements
• Time them against their pure python counterparts using %timeit.
• Generate:
– [2**0, 2**1, 2**2, 2**3, 2**4]
– a_j = 2^(3*j) - j
Other operations
Comparisons:
>>> a = np.array([1, 2, 3, 4])
>>> b = np.array([4, 2, 2, 4])
>>> a == b
array([False, True, False, True])
>>> a > b
array([False, False, True, False])
Logical operations:
>>> a = np.array([1, 1, 0, 0], dtype=bool)
>>> b = np.array([1, 0, 1, 0], dtype=bool)
>>> np.logical_or(a, b)
array([ True, True, True, False])
>>> np.logical_and(a, b)
array([ True, False, False, False])
Transcendental functions:
>>> a = np.arange(5)
>>> np.sin(a)
array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ])
>>> np.log(a)
array([ -inf, 0. , 0.69314718, 1.09861229, 1.38629436])
>>> np.exp(a)
array([ 1. , 2.71828183, 7.3890561 , 20.08553692, 54.59815003])
Shape mismatches
>>> a = np.arange(4)
>>> a + np.array([1, 2])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (4) (2)
>>> a = np.arange(9).reshape(3, 3)
>>> a.T[0, 2] = 999
>>> a.T
array([[ 0, 3, 999],
[ 1, 4, 7],
[ 2, 5, 8]])
>>> a
array([[ 0, 1, 2],
[ 3, 4, 5],
[999, 7, 8]])
Other reductions
— works the same way (and take axis=)
Extrema:
>>> x = np.array([1, 3, 2])
>>> x.min()
1
>>> x.max()
(continues on next page)
Logical operations:
Statistics:
Exercise: Reductions
• Given there is a sum, what other function might you expect to see?
• What is the difference between sum and cumsum?
Tip: Let us consider a simple 1D random walk process: at each time step a walker jumps right or
left with equal probability.
We are interested in finding the typical distance from the origin of a random walker after t left or
right jumps? We are going to simulate many “walkers” to find this law, and we are going to do so
using array computing tricks: we are going to create a 2D array with the “stories” (each walker has a
story) in one direction, and the time in the other:
>>> t = np.arange(t_max)
>>> steps = 2 * np.random.randint(0, 1 + 1, (n_stories, t_max)) - 1 # +1 because the high␣
˓→value is exclusive
4.2.3 Broadcasting
• Basic operations on numpy arrays (addition, etc.) are elementwise
• This works on arrays of the same size.
Nevertheless, It’s also possible to do operations on arrays of different
sizes if NumPy can transform these arrays so that they all have
the same size: this conversion is called broadcasting.
The image below gives an example of broadcasting:
Let’s verify:
A useful trick:
>>> a = np.arange(0, 40, 10)
>>> a.shape
(4,)
>>> a = a[:, np.newaxis] # adds a new axis -> 2D array
>>> a.shape
(4, 1)
>>> a
array([[ 0],
[10],
[20],
[30]])
>>> a + b
array([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]])
Tip: Broadcasting seems a bit magical, but it is actually quite natural to use it when we want to solve
a problem whose output data is an array with more dimensions than input data.
Let’s construct an array of distances (in miles) between cities of Route 66: Chicago, Springfield,
Saint-Louis, Tulsa, Oklahoma City, Amarillo, Santa Fe, Albuquerque, Flagstaff and Los Angeles.
>>> mileposts = np.array([0, 198, 303, 736, 871, 1175, 1475, 1544,
... 1913, 2448])
>>> distance_array = np.abs(mileposts - mileposts[:, np.newaxis])
>>> distance_array
array([[ 0, 198, 303, 736, 871, 1175, 1475, 1544, 1913, 2448],
[ 198, 0, 105, 538, 673, 977, 1277, 1346, 1715, 2250],
[ 303, 105, 0, 433, 568, 872, 1172, 1241, 1610, 2145],
[ 736, 538, 433, 0, 135, 439, 739, 808, 1177, 1712],
[ 871, 673, 568, 135, 0, 304, 604, 673, 1042, 1577],
[1175, 977, 872, 439, 304, 0, 300, 369, 738, 1273],
[1475, 1277, 1172, 739, 604, 300, 0, 69, 438, 973],
[1544, 1346, 1241, 808, 673, 369, 69, 0, 369, 904],
[1913, 1715, 1610, 1177, 1042,
4.2. Numerical operations on arrays 738, 438, 369, 0, 535], 69
[2448, 2250, 2145, 1712, 1577, 1273, 973, 904, 535, 0]])
Scipy lecture notes, Edition 2020.2
A lot of grid-based or network-based problems can also use broadcasting. For instance, if we want to
compute the distance from the origin of points on a 5x5 grid, we can do
>>> x, y = np.arange(5), np.arange(5)[:, np.newaxis]
>>> distance = np.sqrt(x ** 2 + y ** 2)
>>> distance
array([[0. , 1. , 2. , 3. , 4. ],
[1. , 1.41421356, 2.23606798, 3.16227766, 4.12310563],
[2. , 2.23606798, 2.82842712, 3.60555128, 4.47213595],
[3. , 3.16227766, 3.60555128, 4.24264069, 5. ],
[4. , 4.12310563, 4.47213595, 5. , 5.65685425]])
Or in color:
>>> plt.pcolor(distance)
>>> plt.colorbar()
Tip: So, np.ogrid is very useful as soon as we have to handle computations on a grid. On the other
hand, np.mgrid directly provides matrices full of indices for cases where we can’t (or don’t want to)
benefit from broadcasting:
See also:
Broadcasting: discussion of broadcasting in the Advanced NumPy chapter.
Reshaping
The inverse operation to flattening:
>>> a.shape
(2, 3)
>>> b = a.ravel()
>>> b = b.reshape((2, 3))
>>> b
array([[1, 2, 3],
[4, 5, 6]])
Or,
Tip:
>>> b[0, 0] = 99
>>> a
array([[99, 2, 3],
[ 4, 5, 6]])
To understand this you need to learn more about the memory layout of a numpy array.
Adding a dimension
Indexing with the np.newaxis object allows us to add an axis to an array (you have seen this already
above in the broadcasting section):
>>> z[np.newaxis, :]
array([[1, 2, 3]])
Dimension shuffling
>>> a = np.arange(4*3*2).reshape(4, 3, 2)
>>> a.shape
(4, 3, 2)
>>> a[0, 2, 1]
5
>>> b = a.transpose(1, 2, 0)
>>> b.shape
(3, 2, 4)
>>> b[2, 1, 0]
5
>>> b[2, 1, 0] = -1
>>> a[0, 2, 1]
-1
Resizing
Size of an array can be changed with ndarray.resize:
>>> a = np.arange(4)
>>> a.resize((8,))
>>> a
array([0, 1, 2, 3, 0, 0, 0, 0])
>>> b = a
>>> a.resize((4,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: cannot resize an array that has been referenced or is
referencing another array in this way. Use the resize function
• Look at the docstring for reshape, especially the notes section which has some more information
about copies and views.
• Use flatten as an alternative to ravel. What is the difference? (Hint: check which one returns
a view and which a copy)
• Experiment with transpose for dimension shuffling.
In-place sort:
>>> a.sort(axis=1)
>>> a
array([[3, 4, 5],
[1, 1, 2]])
Exercise: Sorting
4.2.6 Summary
What do you need to know to get started?
• Know how to create arrays : array, arange, ones, zeros.
• Know the shape of the array with array.shape, then use slicing to obtain different views of the
array: array[::2], etc. Adjust the shape of the array using reshape or flatten it with ravel.
• Obtain a subset of the elements of an array and/or modify their values with masks
• Know miscellaneous operations on arrays, such as finding the mean or max (array.max(), array.
mean()). No need to retain everything, but have the reflex to search in the documentation (online
docs, help(), lookfor())!!
• For advanced use: master the indexing with arrays of integers, as well as broadcasting. Know more
NumPy functions to handle various array operations.
Quick read
If you want to do a first quick pass through the Scipy lectures to learn the ecosystem, you can directly
skip to the next chapter: Matplotlib: plotting.
The remainder of this chapter is not necessary to follow the rest of the intro part. But be sure to
come back and finish this chapter, as well as to do some more exercices.
Section contents
Forced casts:
Rounding:
int8 8 bits
int16 16 bits
int32 32 bits (same as int on 32-bit platform)
int64 64 bits (same as int on 64-bit platform)
Unsigned integers:
uint8 8 bits
uint16 16 bits
uint32 32 bits
uint64 64 bits
Long integers
Python 2 has a specific type for ‘long’ integers, that cannot overflow, represented with an ‘L’ at the
end. In Python 3, all integers are long, and thus cannot overflow.
>>> np.iinfo(np.int64).max, 2**63 - 1
(9223372036854775807, 9223372036854775807L)
Floating-point numbers:
float16 16 bits
float32 32 bits
float64 64 bits (same as float)
float96 96 bits, platform-dependent (same as np.longdouble)
float128 128 bits, platform-dependent (same as np.longdouble)
>>> np.finfo(np.float32).eps
1.1920929e-07
>>> np.finfo(np.float64).eps
2.2204460492503131e-16
If you don’t know you need special data types, then you probably don’t.
Comparison on using float32 instead of float64:
• Half the size in memory and on disk
• Half the memory bandwidth required (may be a bit faster in some operations)
In [1]: a = np.zeros((int(1e6),), dtype=np.float64)
• But: bigger rounding errors — sometimes in surprising places (i.e., don’t use them unless you
really need them)
>>> samples['sensor_code']
array(['ALFA', 'BETA', 'TAU', 'ALFA', 'ALFA', 'TAU'],
dtype='|S4')
>>> samples['value']
array([0.37, 0.11, 0.13, 0.37, 0.11, 0.13])
>>> samples[0]
('ALFA', 1.0, 0.37)
Note: There are a bunch of other syntaxes for constructing structured arrays, see here and here.
While it is off topic in a chapter on numpy, let’s take a moment to recall good coding practice, which
really do pay off in the long run:
Good practices
• Explicit variable names (no need of a comment to explain what is in the variable)
• Style: spaces after commas, around =, etc.
A certain number of rules for writing “beautiful” code (and, more importantly, using the same
conventions as everybody else!) are given in the Style Guide for Python Code and the Docstring
Conventions page (to manage help strings).
• Except some rare cases, variable names and comments in English.
Section contents
• Polynomials
• Loading data files
4.4.1 Polynomials
NumPy also contains polynomials in different bases:
For example, 3𝑥2 + 2𝑥 − 1:
>>> p = np.poly1d([3, 2, -1])
>>> p(0)
-1
>>> p.roots
array([-1. , 0.33333333])
>>> p.order
2
>>> t = np.linspace(0, 1, 200) # use a larger number of points for smoother plotting
>>> plt.plot(x, y, 'o', t, p(t), '-')
[<matplotlib.lines.Line2D object at ...>, <matplotlib.lines.Line2D object at ...>]
See http://numpy.org/doc/stable/reference/
routines.polynomials.poly1d.html for more.
Example using polynomials in Chebyshev basis, for polynomials in range [-1, 1]:
Note: If you have a complicated text file, what you can try are:
• np.genfromtxt
• Using Python’s I/O functions and e.g. regexps for parsing (Python is quite well suited for this)
Images
Using Matplotlib:
>>> plt.imshow(plt.imread('red_elephant.png'))
<matplotlib.image.AxesImage object at ...>
Other libraries:
Write a Python script that loads data from populations.txt:: and drop the last column and the first
5 rows. Save the smaller dataset to pop2.txt.
NumPy internals
If you are interested in the NumPy internals, there is a good discussion in Advanced NumPy.
[[1, 6, 11],
[2, 7, 12],
[3, 8, 13],
[4, 9, 14],
[5, 10, 15]]
and generate a new array containing its 2nd and 4th rows.
2. Divide each column of the array:
elementwise with the array b = np.array([1., 5, 10, 15, 20]). (Hint: np.newaxis).
3. Harder one: Generate a 10 x 3 array of random numbers (in range [0,1]). For each row, pick the
number closest to 0.5.
• Use abs and argsort to find the column j closest for each row.
• Use fancy indexing to extract the numbers. (Hint: a[i,j] – the array i must contain the
row numbers corresponding to stuff in j.)
Here are a few images we will be able to obtain with our manipulations: use different colormaps, crop
the image, change some parts of the image.
• The face is displayed in false colors. A colormap must be specified for it to be displayed
in grey.
• Create an array of the image with a narrower centering [for example,] remove 100 pixels
from all the borders of the image. To check the result, display this new array with imshow.
• We will now frame the face with a black locket. For this, we need to create a mask cor-
responding to the pixels we want to be black. The center of the face is around (660, 330), so
we defined the mask by this condition (y-300)**2 + (x-660)**2
then we assign the value 0 to the pixels of the image corresponding to the mask. The syntax
is extremely simple and intuitive:
>>> face[mask] = 0
>>> plt.imshow(face)
<matplotlib.image.AxesImage object at 0x...>
• Follow-up: copy all instructions of this exercise in a script called face_locket.py then
execute this script in IPython with %run face_locket.py.
Change the circle to an ellipsoid.
1
over this volume with the mean. The exact result is: ln 2 − 2 ≈ 0.1931 . . . — what is your relative error?
(Hints: use elementwise operations and broadcasting. You can make np.ogrid give a number of points
in given range with np.ogrid[0:1:20j].)
Reminder Python functions:
def f(a, b, c):
return some_result
c = x + 1j*y
z = 0
for j in range(N_max):
z = z**2 + c
2D plotting
Plot a basic 2D figure
import numpy as np
import matplotlib.pyplot as plt
1D plotting
Plot a basic 1D figure
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 3, 20)
y = np.linspace(0, 9, 20)
plt.plot(x, y)
plt.plot(x, y, 'o')
plt.show()
Distances exercise
Plot distances in a grid
import numpy as np
import matplotlib.pyplot as plt
Fitting to polynomial
Plot noisy data and their polynomial fit
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(12)
x = np.linspace(0, 1, 20)
y = np.cos(x) + 0.3*np.random.rand(20)
p = np.poly1d(np.polyfit(x, y, 3))
t = np.linspace(0, 1, 200)
plt.plot(x, y, 'o', t, p(t), '-')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
x = np.linspace(-1, 1, 2000)
y = np.cos(x) + 0.3*np.random.rand(2000)
p = np.polynomial.Chebyshev.fit(x, y, 90)
plt.plot(x, y, 'r.')
plt.plot(x, p(x), 'k-', lw=3)
plt.show()
Population exercise
Plot populations of hares, lynxes, and carrots
import numpy as np
import matplotlib.pyplot as plt
data = np.loadtxt('../data/populations.txt')
year, hares, lynxes, carrots = data.T
import numpy as np
import matplotlib.pyplot as plt
original figure
plt.figure()
img = plt.imread('../data/elephant.png')
plt.imshow(img)
plt.figure()
img_red = img[:, :, 0]
plt.imshow(img_red, cmap=plt.cm.gray)
lower resolution
plt.figure()
img_tiny = img[::6, ::6]
plt.imshow(img_tiny, interpolation='nearest')
plt.show()
Mandelbrot set
Compute the Mandelbrot fractal and plot it
import numpy as np
import matplotlib.pyplot as plt
from numpy import newaxis
c = x[:,newaxis] + 1j*y[newaxis,:]
# Mandelbrot iteration
z = c
# The code below overflows in many regions of the x-y grid, suppress
# warnings temporarily
with np.warnings.catch_warnings():
np.warnings.simplefilter("ignore")
for j in range(N_max):
z = z**2 + c
mandelbrot_set = (abs(z) < some_threshold)
return mandelbrot_set
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(t_max)
# Steps can be -1 or 1 (note that randint excludes the upper limit)
steps = 2 * np.random.randint(0, 1 + 1, (n_stories, t_max)) - 1